We use cookies to enhance user experience, personalize content, and analyze traffic. Cookie Policy

← Back to all articles

Python Requests Proxy Auth: Username, Password, 407 Fix

Format Python requests proxy authentication as http://user:pass@host:port, URL-encode special characters in passwords, and fix 407 and ProxyError.

by Unknown Proxies

13 min read

June 23, 2026

Updated October 17, 2026

Python Requests Proxy Auth: Username, Password, 407 Fix

Python requests proxy authentication works by putting the username and password inside the proxy URL: http://username:password@host:port. Assign that URL to both the http and https keys of the proxies dict. If the password contains @, :, /, #, or spaces, URL-encode it with urllib.parse.quote(password, safe="") first.

If the proxy rejects the login, you get 407 Proxy Authentication Required. For HTTPS targets, Requests raises it as a ProxyError instead of returning a response. Either way, the proxy refused before the target site saw anything.

Use this guide when a proxy works in a dashboard, browser, cURL, or proxy checker but fails in Python requests. For the other proxies options (per-scheme and per-host keys, sessions, environment variables, SOCKS), see our Python requests proxies dictionary reference. For a status-code-first explanation, use the 407 proxy authentication guide.

Python requests proxy authentication flow from client credentials to proxy approval or 407 response

Python requests proxy authentication quick fix

Start with one known-good proxy, not a full rotating list:

import requests

proxy_url = "http://username:password@proxy.example.com:8000"

proxies = {
    "http": proxy_url,
    "https": proxy_url,
}

response = requests.get(
    "https://ipv4.unknownproxies.com/ip",
    proxies=proxies,
    timeout=15,
)

print(response.status_code)
print(response.text)

If this works, Python can authenticate with the proxy and route traffic through it. Then test your real target.

If it fails with 407, check these before changing headers, user agents, target URLs, or rotation:

Check Why it matters
Username and password A typo, stale password, copied space, or wrong account will fail before the target loads
Host and port Residential, ISP, HTTP, and SOCKS endpoints may use different ports
Protocol scheme Most HTTP proxy URLs start with http://, even for HTTPS target pages
URL encoding @, :, /, ?, #, spaces, and similar characters can break proxy URL parsing
IP allowlist If your account uses allowlist auth, the server running Python must be allowed
Plan status Expired plans, empty balances, or wrong product endpoints can look like bad credentials

How a 407 shows up in requests

Target URL What Requests does on a 407
http://... Returns a Response with status_code == 407
https://... Raises requests.exceptions.ProxyError, with a message like Tunnel connection failed: 407 Proxy Authentication Required

The difference comes from CONNECT. For HTTPS, the proxy rejects the tunnel before any response exists, so there is no status code to read. Catch ProxyError and look at the message. We reproduced both outcomes against requests 2.34.2 (October 2026) with a stand-in proxy that returns 407 for wrong credentials.

Do not rotate proxies to fix a shared authentication mistake. If every proxy in the list uses the same wrong username, password, port, or allowlist setting, every request will fail.

The correct username and password format

requests expects a proxy mapping by URL scheme:

proxy_url = "http://username:password@host:port"

proxies = {
    "http": proxy_url,
    "https": proxy_url,
}

The dictionary keys describe the destination URL scheme. The proxy URL value describes how Python connects to the proxy.

That distinction causes a lot of confusion. For an HTTPS target such as https://example.com, the https key should often still point to an http:// proxy URL:

proxies = {
    "https": "http://username:password@host:port",
}

Many HTTP proxies support HTTPS destinations through the HTTP CONNECT method. Only use an https:// proxy URL if your proxy provider explicitly gives you an HTTPS proxy endpoint and your installed client stack supports it.

If your provider gives you this common four-part format:

host:port:username:password

convert it to:

http://username:password@host:port

The proxy formatter converts whole lists between host:port:user:pass and user:pass@host:port without moving each field by hand.

URL-encode special characters in credentials

Proxy credentials live inside a URL. That means reserved URL characters can change how Python parses the string.

This proxy URL is ambiguous:

http://customer@example.com:p@ss:word#1@proxy.example.com:8000

Python may read part of the username or password as the host, separator, fragment, or port. Build the URL with encoded credentials instead:

