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 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:
- Copy the credentials again from the provider dashboard.
- Remove spaces, quotes, and hidden line breaks.
- Confirm the exact host and port for the proxy product.
- Confirm whether the endpoint is HTTP(S) or SOCKS5.
- URL-encode special characters in username and password.
- Check whether your account is in username/password mode or IP allowlist mode.
- Test the same proxy with cURL.
- 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.

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:
- Using
host:port:user:passdirectly whenrequestsexpectshttp://user:pass@host:port. - Forgetting the
httpskey, then wondering why HTTPS targets skip the proxy. - Starting the proxy URL with
https://just because the target URL is HTTPS. - Leaving
@,:, or#unencoded inside credentials. - Pasting a full proxy URL into a host-only field in shared config.
- Logging the full proxy URL with the password visible.
- Retrying hundreds of times after 407 instead of stopping the job.
- Mixing old and new proxy credentials in the same list.
- Debugging target headers before proving proxy authentication works.
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.
