Fix Proxy 407 Authentication Errors: Causes & Solutions

By Nicholas St. Germain —

A 407 is the proxy telling you "I don't know who you are." The request never reached the site you were trying to hit. The proxy checked for credentials, didn't find valid ones, and closed the door. Fix the credentials and the error disappears. There's no deeper mystery here, just a header that's missing or malformed.

This guide covers the exact causes, the correct curl and Python syntax, and the format mistakes that keep a 407 alive even when your password is right.

What a 407 Error Means

HTTP 407 Proxy Authentication Required means the proxy needs credentials and either got none or got the wrong ones. It's the proxy's equivalent of a 401. The difference: a 401 comes from the destination server, a 407 comes from the proxy sitting in front of it. Your request stopped one hop early.

Here's what it looks like in a verbose curl trace:

curl -v -x http://proxy.example.com:8080 https://httpbin.org/ip
*   Trying 203.0.113.10:8080...
* Connected to proxy.example.com (203.0.113.10) port 8080
* CONNECT tunnel: HTTP/1.1 negotiated
> CONNECT httpbin.org:443 HTTP/1.1
> Host: httpbin.org:443
> User-Agent: curl/8.4.0
>
< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Basic realm="proxy"
< Content-Length: 0
<
* CONNECT tunnel failed, response 407
* Closed connection
curl: (56) CONNECT tunnel failed, response 407

The line that matters is Proxy-Authenticate: Basic realm="proxy". The proxy is telling you exactly which scheme it wants: Basic auth. Stat Proxies uses Basic auth with a username and password, no API tokens and no IP whitelisting. If your client sends nothing, or sends the wrong scheme, you get the trace above.

Why Your Proxy Returns 407

Six things cause almost every 407. Work down the list:

  • No credentials sent. You ran curl -x http://proxy:8080 with no -U flag and no user:pass@ in the URL. The proxy needs auth; you gave it none.
  • Credentials sent to the target, not the proxy. -u (lowercase) authenticates to the destination site. -U (uppercase) authenticates to the proxy. Swap them and the proxy still sees nothing.
  • Wrong username or password. A typo, a trailing space, or a copy-paste that grabbed a newline. The proxy can't tell "almost right" from "wrong."
  • Stale credentials. You rotated your proxy password in the dashboard but your script, environment variable, or browser still holds the old one.
  • Wrong auth scheme. The proxy wants Basic; your client is configured for Digest or NTLM. The handshake never completes.
  • Only one proxy in a chain got the credentials. If you're routing through a corporate proxy to reach an upstream proxy, both may demand auth and only one is getting it.

Check Your Credentials First

Before touching any code, confirm the exact strings. Open your Stat Proxies dashboard, find the order, and copy the username and password directly. Don't retype them. Passwords contain characters that are easy to misread (l vs 1, O vs 0).

Then check for the silent killer: whitespace. This is the single most common "but my password is correct" 407. A password pasted from an email or a chat client often carries a trailing space or newline. Run it through printf to see the raw bytes:

printf '%s' "$PROXY_PASS" | xxd | tail -1

If the last byte is 0a (newline) or 20 (space) and it shouldn't be there, that's your bug. Re-copy the password.

Verify the Auth Format

Basic auth is base64-encoded username:password. You almost never encode this by hand. curl, requests, and browsers do it for you when you give them the raw username and password. Encoding it yourself is how people double-encode and stay broken.

The one time you build the header manually is when you're setting Proxy-Authorization directly. It's Basic followed by base64 of username:password:

# Only if you must set the header by hand:
echo -n "username:password" | base64
# -> dXNlcm5hbWU6cGFzc3dvcmQ=
# Then: Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

The -n on echo is not optional. Without it you encode a trailing newline into the credential string and the proxy rejects it. Notice how that mirrors the whitespace problem above: a single stray byte breaks the whole thing.

Test With curl

The fastest way to confirm a fix is a one-line curl. There are two correct ways to pass proxy credentials.

Credentials in the proxy URL:

curl -x http://username:password@proxy.example.com:8080 https://httpbin.org/ip

Credentials with the -U flag (cleaner, keeps them out of the URL):

curl -x http://proxy.example.com:8080 -U username:password https://httpbin.org/ip

Both send a proper Proxy-Authorization: Basic ... header. A working response prints the proxy's exit IP:

{
  "origin": "212.116.248.54"
}