from urllib.parse import quote

username = quote("customer@example.com", safe="")
password = quote("p@ss:word#1", safe="")

proxy_url = f"http://{username}:{password}@proxy.example.com:8000"

proxies = {
    "http": proxy_url,
    "https": proxy_url,
}

Encode only the username and password values. Do not encode the whole proxy URL, because requests still needs to understand the scheme, host, and port.

Requests decodes the values again before it builds the Proxy-Authorization header (requests.utils.get_auth_from_url() calls unquote() on both parts). So the proxy receives your real password, not the encoded form.

Common encodings:

Character Encoded
@ %40
: %3A
/ %2F
# %23
? %3F
% %25
space %20

Pass safe="" to quote(). The default keeps / unencoded, which still breaks the URL.

Use environment variables for proxy secrets

Do not commit real proxy credentials into Python files, notebooks, GitHub issues, logs, or screenshots. Keep the proxy URL in an environment variable:

export PROXY_URL="http://username:password@proxy.example.com:8000"

Then read it in Python:

import os
import requests

proxy_url = os.environ["PROXY_URL"]

response = requests.get(
    "https://ipv4.unknownproxies.com/ip",
    proxies={"http": proxy_url, "https": proxy_url},
    timeout=15,
)

print(response.text)

For production jobs, use the secret system provided by your host, CI runner, container platform, or scheduler. If a request fails, log a proxy label or host, not the full credential-bearing URL. This helper strips the credentials before you print or log:

from urllib.parse import urlsplit


def safe_proxy_label(proxy_url: str) -> str:
    parts = urlsplit(proxy_url)
    return f"{parts.scheme}://{parts.hostname}:{parts.port}"


print(safe_proxy_label("http://user:p%40ss@proxy.example.com:8000"))
# http://proxy.example.com:8000

Exception messages from requests can also include the proxy host and port, so scrub them the same way if your logs are shared.

Fix 407 Proxy Authentication Required in Requests

407 Proxy Authentication Required means the proxy server wants valid proxy credentials before it forwards the request. It is different from:

Response Layer Meaning
401 Target site or API The destination wants user, API, or app authentication
403 Target site, CDN, WAF, or app The request was understood but access was denied
407 Proxy server The proxy needs valid proxy credentials before forwarding
429 Target, API, CDN, WAF, or proxy layer Too much request volume from an identity

In requests, catch proxy errors separately so your logs do not treat authentication failures like normal target blocks:

import requests

proxy_url = "http://username:password@proxy.example.com:8000"

try:
    response = requests.get(
        "https://ipv4.unknownproxies.com/ip",
        proxies={"http": proxy_url, "https": proxy_url},
        timeout=15,
    )
    response.raise_for_status()
except requests.exceptions.ProxyError as error:
    if "407" in str(error):
        print("Proxy rejected the credentials (407):", error)
    else:
        print("Could not reach the proxy:", error)
except requests.exceptions.HTTPError as error:
    if error.response.status_code == 407:
        print("Proxy rejected the credentials (407)")
    else:
        print("HTTP response failed:", error.response.status_code)

The ProxyError branch covers HTTPS targets, where the 407 arrives as an exception. The HTTPError branch covers plain http:// targets, where raise_for_status() turns the 407 response into an exception.

If the response status is 407, use this order:

  1. Copy the credentials again from the provider dashboard.
  2. Remove spaces, quotes, and hidden line breaks.
  3. Confirm the exact host and port for the proxy product.
  4. Confirm whether the endpoint is HTTP(S) or SOCKS5.
  5. URL-encode special characters in username and password.
  6. Check whether your account is in username/password mode or IP allowlist mode.
  7. Test the same proxy with cURL.
  8. Test from the same machine that will run the Python job.

Here is a matching cURL check:

curl -x "http://username:password@proxy.example.com:8000" \
  "https://ipv4.unknownproxies.com/ip"

If cURL fails too, fix the proxy account, credentials, allowlist, or endpoint before changing Python code. If cURL works but requests fails, focus on URL encoding, environment proxy variables, SOCKS support, and how the Python app builds the proxy string.

Can I pass the username and password separately?

Requests has requests.auth.HTTPProxyAuth, which takes the username and password as separate values:

