Proxy error · 403

403 Forbidden

A 403 is the website saying it understood the request and will not serve it. Through a proxy, that usually means the site did not like the address, the headers, or how fast the requests arrived.
Whose fault is it?

Almost always the target. The useful question is what it objected to: the address, the request, or the rate.

Usually from
The target site
Try first
Load the same URL without the proxy and compare
With a proxy in the path

What a 403 means through a proxy

Over HTTPS the proxy only speaks during CONNECT. Once the tunnel is open, TLS runs end to end between your client and the site, and the proxy can neither read nor write what passes through. A 403 you read from a response over HTTPS was written by the site or the CDN in front of it.

Sites block for different reasons, and the response body usually says which. A bot-protection block page, a JSON error from an API, a "not available in your region" notice and a bare nginx 403 are four different problems with four different fixes.

Datacenter addresses are registered to hosting companies, and anyone can look that up. Some sites refuse every request from those ranges, however polite the request. That is the one 403 a change of proxy line fixes.

The key question

Your credentials, the proxy, or the target?

Almost always the target. The useful question is what it objected to: the address, the request, or the rate.

  • Your credentials

    Not the cause. Bad proxy credentials give a 407, and the request never reaches the site.

  • The proxy

    Only through the address it gave you: a datacenter range the site refuses, or a residential exit with a history on that site.

  • The target site

    The source. Its response body and headers say what it objected to.

How to tell

  • Headers such as Server, cf-ray or x-amz-cf-id name the server or CDN that answered. They all belong to the site side.
  • Send the same request, with the same headers, with and without the proxy. A 403 only through the proxy points at the address; a 403 both ways points at the request.
  • On rotating residential, send the same request five times. A mix of 200s and 403s means some exits are refused. Five 403s means the request itself is the problem.
  • Read the body. A block page usually says what kind of block it is.
Cheapest first

Fixes, in the order to try them

  1. Read the body and the headers

    Save one 403 and look at it. The body often names the reason and the headers name who sent it. The samples below print both.

    Costs nothing
  2. Send the headers a normal client sends

    Default agents like python-requests/2.x or curl/8.x are refused on sight by plenty of sites. Send a browser User-Agent with a matching Accept and Accept-Language, and keep them consistent across requests.

    Costs nothing
  3. Check robots.txt and the login wall

    If the path is disallowed in robots.txt, or the page needs an account you do not have, the 403 means what it says. Get the data from a path or an API where you are allowed.

    Costs nothing
  4. Slow down

    Some sites answer too much traffic with 403 where others use 429. Add a delay between requests, cap concurrency per domain, and see whether the 403s stop.

    Costs some time
  5. Keep cookies and the address together

    A session cookie issued to one IP and replayed from another looks like a stolen session to some sites. Keep a logged-in flow on one static IP, or on a residential sticky session; the session setting for your account is in the dashboard.

    Costs some time
  6. Move off datacenter if the site blocks hosting ranges

    If a datacenter IP gets 403 and the same request from a home connection does not, the site is refusing hosting ranges. Residential and ISP addresses are registered to consumer ISPs. Residential rotates; ISP holds one address and lets you pick its country.

    Costs money
Per tool

See the real status and headers

Each sample prints the status, the headers that say who answered, and enough of the body to read the reason.

curl
terminal
URL=https://example.com/page

# through the proxy: the status, then who answered
curl -s -o block.html -D - -x http://USER:[email protected]:8000 "$URL" \
  | grep -iE '^(HTTP|server|cf-ray|x-amz-cf-id|content-type)'

# the same request without the proxy
curl -s -o /dev/null -w 'direct: %{http_code}\n' "$URL"

The first HTTP line is the proxy opening the tunnel. The second is the site.

Python Requests
compare.py
import requests

URL = "https://example.com/page"
PROXY = "http://USER:[email protected]:8000"
HEADERS = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "Accept": "text/html,application/xhtml+xml,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.9",
}

for label, proxies in [("proxy", {"http": PROXY, "https": PROXY}), ("direct", None)]:
    r = requests.get(URL, headers=HEADERS, proxies=proxies, timeout=30)
    print(label, r.status_code, r.headers.get("server"), r.text[:200])
