HTTP Errors

What Is the 403 Status Code? (403 Forbidden)

What Is a 403 Error (Forbidden)? — conceptual illustration
On this page

HTTP 403 Forbidden means the server understood your request but refuses to answer it. The difference from 401 is simple: 401 means "we don't know who you are, log in first," while 403 means "we know who you are and the answer is still no." For scrapers, 403 is the classic anti-bot block — the server decided your request looks automated and cut it off before sending the page.

Quick facts

Status code403
Category4xx Client Error
Common causes (scraping)Bot detection, missing/wrong headers, blocked IP, geo restriction
Common causes (general)Insufficient permissions, expired auth, IP allowlist
Typical body"Access Denied", "Forbidden", or a Cloudflare challenge page

Why 403s happen in scraping

On a normal API, a 403 just means you logged in but aren't allowed to see this particular thing. In scraping it almost always means an anti-bot system spotted you. Common triggers: your IP belongs to a known datacenter range (servers, not homes), a missing or odd User-Agent (the string a browser sends to identify itself), a TLS fingerprint that doesn't match that User-Agent (TLS is the encryption layer behind https, and its handshake quietly reveals which client you really are), too many requests too fast, a geo block on your country, or a behavioral signal picked up on an earlier page. Cloudflare in particular returns 403 with its own branded challenge page as the body — if you see "Cloudflare" and a ray ID in the HTML, that's the source. The 403 is just the symptom; the real decision happened a layer above it.

How to diagnose a 403

Start by reading the response body — that's where the truth is. A short plain "Forbidden" page usually means a simple WAF rule (a web application firewall, basic pattern-matching at the edge); a Cloudflare/Akamai/DataDome branded page means a dedicated bot-detection service. Check the response headers for cf-ray, x-amzn-waf-action, server: AkamaiGHost, or x-datadome to see which vendor it is. Then check what you're sending: is the User-Agent realistic? Is Accept-Language present? Does your TLS fingerprint match the browser you claim to be? Finally, try the same URL through a residential proxy in the target's main country — if that works, the block was based on your IP; if it still fails, the block is based on your fingerprint.

How to recover from a 403

The fix depends on the cause. IP-based 403s clear by rotating through residential or mobile proxy addresses. Header-based 403s clear with realistic headers — copy a real browser's headers from DevTools exactly. Fingerprint-based 403s need a real browser stack: Playwright driving a real browser engine, or a managed scraping API that maintains a consistent browser configuration. A 403 that redirects to a CAPTCHA needs a solver. A geo-based 403 needs a proxy in the right country. One rule above all: never retry a 403 without changing something — repeat 403s from the same identity only make the block stronger.

Fixing a 403 in Python requests

Work through four layers in order — most 403s are solved by the first two:

  1. Send a realistic User-Agent. The default python-requests/2.x string is the single most common cause of a 403 that works fine in a browser.
  2. Send the full browser header setAccept, Accept-Language, Accept-Encoding, and Referer. A request with only a User-Agent still looks nothing like a real browser.
  3. Persist cookies with a requests.Session(). Many sites set a cookie on first load and 403 any request that arrives without it.
  4. If 403s survive all of the above, the site is fingerprinting your TLS/HTTP2 handshake (typical of Cloudflare and DataDome). Plain requests can't change that — switch to curl_cffi with impersonate="chrome", a headless browser, or a managed scraping API.

The code example below shows the headers-plus-session approach and the curl_cffi fallback side by side.

Code example

python
import requests

# Layer 1+2+3: realistic headers + a session that persists cookies.
BROWSER_HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/124.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.9",
    "Accept-Encoding": "gzip, deflate, br",
    "Referer": "https://www.google.com/",
}

session = requests.Session()
session.headers.update(BROWSER_HEADERS)
resp = session.get("https://example.com", timeout=30)
print(resp.status_code)   # often 200 once headers + cookies look real

# Layer 4: still 403? The site is fingerprinting your TLS handshake.
# curl_cffi impersonates a real browser's TLS/JA3, which requests can't do.
#   pip install curl_cffi
from curl_cffi import requests as cffi
resp = cffi.get("https://example.com", impersonate="chrome", timeout=30)
print(resp.status_code)

Related terms

What Is the 429 Status Code (429 Error)?
HTTP 429 Too Many Requests is the status code a server returns when a client has sent more requests in a given window than the server's rate…
What Is the 503 Status Code (503 Service Unavailable Error)?
HTTP 503 Service Unavailable means the server can't handle your request right now — usually because it's overloaded, under maintenance, or d…
What Is Anti-Bot Detection?
Anti-bot detection is the set of techniques websites use to tell automated traffic apart from real human visitors — and then block, challeng…
What Is Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
What Is a 200 Status Code?
HTTP 200 OK is the standard "success" status code: the server got your request, handled it, and sent back the response you expected. For a G…
What Is a 402 Error?
HTTP 402 Payment Required is the status code a server sends to say: "I won't do this until a payment, billing, or quota problem is fixed." I…
What Is a 404 Error?
HTTP 404 Not Found is the server's way of saying "I understood your request, but there is nothing at this address." The server is working fi…
What Is the 502 Status Code (502 Bad Gateway)?
HTTP 502 Bad Gateway means one server, acting as a middleman, got a broken reply from another server behind it. Many websites sit behind a g…
What Is Cloudflare Error 1020 (Access Denied)?
Cloudflare Error 1020 "Access Denied" means a Cloudflare firewall (WAF) rule on the site has blocked your request outright. Unlike Error 101…
How to Make curl Ignore SSL Certificate Errors
To make curl ignore SSL certificate errors, add the -k (or --insecure) flag: curl -k https://example.com. This tells curl to skip certificat…
How to Use Basic Auth with curl
To send HTTP Basic Authentication with curl, use the -u (or --user) flag: curl -u username:password https://example.com. curl Base64-encodes…

Concept map

How 403 Status Code (403 Forbidden Error) connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · HTTP Errors
Building map…

Frequently asked questions

What's the difference between 401 and 403?

401 Unauthorized means you haven't proven who you are yet — log in and try again. 403 Forbidden means you're already authenticated (or no login is needed) but still aren't allowed in. For scraping without a login, 403 is the one you'll actually run into.

Does a 403 mean my IP is banned?

Sometimes. The quickest test is to send the exact same request through a different IP. If the second attempt works, the first IP is on a block list. If it still fails, the block is based on something other than your IP — usually your fingerprint or headers.

Can changing User-Agent fix a 403?

It can satisfy the most basic rules but not modern detection. Real systems cross-check the User-Agent against your TLS fingerprint, the order of your headers, signals collected by JavaScript, and your behavior. Swapping the UA alone matters little; making the UA line up consistently with everything else is what counts.

Is a 403 the same as a Cloudflare block?

Cloudflare blocks usually do return 403 with their challenge page in the body, but plenty of other sources return 403 too. Read the response body and headers to identify which vendor (Cloudflare, Akamai, DataDome, PerimeterX) is doing the blocking.

How do I fix a 403 Forbidden error in Python requests?

Set a real browser User-Agent and the full header set (Accept, Accept-Language, Referer), and use a requests.Session() so cookies persist. If you still get 403, the site is fingerprinting your TLS handshake — switch to curl_cffi with impersonate="chrome", a headless browser, or a scraping API that emulates a real browser.

Last updated: 2026-06-08