import requests
from requests.auth import HTTPProxyAuth

response = requests.get(
    "http://example.com",
    proxies={"http": "http://proxy.example.com:8000"},
    auth=HTTPProxyAuth("username", "password"),
    timeout=15,
)

It has a real limit. HTTPProxyAuth adds the Proxy-Authorization header to the request itself. For an https:// target, that request travels inside the CONNECT tunnel, so the proxy never sees the header and the tunnel fails with 407. It also takes the auth= slot, so you cannot use it with target-site auth on the same call.

For HTTPS targets, put the credentials in the proxy URL. Requests then sends them on the CONNECT request, where the proxy can read them. If your config stores the fields separately, build the URL in code:

from urllib.parse import quote

def proxy_url(host: str, port: int, user: str, password: str) -> str:
    return f"http://{quote(user, safe='')}:{quote(password, safe='')}@{host}:{port}"

In our test, an https:// target through HTTPProxyAuth reached the proxy with no Proxy-Authorization header on the CONNECT request and got a 407, while the same credentials in the URL succeeded. A plain http:// target worked with either method.

Requests only builds Basic proxy auth from the URL. If the proxy answers with a Proxy-Authenticate header for another scheme, see the next section.

When the proxy asks for Digest, NTLM, or Kerberos

Residential and ISP proxy services normally use Basic username and password or an IP allowlist, so the URL format above is all you need. Corporate and some self-hosted proxies can answer 407 with a Proxy-Authenticate header that names a different scheme. Check that header before you change credentials:

curl -sI -x "http://proxy.example.com:8000" http://example.com | grep -i proxy-authenticate

If it says Basic, your URL format or credentials are wrong. If it says Digest, the requests-toolbelt package has a handler, shown in its authentication docs:

import requests
from requests_toolbelt.auth.http_proxy_digest import HTTPProxyDigestAuth

proxies = {
    "http": "http://proxy.example.com:8000",
    "https": "http://proxy.example.com:8000",
}
auth = HTTPProxyDigestAuth("username", "password")

response = requests.get("https://example.com", proxies=proxies, auth=auth, timeout=15)

For NTLM, the requests-ntlm family of packages covers proxies, but check the package docs for the HTTPS tunnel case. For Kerberos or Negotiate, the requests-kerberos issue tracker records that proxy support was added for plain http endpoints in 2021, while HTTPS endpoints through a Kerberos proxy remain unsupported. If you hit that, ask the proxy owner for Basic or allowlist access for the job, or run a local helper proxy that handles the enterprise scheme and forwards to the upstream.

HTTP, HTTPS, and SOCKS authentication

For HTTP proxy endpoints, the basic authenticated format is:

http://username:password@host:port

For SOCKS5 endpoints, install SOCKS support first:

pip install "requests[socks]"

Then use the SOCKS scheme:

proxies = {
    "http": "socks5://username:password@host:port",
    "https": "socks5://username:password@host:port",
}

Use socks5h:// when DNS resolution should happen through the proxy:

proxies = {
    "http": "socks5h://username:password@host:port",
    "https": "socks5h://username:password@host:port",
}

Do not switch protocols as a guess. If the provider sold you an HTTP proxy, use the HTTP proxy format. If the provider sold you a SOCKS5 endpoint, install SOCKS support and use the SOCKS scheme. For a deeper protocol comparison, read SOCKS5 vs HTTP proxy.

Visual checklist for debugging Python requests proxy username and password authentication

Session authentication with requests.Session

If one workflow should keep the same proxy, headers, cookies, and connection pool, put the proxy on a requests.Session:

import os
import requests

session = requests.Session()

session.proxies.update({
    "http": os.environ["PROXY_URL"],
    "https": os.environ["PROXY_URL"],
})

session.headers.update({
    "User-Agent": "Mozilla/5.0 (compatible; ExampleBot/1.0)",
})

first = session.get("https://example.com", timeout=15)
second = session.get("https://example.com/account", timeout=15)

print(first.status_code, second.status_code)

Keep one authenticated proxy identity tied to one logical session when cookies, login state, cart state, or account behavior matters. Rotating the proxy while reusing the same cookies can create avoidable challenges or inconsistent responses.