Here's the mistake that looks right but isn't:

# WRONG: -u authenticates to httpbin, NOT the proxy. Still returns 407.
curl -x http://proxy.example.com:8080 -u username:password https://httpbin.org/ip

Lowercase -u sends an Authorization header to the destination. The proxy in front never sees credentials, so it 407s before the request tunnels through. For the full set of copy-paste variants, see our guide on how to test a proxy in curl.

Fix It in Your App

Python (requests): put the credentials in the proxy URL. requests base64-encodes them for you.

import requests

proxies = {
    "http":  "http://username:password@proxy.example.com:8080",
    "https": "http://username:password@proxy.example.com:8080",
}

r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30)
print(r.status_code, r.json())
# 200 {'origin': '212.116.248.54'}

If your password contains @, :, /, or other reserved characters, URL-encode it or the parser splits the URL in the wrong place:

from urllib.parse import quote

user = "username"
pw = quote("p@ss:word", safe="")  # -> p%40ss%3Aword
proxy = f"http://{user}:{pw}@proxy.example.com:8080"

Browser: most browsers pop a native username/password dialog on the first 407 and cache the answer for the session. If it stopped prompting after a password change, the browser is replaying the old cached credential, so restart the browser or clear cached auth to make it prompt again. Browsers only speak Basic and Digest here; they can't send an API token, which is why username:password is the model Stat Proxies uses.

Node (fetch/undici, axios): pass the credentials inside the proxy URL exactly as in the Python example. The library encodes the Proxy-Authorization header from user:pass@host:port.

Clear Cache and Retry

If curl works but your app still 407s, the app is holding a stale credential. Clear it:

  • Environment variables. Check echo $http_proxy $https_proxy. An old user:pass@ string here overrides what you pass in code. Re-export or unset it.
  • Browser session cache. Restart the browser or clear cached HTTP auth.
  • Language HTTP client caches. Some clients cache connection pools with the auth attached. Restart the process.
  • OS keychain. macOS Keychain and Windows Credential Manager can store proxy credentials and hand back the old one.

Prevent It Next Time

  • Store credentials in environment variables, not hardcoded in a dozen scripts. Change the password once, re-export once.
  • After any password change, update every consumer in the same pass: scripts, browser profiles, scheduled jobs, CI secrets.
  • Never retype passwords. Copy from the dashboard every time.
  • Keep proxy auth (-U) and target auth (-u) mentally separate. They look one keystroke apart and cause hours of 407 debugging.

Still Getting 407?

If a bare curl with fresh credentials still returns 407, the cause is the account, not the header. Confirm the order is active in your dashboard and that the credentials belong to that order. If curl connects but times out or refuses instead of returning 407, you've moved past auth into a connectivity problem, so see proxy connection refused or timeout: causes and fixes. If requests succeed but pages come back blocked or slow, read why is my proxy slow or blocked.

Still stuck? Reach out to Stat Proxies support with your verbose curl trace (redact the password) and we'll tell you within minutes whether it's a credential mismatch or an account issue. Our static ISP proxies are built for exactly this kind of reproducible, authenticated access. The same credentials work on every request because the IP never rotates.

FAQ

What does HTTP 407 mean?

HTTP 407 Proxy Authentication Required means the proxy demands credentials and didn't receive valid ones. It comes from the proxy, not the destination site (that would be a 401). The request stopped at the proxy hop.

How do I send proxy credentials with curl?

Two ways. Put them in the proxy URL with curl -x http://username:password@proxy.example.com:8080 <url>, or use the uppercase -U flag: curl -x http://proxy.example.com:8080 -U username:password <url>. Both produce a Proxy-Authorization: Basic ... header.

Why is my password not working even though it's correct?

Usually a trailing space or newline from copy-paste, or stale credentials cached in an environment variable, browser, or OS keychain. Check the raw bytes with printf '%s' "$PASS" | xxd and clear any cached proxy auth, then retry.

Can I use the same credentials for the proxy and the target?

No. -U authenticates to the proxy; -u (lowercase) authenticates to the destination site. They're separate. Using -u for proxy credentials is a top cause of a persistent 407.

Do I need to base64-encode the password myself?

No. curl, Python requests, browsers, and Node all base64-encode username:password for you. Encoding it by hand usually causes double-encoding. Only build the Proxy-Authorization header manually if you're crafting raw requests, and use echo -n so you don't encode a trailing newline.