Documentation Python quickstart Blog Free tools hello@quanticdata.ioLog in

How to Fix 407 Proxy Authentication Required

A request stops at the proxy with a 407 Proxy Authentication Required and never reaches the target website; adding Proxy-Authorization lets it through

A 407 Proxy Authentication Required is sent by the proxy, not by the website you asked for. It means the proxy refused to forward your request until it sees credentials it accepts, so zero bytes reached the target. Rotating user agents or slowing down cannot fix it. The fix is always one of five things: credentials that were never sent, wrong credentials, an unencoded special character, an IP allowlist mismatch, or a plan the gateway no longer honours.

What a 407 actually says

RFC 9110 defines 407 as the proxy-side twin of 401. The proxy must send a Proxy-Authenticate header naming the scheme it wants, and the client answers with Proxy-Authorization. The two codes look alike but point at different machines, which is why mixing them up sends your debugging in the wrong direction:

401 Unauthorized407 Proxy Authentication Required
Who is refusingThe websiteThe proxy in between
Challenge headerWWW-AuthenticateProxy-Authenticate
What you send backAuthorizationProxy-Authorization
Did the request reach the site?YesNo
Will headers or delays help?NoNo

The scheme in the challenge is the whole diagnosis. Every commercial proxy gateway uses Basic, which is base64 of user:password. If you see NTLM or Negotiate instead, you are behind a corporate proxy, and a client that only speaks Basic will loop on 407 forever no matter how correct the password is. If the 407 carries no Proxy-Authenticate header at all, it is not a real proxy challenge: captive portals, transparent ISP proxies and some load balancers produce 407-shaped responses that no credential will satisfy.

Why on HTTPS you see a tunnel error instead of a status code

Almost every guide tells you to check response.status_code == 407. On an HTTPS target that check never runs. For plain HTTP the proxy receives the full request and answers it, so the 407 comes back as an ordinary response. For HTTPS the client first sends CONNECT host:443 to open a raw tunnel; TLS and the real request happen inside it, invisible to the proxy. A 407 rejects the CONNECT itself, so the tunnel never opens and no HTTP response object is ever built. The same failure shows up under a different name in every client:

  • Python requests: ProxyError: Unable to connect to proxy … Tunnel connection failed: 407 Proxy Authentication Required
  • httpx: httpx.ProxyError: 407 Proxy Authentication Required
  • curl: curl: (56) CONNECT tunnel failed, response 407
  • Node (undici / fetch): TypeError: fetch failed … cause: Proxy response (407)
  • Chrome: ERR_TUNNEL_CONNECTION_FAILED or a proxy sign-in dialog
  • Scrapy: twisted.web._newclient.ResponseNeverReceived or TunnelError: Could not open CONNECT tunnel … 407

So the first thing to do is reproduce against an http:// URL. Hit http://httpbin.org/ip through the proxy and the 407 arrives as a readable response with its challenge header, which tells you the scheme and confirms the proxy is the one talking.

The five causes, in order of likelihood

1. The credentials never left your client

The most common cause is a configuration that looks right and sends nothing. In Python requests the proxies dict is keyed by the target scheme: a dict with only an "http" key sends HTTPS requests straight to the site, and a dict with a bare "https" key pointing at an unauthenticated URL sends the CONNECT without credentials. Environment variables are the other trap: HTTPS_PROXY set in the shell without a user and password overrides nothing you wrote in code, but a NO_PROXY entry that matches your host silently bypasses the proxy. Playwright and Puppeteer have a separate username/password field in the proxy object; putting them in the server URL is ignored by Chromium.

2. Wrong credentials

Proxy credentials are not your dashboard login. Most gateways generate a dedicated proxy user per plan, and the username often carries targeting flags. On QuanticData the username is USER-country-us style: the base user plus the country, and a session token if you want a sticky IP. A typo inside the flags, a country code the plan does not include, or a copy-paste that grabbed a trailing space all produce a 407 that looks identical to a wrong password.

3. A special character that was not URL-encoded

If the password contains @, :, /, # or % and you put it in a URL like http://user:pa@ss@host:7777, the client splits the URL at the first @ and sends garbage. Percent-encode it (@ becomes %40) or, better, pass the credentials through the client's dedicated option instead of the URL. In curl that is -U user:pass, which never touches URL parsing.

4. IP allowlist plans and an egress IP that changed

Some plans authenticate by source IP instead of user and password. On QuanticData that is the case for Residential Basic, Datacenter, ISP, IPv6 and Mobile; Residential Premium uses user:pass. With an allowlist plan, a 407 means the request came from an IP you did not register: a new cloud instance, a home connection whose address rotated overnight, or a container egressing through a NAT you did not know about. Fetch your public IP without the proxy, compare it with the allowlist, and update it. The allowlist is editable via POST /v1/public/proxies/whitelist-ip as documented in the API reference, so you can automate it at boot.

5. The plan itself

Gateways answer 407 for a suspended account, an expired plan, a plan whose bandwidth reached zero, or a concurrency cap on cheaper tiers. If the credentials worked yesterday and nothing on your side changed, check the plan balance before touching code. The same credentials against the wrong port, for example a SOCKS5 port with an HTTP client, produce a connection error rather than a 407, which is a useful way to tell the two apart.

Fix it in curl, Python, Node and Scrapy

