Seeing a Cloudflare page with the 403? Go to Cloudflare 403 Forbidden, which reads the Cloudflare-specific signals.
On this page 7 sections
What does 403 Forbidden mean when scraping?
A 403 is a refusal, not a failure. RFC 9110, section 15.5.4 says the server "understood the request but refuses to fulfill it", and that a server which wants to hide why may answer 404 instead. For scrapers, the refusal usually comes from one of three places:
| Who refuses | How to recognize it | Typical reason |
|---|---|---|
| A bot-protection or firewall layer in front of the site (a CDN, a WAF, a bot manager) | A branded block page, a reference or incident ID, cookies or headers you don't see on normal pages | The request looks automated: headers, fingerprint, rate, IP reputation |
| The website itself | The site's own 403 page | Your IP, country or account is blocked, or the page needs a login |
| The proxy | A 403 that comes back for one target from every exit IP, in the proxy's own format | The proxy service blocks that site category |
The last row is rare but worth ruling out. ProxyField restricts some site categories to protect the network, including financial and government services, but its gateway has no 403 or other response of its own for a restricted target. A 403 through ProxyField comes from the site and is passed through.
Why does my scraper get a 403 when the page works in my browser?
Your browser and your scraper send very different requests, even from the same IP:
- Headers. A browser sends a full, consistent set:
User-Agent,Accept,Accept-Language,Accept-Encoding,Sec-Fetch-*and, in Chrome, client hints. An HTTP library sends a few defaults, and a defaultUser-Agentsuch aspython-requests/2.xis easy to refuse. - Cookies and JavaScript. Many sites set a cookie through JavaScript on the first visit and expect it on every later request. A scraper that doesn't run JavaScript never gets it.
- TLS and HTTP/2 fingerprint. The way a client opens the encrypted connection identifies its library, whatever
User-Agentit claims. - Rate and pattern. A browser loads one page every few seconds; a scraper loads hundreds a minute, in order, with no assets.
If the page loads in a normal browser over the same proxy IP, the IP is fine and your client is the problem. If it fails in the browser too, the IP or the network it belongs to is the problem.
How to fix a 403 Forbidden error when scraping
Change one thing at a time and re-test, simplest first.
1. Send complete, consistent browser headers
Copy the headers a real browser sends to that site (the browser's developer tools show them) and send the same set, in a consistent combination: a Chrome User-Agent with Chrome's Accept values, not a mix. Web scraping headers lists what a real browser sends and what to copy. Add the Referer the site would see when a user clicks through.
2. Keep cookies across requests
Use a session object (requests.Session(), a Scrapy cookie jar, one browser context) so the cookies the site sets on the first page go back on the next ones.
3. Use a real browser for pages that need JavaScript
If the HTML you get is a near-empty shell or a challenge page, load it with Playwright, Puppeteer or Selenium and keep one browser context per session. Setup with proxies: Playwright, Puppeteer, Selenium.
4. Slow down
A 403 that starts after a burst, or after a run of 429s, means a rate rule escalated to a block. Lower concurrency per domain, add random delays and back off on errors; the 429 Too Many Requests doc has a retry loop.
5. Change the IP type, and use sticky sessions for flows
If a browser on your current IP is refused too, the site distrusts the IP or its network. Datacenter ranges are the usual target; move to residential proxies, mobile proxies or static residential (ISP) proxies. Use a new IP per request for independent pages, and one sticky IP (30 s to 36 h) for logins, carts and multi-page flows, so cookies and IP stay together. Choose the country the site serves: country and city targeting are part of the username, as in <login>-country-us-city-new_york. Region and session syntax: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default) after the location, as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600.
6. Use an API where the site offers one
An official API or data export is the most stable route. Otherwise, ProxyField's Web Scraping API fetches any URL as plain HTTP or with JavaScript rendering, billed on successful results only; contact sales for access.
7. If the block is deliberate, stop
If every client and IP you try gets a 403, the site has decided not to serve automated traffic. Read its terms and robots.txt, and ask for access. Getting past a barrier set up on purpose can raise legal risk: see is web scraping legal.
Test a 403 through the proxy
This Python snippet sends the same URL twice through the ProxyField gateway, once with library defaults and once with browser-like headers in a session, and prints both status codes and the page title. Replace <login>, <password> and the URL.
import re
import requests
PROXY = "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"
PROXIES = {"http": PROXY, "https": PROXY}
URL = "https://example.com/"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/130.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.9",
"Accept-Encoding": "gzip, deflate",
}
def title(html):
m = re.search(r"<title>(.*?)</title>", html, re.I | re.S)
return m.group(1).strip() if m else None
plain = requests.get(URL, proxies=PROXIES, timeout=30)
print("defaults:", plain.status_code, title(plain.text))
with requests.Session() as s:
s.headers.update(HEADERS)
s.proxies.update(PROXIES)
browserlike = s.get(URL, timeout=30)
print("browser-like:", browserlike.status_code, title(browserlike.text))If only the second request passes, headers were the cause. If both fail, try a real browser on the same proxy IP (fix 3), then another IP type (fix 5).
How to fix 403 Forbidden in Scrapy
Scrapy's HttpErrorMiddleware drops non-2xx responses before your callback sees them, so a 403 shows up only as a log line. To read the block page, let 403 through for that spider (Scrapy docs: HttpErrorMiddleware):
import scrapy
class ProductSpider(scrapy.Spider):
name = "products"
handle_httpstatus_list = [403]
custom_settings = {
"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
"DEFAULT_REQUEST_HEADERS": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
},
"AUTOTHROTTLE_ENABLED": True,
}
def start_requests(self):
yield scrapy.Request(
"https://example.com/",
meta={"proxy": "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"},
)
def parse(self, response):
if response.status == 403:
self.logger.warning("403 on %s: %s", response.url, response.css("title::text").get())
return
yield {"title": response.css("title::text").get()}Scrapy's default User-Agent names Scrapy, so setting a browser one is the first fix. Full proxy setup: Scrapy.
403 vs 401, 407 and 429
| Code | Meaning | What fixes it |
|---|---|---|
| 401 Unauthorized | The site wants you to log in | Valid credentials for the site |
| 403 Forbidden | The site refuses the request | Change what its rule checks: headers, browser, rate, IP |
| 407 Proxy Authentication Required | The proxy wants your proxy login | The right proxy username and password: 407 Proxy Authentication Required |
| 429 Too Many Requests | A rate limit | Slow down and wait |
A 403 that persists for one IP is also covered in why is my IP address blocked.
403 Forbidden When Scraping Through a Proxy FAQ
Why does my scraper get a 403 while the page loads fine in my browser?
ProxyField residential proxies start at $1.95/GB, but a page that loads in your browser and not in your scraper usually points at your client, not the IP: the site is refusing the scraper's headers, cookies, JavaScript or TLS fingerprint. Test it with 2 requests over the same proxy IP, one from a normal browser and one from the scraper; if both fail, change the IP type.
What causes a 403 error when scraping?
A rule on the site, or a bot-protection layer in front of it, decided the request looks automated or unwelcome. The usual triggers are default library headers, missing cookies or JavaScript, a fast request rate, and an IP or network with a poor reputation.
How do I fix a 403 Forbidden error when scraping?
Fix the simplest cause first: send complete browser headers, keep cookies in a session, render JavaScript pages in a real browser, slow down, and only then change the IP type. If every client and IP is refused, the block is deliberate; ask the site for access.
Does a 403 error mean my IP is banned?
Not necessarily. Most scraper 403s are about the request, not the IP; test the same page in a browser on the same IP to tell which one it is.
Why do I get 403 errors only through a proxy?
The site distrusts the proxy IP or its network, often a datacenter range, or it serves only some countries and your exit is elsewhere. Try a residential or mobile IP in a country the site serves, and keep the same headers you use without the proxy.
Does a 403 error go away on its own?
Sometimes: a block triggered by a burst of requests may lift after minutes or hours. A 403 caused by your headers, client or IP type stays until you change that.
How do I fix 403 Forbidden in Scrapy?
Set a real browser USER_AGENT and default headers, turn on AutoThrottle, and add handle_httpstatus_list = [403] to read the block page. If the page needs JavaScript, render it with a browser integration; if the IP is refused, set a residential proxy in request.meta["proxy"].
Why do I still get 403 Forbidden despite rotating IPs and random user agents?
Random user agents often make it worse: a Chrome User-Agent on a Python client contradicts its other headers and TLS fingerprint. Send one consistent browser profile, keep cookies in sessions and use a real browser where the site checks JavaScript.
Do I need rotating proxies for every 403?
No. Rotation helps when the site blocks or rate-limits per IP; it does nothing for header, cookie, JavaScript or fingerprint checks, which cause most scraper 403s.
What is the difference between a 401 and a 403 in web scraping?
A 401 asks you to log in to the site, and valid credentials fix it. A 403 refuses the request even if you are logged in, so you need to change what the site's rule checks, or stop.
Show 2 more questionsShow fewer questions
What does "403 Forbidden - rate limit exceeded" mean?
The server enforces a rate limit and answers with 403 instead of the usual 429. Treat it like a 429: wait, slow down, back off on each error, and spread requests over more IPs only if the limit counts IPs.
How can I prevent 403 errors at scale?
Keep each request consistent with one real browser, pace requests per domain, reuse cookies through sessions, and match the IP type and country to the site. Watch the share of 403s per target and slow down as soon as it rises.