You point curl at a site through your proxy, wait for the HTML, and get one line back: 403 Forbidden. No page. No redirect. No login form. The proxy connected, the server answered, and the answer was no.
Nothing broke. The TCP handshake happened, your proxy relayed the bytes, and something read your request and refused it. That something is either the site or the proxy itself, and the next step depends on which.
What causes a 403 Forbidden error through a proxy?
A 403 means a server received your request and refused it. RFC 9110 puts it in one sentence: "The 403 (Forbidden) status code indicates that the server understood the request but refuses to fulfill it." Usually that server is the site, applying its own rules: a login you lack, a rule about clients, a limit on addresses or countries, or a firewall service in front of it. Sometimes it is the proxy, refusing on its own policy. The next section separates the two in one command.
First: who sent it?
One thing has to be settled before any fix, because the same status code comes from two different places. The site can refuse you, and the proxy can refuse you, on its own policy, before the site ever sees the request. RFC 9110 says the server understood the request and refused it, and a proxy is a server.
We built both cases on September 16, 2026, with a stub proxy and a stub site on our own machine, and asked curl for a plain page and an https page through each:
| What we asked for | Who refused | What came back |
|---|---|---|
| plain page | the proxy | exit 0, 403, with Via: 1.1 stub-proxy and Proxy-Agent headers and the proxy's own body |
| https page | the proxy | exit 56, CONNECT tunnel failed, response 403, the site never reached |
| plain page | the site | exit 0, 403, with the site's Server header and no Via |
| https page | the site | exit 0, HTTP/1.1 200 Connection established first, then the site's 403 |
That gives two checks, and both take one command:
# Ask for an https page. The answer is unambiguous.
curl -i --proxy http://203.0.113.7:8080 https://example.com/
# CONNECT tunnel failed, response 403 -> the proxy refused. The site is not involved.
# 200 Connection established, then 403 -> the proxy did its job and the site refused.
# Ask for a plain page and read the headers.
curl -i --proxy http://203.0.113.7:8080 http://example.com/
# A Via or Proxy-Agent header on the 403 -> the proxy answered.
# The site's own Server header and no Via -> the site answered.
The Via header is what makes the second check work: it is added by proxies on the way through, so a refusal that carries one came from a machine in the middle. Not every proxy sets it, which is why the https test is the reliable one.
There is a third sender worth knowing. A protection layer in front of the site can refuse before the site does, and Cloudflare marks its own challenge responses with a cf-mitigated header, documented here.
And if a script is doing the asking, make it notice. In our run the proxy refusal left curl at exit code 0, with the proxy's page as the body. A job that checks only the exit code recorded a success for a page it never fetched. Adding --fail turned the same request into exit 22 with the status in the message.
The proxy refused
CONNECT tunnel failed
or a Via or Proxy-Agent header
Use port 443
Squid denies other CONNECT ports
Check the proxy's rules
allowed addresses, blocked sites
Ask whoever runs it
an office proxy is their call
The site refused
Tunnel open, then 403
or a cf-mitigated header
Read the reason
the body may say why
Fix your own access
login, token, API key
Ask the site or use its API
its rules stay the same
If the proxy refused you
A proxy refuses for its own reasons, and its configuration says which. Squid is a common example. Its default configuration carries the line http_access deny CONNECT !SSL_ports, under the comment "Deny CONNECT to other than secure SSL ports", and Squid answers a denied request with 403 Forbidden unless its administrator sets another code. So an https site on an unusual port, such as https://example.com:8443, can be refused by the proxy while the same site on 443 goes through. Use port 443 where the site offers it.
Other refusals come from the proxy's own lists. An office or school proxy can block sites by name or category, and that rule is its administrator's to change. A paid proxy that admits only listed client addresses can refuse yours after your connection changes; the allowed addresses sit in your plan's settings. Read the proxy's own error page, too: it is the body that came back with the Via or Proxy-Agent header, and it often names the rule. Chrome shows the same refusal as ERR_TUNNEL_CONNECTION_FAILED, and a proxy that wants a login answers 407 instead, which our 407 guide covers.
If the site refused you
The tunnel opened, or no Via came back, and the site said no. Work through these in order.
- Read the answer. RFC 9110 allows a server "that wishes to make public why the request has been forbidden" to "describe that reason in" the response. Read the body and the headers. A
cf-mitigated: challengeheader means Cloudflare's challenge answered, as its documentation describes. A login page, a message or an error code from the site tells you more than the status. - Check your own access. Some 403s mean you lack a login cookie, a token or an API key, and then the request fails from any address. Test the same request from your own connection with the right credentials. RFC 9110 is direct about retries: the client "SHOULD NOT automatically repeat the request with the same" credentials, though it "MAY repeat the request with new or different" ones. It adds that a refusal can be "unrelated to the credentials."
- Say who you are. Some sites refuse anonymous scripts. Wikimedia states its rule plainly: "On Wikimedia wikis, if you don't supply a User-Agent header, or you supply an empty or generic one, your request will fail with an HTTP 403 error." Its policy asks for "an informative User-Agent string with contact information", and gives this example:
User-Agent: CoolBot/0.0 (https://example.org/coolbot/; coolbot@example.org) generic-library/0.0. Name your tool and a way to reach you. - Use the site's own route. Many sites offer an API or a data feed, and every site publishes its rules for automated clients in its robots.txt and its terms. If the site answers with 429 instead, it is asking you to slow down; our 429 guide covers waiting and retrying. When none of that fits, ask the site's owner.
- If the site is yours, allow your own traffic. Testing your own site through proxies from other countries can trip your own firewall. Cloudflare documents the fix for its customers: "Another common use of IP Access rules is to allow services that regularly access your site, such as APIs, crawlers, and payment providers." IP Access rules can name an address, a range, a network or a country, and "IP Access rules are available to all customers." Add the addresses you test from.
This page leaves out disguising a script as a browser, matching TLS fingerprints, rotating addresses past a refusal and moving to another country to pass a geographic block. Each of them works around a decision the site made.
When the 403 is not about the proxy at all
One more check, because chasing the proxy when the proxy is fine wastes hours. If the same request with the right credentials also fails from your own connection, the block is about authentication or permissions, and no proxy change touches it. If it works from your connection and fails through the proxy, the site treats the proxy's address differently, which is the site's rule, as in step 5 above when the site is yours.
Check what your proxy sends
A transparent proxy passes your real IP to the site in headers like X-Forwarded-For or Via. X-Forwarded-For is the de-facto standard MDN documents for carrying a client's originating IP through a proxy chain, and Via is defined in RFC 9110 section 7.6.3 to record the intermediaries a message passed through. An elite (high anonymity) proxy sends neither. Run your proxy through our proxy checker, which shows the headers the destination receives, so you know what the site sees before you read its answer.
Where HProxy fits
A proxy changes the address a request comes from, and a site's rules stay the same. For checking your own site, ads or prices from other countries, our residential proxies start at $0.44 per GB; allow their addresses in your own firewall as in step 5. Our proxy checker tells you whether an exit is elite before you route a request through it.
What we could not find out
- Which sites refuse proxy or hosting addresses as such. Sites set those rules one by one, and few publish them.
- How Cloudflare scores a request before it challenges. Its documents describe the tools a site owner sets, such as IP Access rules, and the
cf-mitigatedheader on a challenge.
Sources
- RFC 9110 section 15.5.4, June 2022, the 403 status code, read October 13, 2026.
- RFC 9110 section 7.6.3 and MDN on Via, read September 16, 2026: the header "is added by proxies, both forward and reverse", for tracking message forwards and avoiding loops.
- RFC 9110 section 9.3.6, June 2022, for the CONNECT request that an https page makes through a proxy.
- Squid's default configuration, src/cf.data.pre, read October 13, 2026.
- Wikimedia Foundation User-Agent Policy, read October 13, 2026.
- Cloudflare: detecting a challenge response, read September 16, 2026, and IP Access rules, read October 13, 2026.
- libcurl error codes, read September 16, 2026, for exit 56 and exit 22.
- Measured by HProxy on September 16, 2026, with curl 8.16.0: a stub proxy that refuses, a stub proxy that relays and a stub site that refuses, all on this machine, asked for a plain page and an https page. Nothing outside the machine was contacted.