Every example uses the residential gateway format from the residential proxies page: pr.quanticdata.io:7777 with a username that carries the country flag.

# curl: -U keeps credentials out of URL parsing; -v shows the CONNECT exchange
curl -v -x pr.quanticdata.io:7777 -U "USER-country-us:PASS" https://ipinfo.io/json
# Python requests: same proxy under BOTH keys, credentials inside the URL
from urllib.parse import quote
proxy = f"http://USER-country-us:{quote('PASS', safe='')}@pr.quanticdata.io:7777"
r = requests.get("https://ipinfo.io/json", proxies={"http": proxy, "https": proxy}, timeout=30)
# httpx: one proxy string for every scheme
with httpx.Client(proxy="http://USER-country-us:PASS@pr.quanticdata.io:7777") as c:
    print(c.get("https://ipinfo.io/json").json())
// Node 18+: undici ProxyAgent takes a token so no URL encoding is needed
import { ProxyAgent } from "undici";
const dispatcher = new ProxyAgent({
  uri: "http://pr.quanticdata.io:7777",
  token: "Basic " + Buffer.from("USER-country-us:PASS").toString("base64"),
});
const r = await fetch("https://ipinfo.io/json", { dispatcher });
# Scrapy: per-request meta, HttpProxyMiddleware adds Proxy-Authorization itself
yield scrapy.Request(url, meta={"proxy": "http://USER-country-us:PASS@pr.quanticdata.io:7777"})

Two rules cover most of the rest. Catch ProxyError (requests, httpx) or exit code 56 (curl) as a 407, not as a network failure, so your retry logic does not treat it as transient and hammer the gateway. And never fall back to the direct connection on a proxy error: that leaks your real IP to the site you were proxying.

A 60-second diagnosis

  1. Reproduce on plain HTTP. curl -v -x host:port http://httpbin.org/ip without credentials. You should see Proxy-Authenticate: Basic realm=…. No header means it is not a real proxy challenge.
  2. Add -U. If it now works in curl but not in your code, the credentials are not leaving your client (cause 1) or are being mangled by the URL (cause 3).
  3. Check the egress IP. curl https://ipinfo.io/ip without the proxy. On an allowlist plan it must match the dashboard.
  4. Check the username flags. Drop everything after the base user and retry. If that works, a targeting flag is wrong or not included in the plan.
  5. Check the plan. Bandwidth left, expiry, concurrency. A gateway that says 407 for “no balance” is common.

What a 407 is not

It is not a block. A ban, a rate limit or an anti-bot challenge comes from the target and arrives as 403, 429, a CAPTCHA page or a 200 with the wrong body. Those are the errors where proxy quality, headers and pacing matter; a guide to the whole family of failures is in proxy not working: a 10-step checklist. A 407 means the request stopped one hop earlier, at a machine you pay for, and it will keep stopping there until the credentials or the allowlist are right. If you would rather not manage gateway credentials at all, the web scraping API runs the proxy layer for you and bills per successful page, so a request that fails at any hop is never charged.

Sources & further reading

FAQ

Quick answers on 407 proxy authentication required.

Something else? Ask us →

How do I fix error 407 Proxy Authentication Required?

Send credentials the proxy accepts. In curl use -x host:port with -U user:pass; in Python requests put http://user:pass@host:port under both the "http" and "https" keys of the proxies dict; in Node pass a Basic token to undici's ProxyAgent. If the plan authenticates by IP instead, add your current public IP to the allowlist. Percent-encode any @ or : in the password if it goes inside a URL.

What does 407 Proxy Authentication Required mean?

The proxy between you and the website refused to forward the request because it did not receive valid credentials. The response carries a Proxy-Authenticate header naming the scheme, almost always Basic. The target website never saw the request, so it is not a ban or a rate limit.

Why do I get a tunnel connection failed 407 instead of a status code?

For HTTPS targets the client first asks the proxy to open a tunnel with CONNECT. A 407 rejects that CONNECT, so no HTTP response object exists. Python requests raises ProxyError with "Tunnel connection failed: 407", curl exits with code 56, Chrome shows ERR_TUNNEL_CONNECTION_FAILED. Reproduce on an http:// URL to see the 407 as a normal response.

Does a 407 mean my proxy has been banned?

No. Bans and blocks come from the target website and arrive as 403, 429, a CAPTCHA page or a challenge body. A 407 comes from the proxy itself and is about credentials, IP allowlists or plan status. Changing headers, user agents or request rate has no effect on it.

What is the difference between 401 and 407?

Both ask for authentication, but 401 comes from the website and is answered with an Authorization header, while 407 comes from an intermediary proxy and is answered with Proxy-Authorization. With a 401 the request reached the site; with a 407 it did not.

Do I need to URL-encode my proxy password?

Only if it goes inside a URL and contains characters like @, :, /, # or %. The client splits the URL at the first @, so an unencoded one breaks parsing. Percent-encode it, or pass credentials through the client's dedicated option such as curl -U or undici's token field, which never touch URL parsing.

Skip the credential dance entirely

Residential proxies from $0.80/GB with user:pass or IP allowlist auth and a country flag in the username, or the web scraping API at $0.0002 per successful page with the proxy layer already inside. Failed requests are never billed, and every account gets $2 of free usage a month.

Related reading