OP-Proxy
PricingBlogResellerAPI
Dashboard
← Blog
Troubleshooting·August 17, 2026·5 min read

403 through a proxy: the site refused, the proxy refused, or the address is not the problem

403 is a code that can arrive from two different places: the proxy that would not let you through, and the target site that would not let you in. The remedies are opposite, and the code itself does not distinguish them. Here is where the difference is visible, where it is invisible in principle, and when changing addresses will not help at all.

In this article

  • When a 403 can be attributed at all
  • How to see the code the proxy returned
  • Cloudflare's codes live in the response body
  • What each code means
  • When changing the address will not help
  • Why proxy headers are a poor test
  • Codes that are definitely not about the proxy

When a 403 can be attributed at all

It can be told apart reliably only over HTTPS, and here is why. For HTTPS the client first asks the proxy to open a tunnel with CONNECT. Per RFC 9110, any response to CONNECT other than a successful one means the tunnel was not established: a 403 at that stage came from the proxy, and the request never reached the site. Once the tunnel is open, anything arriving inside it came from the site — the proxy cannot inject into an encrypted stream.

For plain http:// there is no CONNECT at all, the request goes through the proxy directly, and the status line looks identical whether the proxy or the site produced it. Here the code cannot separate them — only the response body can, and not always. That is a limitation, not a gap in your diagnostics.

How to see the code the proxy returned

curl has a dedicated output field for this: http_connect, the code found in the proxy's last response to a CONNECT request. That is what to read, not the utility's exit code — the exit number has changed between curl versions, this field has not.

bash
# http_code    -> what the site answered
# http_connect -> what the proxy answered to CONNECT
curl -x http://GATEWAY:PORT https://example.com -o /dev/null \
     -w 'connect=%{http_connect} site=%{http_code}\n'

# connect=200 site=403 -> the proxy let you through, the 403 is the site's
# connect=403 site=000 -> the proxy refused, you never reached the site

The failed-tunnel message is worth knowing in two forms: modern curl prints “CONNECT tunnel failed, response 403”, while “Received HTTP code 403 from proxy after CONNECT” was its wording up to version 7.87.0 of December 2022 — which is what half the guides in search were written against.

Cloudflare's codes live in the response body

If the target sits behind Cloudflare, the most useful thing is the four-digit 1xxx code. The documentation says it plainly: ordinary codes such as 403, 409 or 429 are returned in the HTTP status header, while 1XXX errors appear in the HTML body. So grepping the status line will not find them — look in the body.

And an inverse signal from Cloudflare itself: a 403 without its branded page means the refusal came from the site's server rather than from Cloudflare. That said, it cannot be relied on as a rule — Cloudflare also serves an unstyled 403, and it will return whatever code a site owner configured in their rule.

What each code means

  • 1005 — the ASN is banned, that is, the autonomous system the address belongs to. A network-level refusal, not an address-level one.
  • 1006, 1007, 1008 and 1106 — Cloudflare documents them under a single heading, “access denied: your IP address has been banned”, with a single cause: a Cloudflare customer blocked your client. The documentation gives these numbers no distinct meanings.
  • 1009 — the country or region is banned. Fixed by a different exit country, not by a different address inside the same one.
  • 1010 — refused on the browser's signature. The site owner enabled the browser integrity check; the address has nothing to do with it.
  • 1020 — refused by a Cloudflare firewall rule. Not the same as an address ban: the rule can look at anything.
Code 1015 (“you are being rate limited”) is not on this list: rate-limiting rules block with 429, not 403. If you are getting a 403, request frequency is not the cause and pauses will not cure it — there is a separate article on 429 and computing delays.

When changing the address will not help

This is the main practical conclusion and it runs against intuition. Growing the address pool makes sense only when the refusal is bound to an address or a network: 1005 is answered by a different autonomous system, 1006–1008 by a different address, 1009 by a different country. But 1010 and 1020 are not bound to an address at all: the first looks at the browser signature, the second at a rule that may inspect headers, the path or anything else. Buying more addresses and getting the same refusal is the most common way to waste money here.

Why proxy headers are a poor test

The advice “check whether the response has Via or X-Squid-Error” is common and unreliable in both directions. A proxy may not send Via at all, may substitute a pseudonym for its name, and an administrator can strip the header by configuration. X-Squid-Error exists only in Squid and is likewise removable. The absence of these headers does not prove the site answered, and their presence does not prove the opposite for a given request.

Relatedly: a proxy denying a request by its own access list is not obliged to return 403. Squid returns 403 by default, but configuration can change that to any other code, to a redirect, or to dropping the connection with no response at all. And an authentication refusal is 407 — a separate code with a separate cause.

Codes that are definitely not about the proxy

  • 451 — refused for legal reasons; for a Russian-speaking audience more often regulatory blocking than anti-bot.
  • 421 — per RFC 9110 a proxy must not generate it, so a 421 always came from the origin or the site's gateway.
  • 400 on CONNECT — a server must reject a request with an empty or invalid port number, typically with exactly this code. So check the port in your connection string.

When it really is about addresses

If the refusal is bound to an address or a network, a pool helps: rotating IPv6 from 650 ₽ for 50 threads, IPv4 from 1375 ₽ for 100. If it is a browser-signature or rule refusal, a pool will not help — and we will say so.

View pricing

Proxies for this job

  • For parsing and scraping
  • Rotating IPv6 and IPv4
  • For marketplaces

Check it with our tools

  • My IP address
  • Website check

Read next

  • Proxy error 407 Proxy Authentication Required: why the proxy rejects your scraper407 comes from the proxy, not the target site. Five causes by frequency, a one-command check, and what to do when your software only accepts ip:port.
  • Error 429 while scraping: how to size pauses and retries instead of guessing429 is a per-address limit, not a per-account one. Retry-After, exponential backoff with jitter, and why backoff works differently in Scrapy.
  • One gateway instead of a proxy list: configuring software that expects a listScrapers measure scale by proxy count, and a rotating gateway is one line. Program by program: which mode to switch, where to duplicate the line, and where the honest answer is to buy a list.
OP-Proxy
Plans & Information
PricingBlogReseller programAPITerms of ServicePrivacy Policy
Proxies by use case
For parsing and scrapingFor multi-threaded softwareFor marketplacesFor antidetect browsersFor captcha softwareFor SEO and SERP checksRotating IPv6 and IPv4IPv6 proxiesStatic datacenter IPv4
Tools
My IP AddressSpeed TestAnonymity CheckWHOIS LookupDNS Leak TestWebsite Check
ИП Артамонов Анатолий Михайлович ОГРНИП 324700000026212 ИНН 701755408691