Tutorial29 September 20267 min read

How to fix 407 Proxy Authentication Required

A 407 Proxy Authentication Required error means the proxy refused your login. Find the cause with curl -v, then apply the fix for curl, Python or Node.

A 407 Proxy Authentication Required error means the proxy itself turned you away before your request reached the website. It did not receive a username and password it accepts. The cause is almost always the login string: a mistyped or truncated password, stray whitespace, an unencoded symbol, or a tool that quietly dropped the credentials.

The website never saw the request, so nothing about the target, your headers or your IP reputation matters yet. This post shows how to confirm that with one curl command, then walks through the causes in the order they turn up, with the fix for each tool.

Every snippet uses HOST, PORT, USERNAME and PASSWORD as placeholders. Your own values are on each service's page in the dashboard.

What does 407 Proxy Authentication Required mean?

HTTP has two login statuses that look alike. A 401 comes from the website: the page wants you to sign in. A 407 comes from the proxy in between: the proxy wants credentials before it will forward anything.

The exchange works like this. Your client sends a request to the proxy with a Proxy-Authorization header carrying USERNAME:PASSWORD in Base64. If the header is missing or the values do not match, the proxy answers 407 with a Proxy-Authenticate header saying which scheme it expects, usually Basic. For an https site the request in question is a CONNECT (see CONNECT tunnel), so the 407 arrives before any encryption to the site starts.

That is why the glossary entry for 407 says the fix is almost always in the login string. The proxy has only two things to check, and you control both.

Reproduce the proxy 407 error with curl -v

Before touching your code, prove where the failure is. curl prints the whole exchange with the proxy when you add -v:

curl -v -x "http://USERNAME:PASSWORD@HOST:PORT" https://api.ipify.org

With a wrong password, the interesting lines look like this:

* Proxy auth using Basic with user 'USERNAME'
* Establish HTTP proxy tunnel to api.ipify.org:443
> CONNECT api.ipify.org:443 HTTP/1.1
> Host: api.ipify.org:443
> Proxy-Authorization: Basic VVNFUk5BTUU6d3Jvbmc=
>
< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Basic realm="proxy"
* CONNECT tunnel failed, response 407
curl: (56) CONNECT tunnel failed, response 407

How to read it:

  • Lines starting with > are what curl sent to the proxy. If there is no Proxy-Authorization line at all, curl never had credentials, and the problem is how you passed them.
  • Lines starting with < are the proxy's reply. A 407 here, with no line from the website, confirms the refusal happened at the proxy.
  • Exit code 56 with "CONNECT tunnel failed, response 407" is curl's way of saying the tunnel was refused. On a plain http:// URL you get the 407 as an ordinary response instead, with exit code 0.

One warning before you paste this anywhere. The Proxy-Authorization value is your username and password in Base64, which anyone can decode in a second. Delete that line, and the -x URL, before sharing the output.

If curl succeeds and your program still gets a 407, the credentials are fine and your program is sending them differently. Skip to the per-tool fixes below.

The causes, most likely first

1. The password is wrong or cut short

Double-clicking a password to select it often stops at a symbol, so you copy half of it. Passwords pasted into chat apps get reformatted. Old credentials linger in a config file after you regenerated them. Copy the password again from the service page with the copy button, and paste it somewhere you can see all of it.

2. Invisible whitespace

A trailing space, a newline read from a file, or a \r from a .env file saved on Windows all become part of the password. The proxy compares bytes, so PASSWORD and PASSWORD are different logins. In Python, .strip() anything you read from a file or environment variable:

import os

PASSWORD = os.environ["PROXY_PASSWORD"].strip()

3. A symbol that needs percent-encoding

Inside a URL, some characters have jobs. @ separates the login from the host, : separates the username from the password and the host from the port, and /, #, ? and % have meanings of their own. A password containing any of them breaks the URL before the proxy sees it. Sometimes the parser fails loudly, as curl does with a / in the password:

curl: (5) Unsupported proxy syntax in 'http://USERNAME:pa/ss@HOST:PORT': Port number was not a decimal number between 0 and 65535

Sometimes it quietly reads a different password. A % followed by two hex digits is the sneaky one: curl, requests and undici all decode ab%41cd to abAcd, send that, and get a 407 with no hint about why. The fix is to percent-encode the username and password before building the URL. In Python:

from urllib.parse import quote

import requests

user = quote("USERNAME", safe="")
password = quote("PASSWORD", safe="")
PROXY = f"http://{user}:{password}@HOST:PORT"

r = requests.get("https://api.ipify.org", proxies={"http": PROXY, "https": PROXY}, timeout=30)
print(r.text)

safe="" matters: by default quote leaves / alone, which is one of the characters you need encoded. In JavaScript the equivalent is encodeURIComponent, shown in the Node section below.