For independent public-page scraping, rotation can be fine after authentication works. Once this single-proxy test passes, the rotating proxy Python guide shows how to spread requests across many authenticated proxies.

Environment proxy variables can override your test

requests can also read proxy settings from environment variables such as HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY.

That is useful for global configuration, but it can hide mistakes when you are debugging explicit credentials. If your code says one proxy but traffic leaves through another, check the shell environment:

env | grep -i proxy

Note that environment proxies override session.proxies (the Requests docs warn about this), but not a proxies= argument passed on the call. For a controlled test, pass the proxies dictionary directly, or set session.trust_env = False as shown in the guide to disabling environment proxies in Requests. If you intentionally use environment-level proxy settings, keep them in the same credential format:

export HTTPS_PROXY="http://username:password@proxy.example.com:8000"

Then test one target and one IP echo endpoint before running the full scraper.

Troubleshooting matrix

Use this matrix to avoid changing the wrong layer:

Symptom in Python requests Likely cause Next step
407 Proxy Authentication Required Bad credentials, format, protocol, allowlist, or plan access Test one proxy with encoded credentials
ProxyError with connection refused Wrong host, port, protocol, or network route Confirm endpoint and test with cURL
Missing dependencies for SOCKS support SOCKS URL used without the extra package Install requests[socks] or use HTTP endpoint
Local IP appears in IP test Proxy was not applied to that scheme Set both http and https keys
Target returns 403 after IP test works Target rejected the request Debug permissions, WAF policy, sessions, and site rules
Target returns 429 after IP test works Request rate is too high Lower concurrency and add backoff

If the error is not clearly one of these, the general proxy error checklist helps you find the failing layer. For scraping and automation, keep the debugging sequence responsible and narrow. Confirm your proxy account and client are configured correctly, respect site terms and applicable law, and reduce request pressure before scaling workers.

Common mistakes

Avoid these mistakes when setting up Python requests proxy authentication:

Change one thing at a time. A clean single-proxy test is faster than guessing across proxy type, headers, retries, and target URLs at the same time.

FAQ

How do I add proxy authentication to Python requests?

Put the proxy username and password in the proxy URL, then pass it in the proxies dictionary: {"http": proxy_url, "https": proxy_url}. The common URL format is http://username:password@host:port.

Why does requests return 407 Proxy Authentication Required?

requests returns 407 when the proxy did not accept authentication. Check the username, password, host, port, protocol scheme, URL encoding, plan status, and IP allowlist.

How do I handle special characters in a proxy password?

Use urllib.parse.quote on the username and password values before building the proxy URL. Encode the credential values, not the whole URL.

Can I pass proxy username and password separately in requests?

Only partly. requests.auth.HTTPProxyAuth(user, password) works for plain http:// targets. For https:// targets, the credentials must be in the proxy URL so Requests can send them on the CONNECT request.

Why do I get ProxyError instead of a 407 status code?

For HTTPS targets, the proxy rejects the CONNECT tunnel, so Requests raises ProxyError with 407 Proxy Authentication Required in the message. There is no Response object to inspect.

What if the proxy needs Digest, NTLM, or Kerberos authentication?

Requests builds only Basic proxy auth from the URL. For Digest, use HTTPProxyDigestAuth from requests-toolbelt. For NTLM or Kerberos, use the matching third-party package or a local helper proxy, and check its HTTPS support first.

Final thoughts

Python requests proxy authentication is mostly a formatting and verification problem: build http://username:password@host:port, URL-encode credentials when needed, set both http and https keys, and test one proxy against an IP endpoint before running a scraper.

If authentication works but the target still blocks or rate limits the job, move to session design, pacing, and proxy selection. Unknown Proxies residential and ISP plans use username and password auth in this same http://user:pass@host:port format. Compare ISP plans for retail and ticketing flows that need one stable IP, and residential pricing for rotating traffic.

Technical references: Requests advanced usage: proxies, Python urllib.parse.quote, requests-toolbelt authentication, and MDN 407 Proxy Authentication Required.

About the Author

Unknown Proxies

Proxy Infrastructure Team

Stay Unknown

High-performance dedicated proxies optimized for speed and reliability. Get uncompromising quality, 99.9% uptime, and unmatched support. Stay Unknown.

Explore Plans