curl geeft 403 maar de browser werkt: Vind het ontbrekende stuk

De browser laadt het. curl krijgt 403. Het verschil tussen die twee verzoeken is altijd eindig en altijd vindbaar — hier is hoe je het in vijf minuten kunt halveren.

Je plakt een URL in Chrome en de pagina laadt. Je plakt dezelfde URL in curl en krijgt 403 Forbidden. Niets aan de bron veranderde tussen die twee seconden, dus het verschil zit volledig in het verzoek — en een verzoek is een eindig, inspecteerbaar ding. Deze gids is een bisectieprocedure: speel precies na wat de browser stuurde, verwijder dan stukjes totdat de 403 terugkomt. Wat je als laatste verwijderde, is je antwoord. De verdachten, in de volgorde waarin ze meestal schuldig zijn, zijn User-Agent, Referer, cookies, TLS-vingerafdruk en JavaScript.

Stap 0: bewijs dat de verzoeken echt verschillend zijn

Voordat je theoretiseert, kijk naar wat curl daadwerkelijk verstuurt. Met -v zie je de verzoekregel, elke header en de TLS-handshake. Een standaard curl-verzoek is verrassend dun — typisch Host, User-Agent: curl/8.x en Accept: */*. Een browser stuurt een dozijn meer.

# what you send, what you get back, and the TLS details
curl -v -o /dev/null https://target.example/page

# just the response headers, quickly
curl -sS -o /dev/null -D - https://target.example/page

Lees de antwoordheaders net zo zorgvuldig als de statusregel. Een ervan beantwoordt de vraag direct in het meest voorkomende geval: Vary: User-Agent betekent dat de server opzettelijk verschillende antwoorden geeft, afhankelijk van wie je beweert te zijn. In een goed gedocumenteerd Stack Overflow-geval gaf curl -f tegen een gewone Apache 2.4.38-host 403 terug terwijl wget hetzelfde bestand met een 200 ophaalde — en het succesvolle antwoord bevatte precies die Vary: User-Agent header. Het doorgeven van -A 'Wget/1.21.2' aan curl loste het onmiddellijk op. De site-eigenaar had de user-agent van curl na misbruik op de zwarte lijst gezet; niets anders aan het verzoek deed ertoe.

Terwijl je de uitvoer leest: curl: (22) De opgevraagde URL gaf fout: 403 terug is geen apart probleem. Exitcode 22 is wat -f/--fail doet met elke HTTP-fout — de vlag onderdrukt de body en laat het commando falen. Laat -f tijdelijk weg zodat je de blokkeerpagina daadwerkelijk kunt lezen, die meestal het systeem noemt dat je stopte.

Stap 1: Kopieer als cURL, het 30-seconden antwoord

Beide grote browsers kunnen je het exacte verzoek geven dat ze zojuist hebben gedaan. Open DevTools, ga naar het tabblad Netwerk, klik met de rechtermuisknop op het verzoek en kies "Kopieer als cURL". Chrome heeft dit sinds versie 26 en Firefox sinds 31, en de uitvoer bevat elke header, elke cookie en de referer. Plak het in je terminal: als het 200 teruggeeft, ligt je probleem definitief in de vorm van het verzoek, en stap 2 vindt welk deel.

Een valkuil die hier veel tijd verspilt. Als de URL omleidt, wordt het Netwerkpaneel gewist bij navigatie en kopieer je het verkeerde verzoek. Vink "Logboek behouden" aan in Chrome of "Persistente logs" in Firefox eerst, zodat je zowel het verzoek dat omleidde als het verzoek dat uiteindelijk inhoud serveerde kunt zien. Omleidingsketens zijn belangrijk: in een bekende Unix Stack Exchange-thread controleerde de server de Referer, en sprong toen via een 302 naar een locatie die niets controleerde — wat de fout willekeurig deed lijken totdat de hele keten zichtbaar was.

Zij-aan-zij vergelijking van een Chrome-browserverzoek en een standaard curl-verzoek met weergave van headeraantal, cookies, TLS-vingerafdruk en JavaScript-ondersteuning
Elke 403 in dit scenario verbergt zich in deze kloof. Sluit het één kolom tegelijk.

Stap 2: halveer de headers

Begin met het werkende "Kopieer als cURL"-commando en verwijder headers één voor één, opnieuw uitvoeren na elke verwijdering. De eerste verwijdering die de 403 terugbrengt, noemt je schuldige. In de praktijk is het bijna altijd een van de vier.

curl -sS -o /dev/null -w '%{http_code}\n' \
  -A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
  -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
  -H 'Accept-Language: en-GB,en;q=0.9' \
  -e 'https://target.example/' \
  -b 'session=abc123; consent=1' \
  -L \
  'https://target.example/page'

Twee afsluitende details voor deze stap. Zet de URL tussen aanhalingstekens: een querystring met & of een toegangstoken wordt anders door je shell verminkt, en de resulterende 403 heeft niets met de server te maken. En als je aan het debuggen bent vanuit PHP of Node in plaats van de shell, repliceer dan dezelfde header set daar — de standaardinstellingen van libcurl binnen PHP verschillen van die van de command-line tool, wat de reden is waarom het identieke verzoek in een terminal kan slagen en in code kan falen. Onze curl proxy recepten dekt de vlagsyntax volledig.

Stap 3: wanneer identieke headers nog steeds 403 teruggeven

Als een byte-voor-byte kopie van de browserheaders nog steeds faalt, is de beslissing genomen voordat je headers werden geparsed. Twee lagen zitten eronder.

TLS-vingerafdruk. Je ClientHello — cipher suites, extensies, curve-voorkeuren, ALPN, plus het HTTP/2-instellingsframe dat volgt — wordt gehasht naar een JA3 of JA4-waarde. curl gebouwd tegen OpenSSL produceert er een die geen enkele browser ooit produceert, en anti-botsystemen vergelijken het met je geclaimde User-Agent. Beweren dat je Chrome bent terwijl je handshaket als OpenSSL is een tegenstrijdigheid die ze zijn gebouwd om te vangen. De oplossing is een client die browserhandshakes reproduceert: curl-impersonate op de command line, of curl_cffi vanuit Python.

# pip install curl_cffi
from curl_cffi import requests

proxy = "http://USER:PASS@gate.quantumproxies.io:8000"

r = requests.get(
    "https://target.example/page",
    impersonate="chrome",          # browser ClientHello + HTTP/2 settings
    proxies={"http": proxy, "https": proxy},
    timeout=20,
)
print(r.status_code, r.headers.get("content-type"))

Je IP. De browser die werkt is meestal op je thuisverbinding terwijl curl op een VPS draait. Hosting-ASN's zijn gepubliceerd en vooraf gescoord, dus hetzelfde verzoek vanaf een residentieel adres wordt anders beoordeeld voordat het zelfs maar wordt gelezen. Het wisselen van de exit is een wijziging van één regel met residential proxies — 90M+ IP's in meer dan 200 landen, HTTP en SOCKS5 op elk plan — en het is de snelste manier om het netwerk uit te sluiten. De mechanica van de vingerafdruklaag staan in JA3/JA4 TLS fingerprinting.

Sluit het netwerk uit met residentiële proxies

Beslisboomstroom van een curl 403 naar een 200-reactie: kopieer als cURL, halveer headers, imiteer TLS, render dan of gebruik een scraper API
Vier stappen, elk een test. Stop zodra de status 200 wordt — klim niet verder dan nodig is.

Stap 4: de pagina heeft een browser nodig, geen client

Soms is de 403 geen oordeel over jou — het is de foutmodus van een uitdaging die je nooit hebt geprobeerd. Een openbare GitHub-discussie over linkcheckers die npmjs.com raken, zegt het duidelijk: curl kan geen geldige oplossing voor de uitdaging produceren, dus het verzoek wordt geblokkeerd met een 403. De server geeft een klein JavaScript-probleem, wacht even op het antwoord en weigert alles wat het niet kan uitvoeren. Geen header set, geen vingerafdruk en geen IP slaagt voor een test die vereist dat er code wordt uitgevoerd.

Op dat punt heb je drie eerlijke opties: een echte browser aansturen en de kosten betalen, het JSON-eindpunt vinden dat de pagina zelf aanroept (vaak in hetzelfde Netwerk-tabblad dat je al open hebt), of de URL aan een dienst geven die op aanvraag rendert. De QuantumProxies Scraper API doet het laatste — browser-grade TLS, residentiële exits, JavaScript-rendering alleen waar een pagina het nodig heeft, en markdown, JSON of ruwe HTML terug van één verzoek. Als wat je krijgt een lege pagina is in plaats van een verboden pagina, is dat een andere diagnose: zie waarom je scraper een lege pagina retourneert. En als de blokkeerpagina een Cloudflare Ray ID bevat, ga dan naar Cloudflare fout 1020 in plaats daarvan.

Veelgestelde vragen

Waarom krijgt curl 403 als mijn browser dat niet doet?

Omdat curl ongeveer drie headers stuurt, geen cookies, geen referer en een niet-browser TLS-vingerafdruk, terwijl je browser een dozijn headers stuurt, een cookie jar en een Chrome-handshake. De server weigert het verzoek, niet de bron. Speel het exacte verzoek van de browser na met "Kopieer als cURL", verwijder dan headers één voor één om te vinden welk verschil ertoe doet.

Waarom slaagt wget waar curl 403 krijgt?

Bijna altijd de User-Agent. Sommige servers zetten curl's UA specifiek op de zwarte lijst na misbruik terwijl ze die van wget met rust laten — een gedocumenteerd geval toonde een Vary: User-Agent antwoordheader die bevestigde dat de server erop vertakt, en curl -A 'Wget/1.21.2' herstelde de 200. wget stuurt ook Accept-Encoding en Connection standaard, wat soms ook uitmaakt.

Hoe stel ik een User-Agent in curl in?

Gebruik -A 'string', of het equivalent -H 'User-Agent: string'. Geef de voorkeur aan een volledige, actuele browserstring boven een ingekorte Mozilla/5.0, die sommige servers nu afwijzen omdat geen enkele echte browser slechts twee tokens stuurt. Combineer het met bijpassende Accept en Accept-Language waarden zodat de hele set coherent blijft.

Wat betekent curl fout 22?

Exitcode 22 wordt geproduceerd door -f/--fail wanneer de server een HTTP-fout retourneert, en het bericht citeert de status — meestal 403. Het is een rapportagevlag, geen aparte fout. Verwijder -f om de response body te zien, die meestal de blokkade veel beter uitlegt dan de exitcode doet.

Kan een proxy een curl 403 oplossen?

Het lost de subset veroorzaakt door IP-reputatie of geografie op — een grote subset wanneer je script op een cloudhost draait en je browser niet. Het lost geen ontbrekende referer, afwezige cookie of JavaScript-uitdaging op. Test eerst headers, aangezien ze niets kosten, verander dan het exit-IP om de netwerklaag te isoleren.

Er is hier geen mysterie, alleen een kloof: de browser stuurde één verzoek en jij stuurde een ander. Kopieer die van de browser, verklein het totdat het breekt, en je zult altijd het stuk vinden dat ertoe deed — meestal een header, soms een vingerafdruk, af en toe een uitdaging die een echte browser nodig heeft om te beantwoorden.

Haal elke pagina op met de Scraper API