4. Credentials sent to the wrong service or port

There is no single gateway address shared by every product. Each service in the dashboard has its own host, port, username and password, and each ISP or datacenter IP is listed with its own port and login. A residential login sent to a datacenter port, or credentials from one IP pasted next to another IP's port, gets a 407 even though every character is right. Copy the four values from the same row. While you are on that page, confirm the service still shows as active.

5. A sticky credential copied partially

When a residential service is set to Sticky IP, the dashboard builds a credential with a session ID inside it. It is longer than the plain one, and copying part of it is easy. Copy the whole string as the dashboard shows it. Rotating vs sticky proxies explains when you want that mode at all.

6. The tool dropped the credentials

This is the cause behind "it works in curl but not in my code". Some common ones:

  • auth= in requests goes to the website, not the proxy. requests.get(url, proxies={"https": "http://HOST:PORT"}, auth=(...)) still gets a 407. The proxy login belongs in the proxy URL.
  • Environment variables with two spellings. curl only reads lowercase http_proxy for http:// URLs and ignores HTTP_PROXY. For https:// URLs it reads both https_proxy and HTTPS_PROXY, and when both are set, the lowercase one wins. Python's requests behaves the same way. An old https_proxy in your shell profile silently overrides the HTTPS_PROXY you just exported. Run env | grep -i proxy to see what is set.
  • Browsers ignore user:pass in proxy settings. Browser proxy settings take a host and port; the browser asks for the login in a pop-up. Chrome's --proxy-server flag does not accept a username or password at all, so a headless Chrome started that way cannot answer the prompt and fails. The browser setup guide covers the desktop browsers, and the Selenium guide shows how to supply the login from code.

Fixes per tool

curl

Pass the login with -U instead of inside the URL. curl then sends it as it is, so symbols need no encoding:

curl -x "http://HOST:PORT" -U "USERNAME:PASSWORD" https://api.ipify.org

If this works and the URL form does not, the problem was encoding. The curl guide lists the other exit codes.

Python requests

Build the proxy URL with quote(..., safe="") as in cause 3, strip anything read from a file, and put the login in the proxy URL. With an https target, a 407 shows up as an exception, not a response:

ProxyError: ... (Caused by ProxyError('Unable to connect to proxy', OSError('Tunnel connection failed: 407 Proxy Authentication Required')))

The same login on an http:// target returns a normal Response with status_code == 407. Same cause, different shape. The requests guide has the full setup.

Node.js with undici or axios

With undici's ProxyAgent, encode both parts and put them in the URL:

import { fetch, ProxyAgent } from 'undici';

const user = encodeURIComponent('USERNAME');
const password = encodeURIComponent('PASSWORD');
const dispatcher = new ProxyAgent(`http://${user}:${password}@HOST:PORT`);

try {
  const res = await fetch('https://api.ipify.org', { dispatcher });
  console.log(await res.text());
} catch (err) {
  console.error(err.cause?.cause ?? err.cause ?? err);
}

undici reports a 407 as TypeError: fetch failed, which says nothing on its own. The real message, Proxy response (407) !== 200 when HTTP Tunneling, sits two levels down in err.cause.cause, which is why the example logs it.

axios takes the login as an object, so no encoding is needed:

import axios from 'axios';

const res = await axios.get('https://api.ipify.org', {
  proxy: {
    protocol: 'http',
    host: 'HOST',
    port: Number('PORT'),
    auth: { username: 'USERNAME', password: 'PASSWORD' },
  },
});
console.log(res.data);

A wrong login there throws AxiosError: Request failed with status code 407. The Node.js guide covers the https-proxy-agent route as well.

Quick answers

Is a 407 the website blocking me? No. A 407 comes from the proxy, before the website is contacted. A block from the website looks like a 403, a 429 or a captcha page, and why your scraper started getting blocked covers those.

Why does curl work when my script gets a 407? The credentials are right and the script is sending them differently: unencoded symbols, whitespace from a file, an environment variable overriding your setting, or auth= aimed at the website.

Why do I get an exception on https URLs and a 407 response on http ones? For https, the client must open a CONNECT tunnel first, and a refused tunnel is an error. For plain http, the proxy's 407 comes back as an ordinary response.

Will changing my password fix it? Only if the old one was wrong or leaked. If the new password has symbols in it, encode it or use -U, or you will be back here.

Still stuck?

Run the curl -v command above, delete the Proxy-Authorization line and the -x URL, and paste the rest in Discord together with the tool you are using. If your problem turns out to be a hang and not a refusal, proxy timeout errors is the next place to look.

Got a follow-up question?

Ask it in Discord. The answer helps whoever reads the thread next.

Join the Discorddiscord.gg/proxypanda
Start with $5Ask in Discord