Scrapy
settings.py + spiders/page.py
# settings.py
ROBOTSTXT_OBEY = True
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"


# spiders/page.py
import scrapy


class PageSpider(scrapy.Spider):
    name = "page"
    handle_httpstatus_list = [403]
    start_urls = ["https://example.com/page"]

    def parse(self, response):
        if response.status == 403:
            self.logger.warning("403 at %s, server=%s", response.url, response.headers.get("Server"))
            with open("block.html", "wb") as f:
                f.write(response.body)

Without handle_httpstatus_list, Scrapy drops the 403 before parse sees it.

Playwright
look.py
from playwright.sync_api import Error, sync_playwright

PROXY = {"server": "http://resi.proxymonkey.io:8000", "username": "USER", "password": "PASS"}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=PROXY)
    page = browser.new_page()
    response = page.goto("https://example.com/page")
    print(response.status, response.headers.get("server"), page.title())
    if response.status == 403:
        page.screenshot(path="block.png", full_page=True)
    browser.close()
Node.js
look.mjs
import { writeFile } from "node:fs/promises";
import { fetch, ProxyAgent } from "undici";

const dispatcher = new ProxyAgent("http://USER:[email protected]:8000");

const res = await fetch("https://example.com/page", {
  dispatcher,
  headers: {
    "user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
    "accept-language": "en-US,en;q=0.9",
  },
});

console.log(res.status, res.headers.get("server"), res.headers.get("cf-ray"));
if (res.status === 403) await writeFile("block.html", await res.text());

The samples use the residential gateway, resi.proxymonkey.io:8000. For an ISP or datacenter IP, use USER:PASS@IP:PORT for the address you rented. Your dashboard lists the host and port for every order, and where it differs from this page, the dashboard is right.

Before you buy anything

Will a different proxy line fix it?

When switching helps

Datacenter IPs on a site that refuses hosting ranges. Our datacenter page says it plainly: you will get 403s there, go residential. ISP is the other way out when you also need one steady address or a named country.

When it will not

A 403 caused by your headers, a missing login, a disallowed path or a region lock follows you to every line. Residential cannot be pinned to a country, so it will not get you past a region lock. Fix the request first, since that costs nothing.

The meter

Is a failed request billed?

We bill for request bytes and response bytes, including headers and protocol overhead on the tunnelled connection. Connections that fail before transferring data are not billed. Retries that you initiate are billed and appear as separate entries in your usage log.

From the metering section of our terms of service.

The terms bill request bytes and response bytes and make no exception for error statuses. A 403 page that came back through the proxy moved bytes both ways, so it shows in your usage log like any other response. Block pages can be heavy, so test against one small URL before running the whole list.

Residential is billed per GB of that traffic. ISP and datacenter addresses are charged per IP for their term, and where a plan includes a traffic allowance, traffic past it is billed per GB under the same rule. The usage log in your dashboard has one row per request with bytes in, bytes out and cost, so you can look up the failed request yourself.

403, asked often

Questions people ask about a 403

Is a 403 through a proxy the proxy’s fault?

Rarely. Over HTTPS the proxy cannot write into the response, so the 403 came from the site or its CDN. The proxy is involved only through the address it gave you, which is why comparing with and without the proxy tells you so much.

Why do I get 403 on datacenter IPs but 200 from home?

The site is refusing hosting ranges. Anyone can look up which network an address belongs to, and datacenter addresses belong to hosting companies. Residential and ISP addresses are registered to consumer internet providers, which is what those sites let through.

Can residential proxies get a 403 too?

Yes. Residential addresses are used by other people too, and some have a history with some sites. On the rotating gateway the next request leaves from a different address, so a few 403s among 200s is normal and a retry deals with it. A 403 on every request is about the request.

Should I solve the captcha on the block page?

We do not help with that. A challenge page is the site saying it does not want automated traffic on that path. Slow down, send honest headers, check robots.txt, and if the site still refuses, look for an official API or another source for the data.

The community layer

Still stuck on a 403?

Paste your error in the Discord: the full message plus the command or the few lines that set up the proxy, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk