Skip to content
Get Started

How to Fix 407 Proxy Authentication Required (curl, Python, Node, Browsers)

Team CleanProxies
13 Min Read

A 407 Proxy Authentication Required error means the proxy server refused your request because it did not get valid credentials. The website you wanted never saw the request, so changing headers, user agents or request speed will not help. Fix the login: check the username and password, encode special characters, confirm the auth mode, and make sure your tool sends proxy credentials at all. If you need a line that just works, ISP proxies from CleanProxies start as low as $0.75/IP and come with username:password or IP whitelist login out of the box.

This guide is for a 407 that keeps coming back: it helps you diagnose it client by client. For a one-line definition see the 407 glossary entry, and for other status codes see our proxy errors guide. We wanted real error messages rather than a list of guesses, so on 8 November 2026 we built a small password-protected proxy on localhost and ran more than 30 test requests through it with curl, Python requests, axios, undici, Playwright and Chromium. Every error string below was copied from those runs.

TL;DR

  • 407 comes from a proxy, not the website. That one fact tells you where to look.

  • Most 407s are credentials. Wrong password, missing password, or a stray character picked up on the way into your code.

  • Special characters are the sneaky one. In a proxy URL, @ : / # and % must be percent-encoded. A literal %41 in a password failed with a 407 in curl 7.68.0 because it was decoded to A.

  • Headless browsers can hide the 407. In our Playwright and Chromium headless runs, a rejected login surfaced only as a navigation timeout, never as a 407.

  • A SOCKS5 and HTTP mix-up is usually not a 407. It produced protocol errors in our tests, so if you see a real 407, stop looking at the protocol.

  • Fastest proof: run the line through curl or our free proxy checker. If it works there, the problem is in your tool's settings.

What a 407 looks like in each tool

The same refused login looks different in every client. This table is what we got back. Search this page for your exact message.

Tool (version tested)What you see on a refused loginWhere the 407 appears
curl 7.68.0, HTTPS targetcurl: (56) Received HTTP code 407 from proxy after CONNECTExit code 56, %{http_connect} = 407
curl 7.68.0, HTTP targetNo error text, exit code 0%{http_code} = 407, the proxy's page becomes your "response"
Python requests 2.22.0, HTTPS targetProxyError ... Tunnel connection failed: 407 Proxy Authentication RequiredRaised as an exception
Python requests 2.22.0, HTTP targetNo exceptionresponse.status_code == 407
axios 1.21.0ERR_BAD_REQUEST Request failed with status code 407error.response.status
undici 8.12.4 fetch + ProxyAgentfetch failedOnly in error.cause.cause: Proxy response (407) !== 200 when HTTP Tunneling
Playwright 1.48.2 (Chromium headless shell)page.goto: Timeout 25000ms exceeded. (25 s was the timeout we set)Not shown anywhere in the error

Two rows catch people out. With plain HTTP targets, curl and requests treat a 407 as a normal response, so a script that never checks the status code will happily store the proxy's error page as data. And undici's top-level message says nothing about the proxy, so log error.cause when you debug it.

What 407 means in the HTTP spec

RFC 9110, section 15.5.8 defines 407 as similar to 401, except that "the client needs to authenticate itself in order to use a proxy." The proxy must send a Proxy-Authenticate header naming the scheme it accepts, and the client can retry with a Proxy-Authorization header.

For HTTPS targets this happens during the CONNECT step, before any encryption with the website starts. That is why the target site's status never appears. Here is the exchange from our test, with the credential value removed:

> CONNECT 127.0.0.1:18443 HTTP/1.1
> Proxy-Authorization: Basic [removed]
> Proxy-Connection: Keep-Alive
< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Basic [removed]
* Received HTTP code 407 from proxy after CONNECT

Step zero: test the line on its own

Before you touch your code, take the tool out of the picture. Run this with your real details (keep the quotes):

curl -v --noproxy '' -x "http://USERNAME:PASSWORD@HOST:PORT" https://api.ipify.org
  • It prints an IP and the verbose log shows CONNECT succeeding: the proxy and login are fine. The bug is in how your tool passes credentials. Skip to the tool fixes.

  • Confirm that IP is your proxy's, not your own. --noproxy '' matters: with NO_PROXY=* set in our test, curl ignored -x, went direct and returned 200 with no credentials at all. Adding --noproxy '' brought the 407 back.

  • You see 407: the credentials or auth mode are wrong. Work through the causes below.

  • You see Could not resolve proxy or it hangs at Trying HOST:PORT: this is a host, port or network problem, covered in our proxy errors guide.

If you would rather not use a terminal, paste the line into the free proxy checker. It takes up to ten lines at once and reports working or failed per line, with the reason. It runs from our server, so it proves the username and password, not whether your own machine's IP is allowlisted. Test allowlists from the machine that fails. More curl recipes are on the cURL integration page.

Paste a proxy line, percent-encoded if needed, and check it without writing code. Screenshot: cleanproxies.com/tools/proxy-checker, 8 Nov 2026.
A failed line shows its reason under the status. This example used a reserved documentation address, so the reason is Private or reserved address. Screenshot: cleanproxies.com/tools/proxy-checker, 8 Nov 2026.

The causes, in the order to check them

1. Wrong or missing username or password

Our proxy logged what each client really sent. A missing login arrived with no Proxy-Authorization header at all, and a wrong one arrived with the wrong value. Both returned the same 407, so the error alone cannot tell you which happened.

Fix: copy the credentials again from your dashboard rather than retyping them. Confirm you are using the proxy's username and password, not your website account login. If the plan was just renewed or changed, fetch the current line.

2. Special characters in the password

Proxy URLs have a fixed shape, scheme://user:pass@host:port, so characters such as @, :, / and # inside a password break it. We tested the password p@ss:w/rd#1:

MethodResult
curl, raw password in the URLcurl: (5) Unsupported proxy syntax
requests, raw password in the URLInvalidURL Failed to parse
curl, percent-encoded in the URL200 OK
requests, percent-encoded in the URL200 OK
undici, percent-encoded in the URL200 OK
curl --proxy-user with the raw password200 OK
axios auth object with the raw password200 OK

So encoding fixes URLs, and separate user and password fields often avoid the issue entirely.

The nastier case is a password that already contains a percent sign. With a password of a%41b, curl 7.68.0 decoded %41 to A and sent aAb. That happened both in the URL and with --proxy-user, and the proxy answered 407 with a perfectly correct password. Writing the % as %25 (a%2541b) gave a 200 in both. This is what we saw on curl 7.68.0, so check your own version.

Fix: encode the username and password separately before building the URL.

from urllib.parse import quote

user = quote("labuser", safe="")
password = quote("p@ss:w/rd#1", safe="")   # p%40ss%3Aw%2Frd%231
proxy = f"http://{user}:{password}@HOST:PORT"
const proxy = `http://${encodeURIComponent(user)}:${encodeURIComponent(password)}@HOST:PORT`;

3. A hidden character in the credential

With a plain password, curl returned 200. The same password with a trailing carriage return, a character that can slip in from Windows line endings or a careless copy, went out as plainpass\r and the proxy answered 407. Nothing in the error hints at it.

Fix: print the value with its hidden characters visible (repr(password) in Python, JSON.stringify(password) in JavaScript) and remove only what clearly should not be there. Avoid a blanket trim, since a real password can contain spaces at the ends.

4. The client uses the wrong auth scheme

When we forced NTLM against a proxy that only accepts Basic, the proxy logged an NTLM token and answered 407. Read the Proxy-Authenticate line in curl -v output. If it says Basic, make sure nothing in your tool is set to NTLM, Digest or Negotiate.

5. Your IP is not on the allowlist

If a proxy is set to IP authentication, it checks where the connection comes from. Move the same script to a VPS, a new office or a home connection with a changing IP, and it stops working even though nothing in your code changed. The exact error here depends on the provider, and we did not simulate it in the lab.

Fix: find your current public IP with what is my IP, add it to the allowlist, or switch that line to username and password. On CleanProxies, ISP and ISP subnet proxies support username:password or an allowlist of 1 to 20 IPv4 addresses. Rotating residential and mobile proxies use username:password only, so there is no allowlist to get wrong.

6. Your tool drops the credentials

This one matters most in browsers. Chromium's --proxy-server flag takes an address, not a login. Launched with credentials inside that flag, our Chromium failed in 6 ms with net::ERR_NO_SUPPORTED_PROXIES. Playwright with credentials inside the server string sent no Proxy-Authorization header at all and timed out. The working forms are shown below.

7. It isn't a 407 at all: SOCKS5 and HTTP mixed up

People often list this as a 407 cause. In our tests it produced different errors:

MistakeResult in curl 7.68.0
SOCKS5 client pointed at an HTTP proxy portcurl: (7) Received invalid version in initial SOCKS5 response.
HTTP client pointed at a SOCKS5-only portcurl: (56) Proxy CONNECT aborted

If you see those, match the scheme to the port and auth mode. CleanProxies ISP proxies have a SOCKS5 switch in the dashboard, and our SOCKS5 setup guide walks through it. Other proxies may answer a wrong protocol differently, so treat this as a strong hint rather than a law.

Working fixes per tool

The curl, requests, axios, undici and Playwright login methods below each returned a 200 through our test proxy. The snippets are trimmed to the essentials.

curl

# Password with special characters: use --proxy-user and single quotes
curl -x "http://HOST:PORT" --proxy-user 'USERNAME:PASSWORD' https://api.ipify.org

On curl 7.68.0, a literal % in the password had to be written as %25, as shown above. Full recipes: cURL proxy setup.

Python requests

import requests
from urllib.parse import quote

proxy = f"http://{quote(USER, safe='')}:{quote(PASS, safe='')}@HOST:PORT"
r = requests.get("https://api.ipify.org",
                 proxies={"http": proxy, "https": proxy}, timeout=15)
r.raise_for_status()   # turns a 407 on plain HTTP into an exception
print(r.text)

More patterns: Python requests integration.

axios

const res = await axios.get(url, {
  proxy: { protocol: "http", host: HOST, port: PORT,
           auth: { username: USER, password: PASS } },
});

Pass the raw password here. The auth object is not a URL, so it needs no encoding. Our test covered both an HTTP target and an HTTPS target through a tunnel.

undici / Node fetch

import { fetch, ProxyAgent } from "undici";

const token = "Basic " + Buffer.from(`${USER}:${PASS}`).toString("base64");
const res = await fetch(url, {
  dispatcher: new ProxyAgent({ uri: `http://${HOST}:${PORT}`, token }),
});

Playwright

const browser = await chromium.launch({
  proxy: { server: "http://HOST:PORT", username: USER, password: PASS },
});

Keep the login in the username and password fields, never in server. See the Playwright integration.

Puppeteer and Selenium

We did not run these two in the lab, so treat this as guidance from their documentation. Puppeteer takes the address in --proxy-server and the login through page.authenticate(), called before navigation on each page. See the Puppeteer integration.

Selenium with Chrome has no built-in field for a proxy password, which is the same limit as the flag test above. The simplest route is an IP allowlist on an ISP proxy, so no password is needed at all. See the Selenium integration.

When a 407 comes from a proxy you didn't mean to use

Sometimes the credentials are fine and the wrong proxy is answering. Check these if the 407 only happens on one machine or network:

  • Environment variables: HTTP_PROXY, HTTPS_PROXY and NO_PROXY can send a tool somewhere other than you expect. Run env | grep -i proxy on Linux or macOS.

  • System and PAC settings: browsers often follow the operating system's proxy or a PAC file, and a PAC file can route different sites to different proxies.

  • Corporate proxies: these commonly ask for your company login through NTLM or Negotiate. Those schemes take several rounds, so one 407 during the exchange is expected. Microsoft's guidance says to treat a 407 as a failure only when the exchange stops making progress or repeats the same challenge. Your IT team has to resolve those.

407 vs 401, 403 and 502

A 407 always points at a proxy. Other codes can come from either side, so on HTTPS check whether the code appeared on the CONNECT line (from the proxy) or after the tunnel opened (from the website).

CodeWho is refusingWhat it meansFirst move
407Your proxyNo valid proxy loginCheck credentials and auth mode
401Usually the websiteNo valid website loginFix the site's auth, not the proxy
403Website, sometimes the proxyRequest understood and refusedSee our proxy errors guide
429Website, sometimes the proxyToo many requestsSlow down, honor Retry-After
502 / 503Proxy or upstreamGateway could not complete the requestRetry, then check the target and the plan

How we tested

So you can judge these results yourself: the proxy was a small Node.js server on 127.0.0.1 that accepted Basic auth for one user, forwarded HTTP and handled CONNECT tunnels to local HTTP and HTTPS test servers. A second port accepted only SOCKS5 handshakes, and a log recorded the decoded credentials each client sent.

Client versions are in the table at the top; the Puppeteer and Selenium notes come from documentation only. Results can differ on other versions, and other proxies can word their responses differently, so use the messages as a guide to which layer failed. We did not test any third-party proxy service, and the allowlist case in cause 5 was not simulated.

Why CleanProxies lines are easy to get right

Most of the failures above come from fiddly login setups. CleanProxies is built so you can get a working line quickly and spend your time on the actual project:

  • Choice of login on ISP proxies: username:password, IP allowlist (1 to 20 IPv4 addresses), or SOCKS5 through a dashboard switch.

  • Instant delivery: proxies go live seconds after payment, with ready-to-copy lines in the dashboard.

  • Static, dedicated IPs on real carriers: 100% dedicated ISP IPs on Tier-1 carrier ASNs, the cleanest ISP IPs, listed at 0 fraud score, with 99.9% uptime.

  • Unlimited bandwidth and threads on ISP proxies, so a retry loop during debugging costs nothing extra.

  • 24/7 support if a line still refuses you.

Pricing is the cheapest in the market: ISP proxies as low as $0.75/IP and ISP subnets from $1.19/IP (monthly, at top volume tiers), rotating residential as low as $0.63/GB and mobile as low as $0.79/GB, with residential and mobile data that never expires. See every tier on the pricing page.

Checklist before you buy or blame the proxy

  1. Run the bare curl command from step zero.

  2. Read Proxy-Authenticate in the -v output, and match the scheme.

  3. Re-copy the username and password from the dashboard.

  4. Percent-encode both, including any literal %.

  5. Strip whitespace from values loaded out of .env files.

  6. If you use an allowlist, confirm your current public IP is on it.

  7. In browsers, use the username and password fields, or an allowlist.

  8. Check the same line in the proxy checker.

  9. Try a known-good line. The $4.99 trial gives you 5 ISP IPs for 1 hour, or 100 MB of residential or mobile data, which is enough to prove your code works.

Get a line that authenticates first time

Most 407s take five minutes to fix once you know which layer refused you. If you also want a provider where login is simple and delivery is immediate, start with CleanProxies ISP proxies, compare every plan on the pricing page, or read the 407 glossary entry for the short version.

Frequently Asked Questions

The proxy server refused your request because it did not receive valid credentials. The website you wanted never saw the request, so the fix is in your proxy login or configuration, not your headers.

Test the line alone with curl -v --noproxy '' -x http://USERNAME:PASSWORD@HOST:PORT https://api.ipify.org. Re-copy the credentials, percent-encode special characters in the URL, or pass them with --proxy-user.

Common reasons are special characters that were not percent-encoded, a hidden trailing character such as a carriage return, a client using the wrong auth scheme, or a request going to a different proxy than you expected. In curl 7.68.0 a literal %41 in a password was decoded to A, which caused a 407 until the % was written as %25.

A 407 comes from a proxy and asks for proxy credentials in the Proxy-Authorization header. A 401 asks for credentials for the destination, usually the website you are visiting.

It can, depending on the provider. If a proxy uses IP authentication and your public IP changes, the login stops working. On CleanProxies, ISP and ISP subnet proxies support username:password or an allowlist of 1 to 20 IPv4 addresses; rotating residential and mobile proxies use username:password only.

In our tests with Playwright 1.48.2 and Chromium headless shell, a refused proxy login surfaced as a navigation timeout rather than a 407. Set the proxy login in the username and password fields of the proxy option, not inside the server string.

Team CleanProxies
Proxy Infrastructure Team
Tags
  • 407
  • proxy errors
  • troubleshooting
  • proxy authentication
  • isp proxies

Related Articles