Getting a 403 Forbidden when scraping is not a server crash or a network blip — it is the target site telling your bot exactly what it thinks of you. The response is deliberate, and fixing it requires knowing which of a dozen different triggers fired. This guide covers the 12 most common causes in 2026 and what to actually do about each one.
Why 403s Are Not All the Same
A 403 from nginx serving a static file is trivially different from a 403 issued by Akamai’s bot manager after scoring your TLS fingerprint. Lumping them together wastes hours. The first thing to check is the response body and headers — many WAFs embed a reference code or vendor signature that tells you exactly which layer blocked you.
| Source | Signature in response | Fix category |
|---|---|---|
| Cloudflare | cf-ray header, Turnstile/IUAM page | IP reputation, fingerprint |
| Akamai | Reference #18.xxxxxx in body | Sensor data, TLS JA3 |
| AWS WAF | x-amzn-requestid, terse JSON | Header rules, rate |
| nginx/Apache | Plain HTML 403, no vendor markers | File permissions, allow/deny rules |
| App-level | Custom JSON {"error": "forbidden"} | Auth token, CSRF, geo |
Once you know the source, the fix becomes mechanical.
Causes 1 to 5: Request Fingerprinting Failures
1. Missing or wrong User-Agent. Requests without a User-Agent, or with the default python-requests/2.x.x string, are blocked by virtually every serious target in 2026. Use a real Chrome UA and rotate it.
2. Incomplete header set. Browsers send 12 to 18 headers in a specific order. Sending only 4 headers in alphabetical order (the requests library default) is a fingerprint. Add Accept, Accept-Language, Accept-Encoding, Sec-Fetch-, and Sec-Ch-Ua- headers and match the order Chrome uses.
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
}3. TLS/JA3 fingerprint mismatch. Akamai, PerimeterX, and DataDome score your TLS client hello. Python’s requests library uses OpenSSL defaults that produce a JA3 hash nothing like Chrome. Use curl_cffi with impersonate="chrome124" or route through a browser. If you are seeing Akamai Reference Number 18.xxxxxx errors, TLS fingerprinting is almost always the root cause.
4. No cookies from prior navigation. Protected pages expect session cookies set on the homepage or a login redirect. Hitting a deep URL cold with no cookies triggers a 403. Use a session object and load the root page first.
5. Headless browser detection. navigator.webdriver === true, missing plugins array, and synchronous Date.now() patterns all expose headless Chrome. Use Playwright with stealth patches or Camoufox. If your headless browser is also hanging before it even gets a 403, see the headless Chrome timeout diagnosis guide.
Causes 6 to 9: IP and Network Issues
6. Datacenter IP range. AWS, GCP, and Azure IP ranges are blacklisted by default on most e-commerce and travel sites. This is the single most common cause of 403s for scrapers running in cloud VMs. Residential or mobile proxies fix this.
7. IP already flagged or rate-limited. A 403 after successfully scraping 200 pages usually means your IP hit a velocity threshold. Rotate IPs per request or per session depending on the target’s aggression.
8. Geo-block. Some sites 403 non-US or non-local IPs entirely. Check with a VPN or geo-targeted proxy before assuming it is a bot detection problem.
9. Shared proxy reputation. Cheap shared proxies carry abuse history. If you are using a shared pool and seeing instant 403s with clean headers, the IP is already burned. Cloudflare’s tiered IP block system distinguishes between temporary soft-blocks and permanent bans — a tier-3 Cloudflare ban means that IP is dead for that domain, rotate out entirely.
Causes 10 to 12: Auth, Tokens, and Server Config
10. Missing or expired auth token. API endpoints and AJAX calls often require a bearer token, CSRF token, or x-api-key that was issued during page load. Scraping the JSON endpoint directly without first extracting the token gives a 403. Capture tokens from the initial HTML or use browser automation to inherit the session.
11. Referrer or origin check. Some sites validate that Referer or Origin matches an expected domain. Sending no referrer on a request that should appear to come from within the site is a fast 403. Set Referer: https://targetsite.com/ explicitly.
12. Server-side file or route permission. On self-hosted targets or less-sophisticated sites, a plain 403 can mean directory listing is disabled, an .htaccess rule blocks the path, or a WAF rule matches your URL pattern. This is the only cause where the fix is entirely on the server side. If you are scraping your own staging environment and hitting 403s, check file permissions and Apache/nginx allow rules before assuming bot detection.
Debugging Workflow
When a 403 lands, run through this sequence before changing anything:
- Check response headers for vendor signatures (cf-ray, x-amzn, akamai-reference).
- Check response body for an embedded error code or challenge page.
- Replay the request with
curl -vusing the exact same headers to isolate whether it is headers or IP. - Swap to a residential proxy to rule out IP reputation.
- If it passes with curl but not your scraper, the delta is TLS fingerprint or cookie state.
- If it fails with curl too, check geo-block and auth token requirements.
A 403 that escalates to intermittent 503s as you scale is a different problem — rate limiting and origin overload follow a different diagnostic path, covered in the HTTP 503 scraping diagnosis guide.
Bottom Line
Most 403s in 2026 collapse into two root causes: datacenter IP reputation and a browser fingerprint that does not survive scrutiny. Fix the IP layer first (residential or mobile proxies), then fix the fingerprint layer (curl_cffi, Playwright stealth, correct header order). DRT covers both layers in depth across its scraping infrastructure guides — if a specific WAF or error code is still blocking you after this checklist, search for the vendor name in the archive.
Related guides on dataresearchtools.com
- HTTP 503 Service Unavailable When Scraping: Diagnosis Guide (2026)
- Cloudflare Error 1006/1007/1008: IP Block Tier Diagnosis
- Akamai Reference Number 18.xxxxxx: Decoding the Error Code (2026)
- Why Your Headless Chrome Times Out: Common Causes and Fixes (2026)
- Pillar: 403 Forbidden in Web Scraping: How to Fix It