Proxies in Python: requests, httpx and Playwright
Published 7 min read
In short
In requests you pass a proxy as a proxies dict with http and https keys, in httpx as the proxy argument, and in Playwright as a proxy option with server, username and password fields. SOCKS5 in requests needs the PySocks package and the socks5h scheme. Chromium through Playwright does not accept SOCKS5 with a username and password, so use HTTP in the browser: port 8080 at Proxeta.
This article shows how to use a proxy with a username and password in requests, httpx and Playwright for Python, how to control when the IP changes, and how to read the errors. The details in the examples are placeholders: use your own from the Dashboard. To find the exit IP, the examples ask api64.ipify.org, a service that returns the address a request came from.
The proxy string as a URL
The Dashboard issues a host:port:login:password string, while Python libraries expect a URL: http://login:password@host:8080 for HTTP or socks5h://login:password@host:1080 for SOCKS5. The function below converts one into the other and encodes the login and password if they contain @, : or /:
from urllib.parse import quote
def proxy_url(line, scheme="http"):
host, port, login, password = line.strip().split(":", 3)
return f"{scheme}://{quote(login, safe='')}:{quote(password, safe='')}@{host}:{port}"
print(proxy_url("proxy.example.invalid:8080:login-country-us:password"))
print(proxy_url("proxy.example.invalid:1080:login-country-us:password", "socks5h"))
split(":", 3) splits the string into at most four parts, so a colon in the password does no harm. The logins in the examples are shortened: a string from the Dashboard also carries a filter parameter such as -filter-medium, so copy it whole. The parts of the login are explained in proxy string format.
requests: HTTP and SOCKS5
You need the package: pip install requests.
import requests
PROXY = "http://login-country-us:password@proxy.example.invalid:8080"
PROXIES = {"http": PROXY, "https": PROXY}
r = requests.get("https://api64.ipify.org?format=json", proxies=PROXIES, timeout=(10, 30))
r.raise_for_status()
print(r.json()["ip"])
What matters in this code:
- Both keys. The dict key is the site’s scheme, and the value is the proxy address. If you set only
"http", requests to https:// sites go out directly, bypassing the proxy. - The value starts with http://, even for the
"https"key: requests opens a plain connection to the proxy and a CONNECT tunnel through it.https://in the proxy address would mean a TLS connection to the proxy itself, which is a different kind of proxy. - The timeout.
timeout=(10, 30)gives 10 seconds to connect and 30 to wait for the response. Without a timeout, requests can wait for a response forever.
For SOCKS5, install the PySocks package with pip install "requests[socks]" and change the address:
PROXY = "socks5h://login-country-us:password@proxy.example.invalid:1080"
With socks5h:// the proxy resolves the site’s name; with socks5:// your device does, and the site’s address may be chosen for your location rather than the exit location; see HTTP vs SOCKS5. Without PySocks, requests stops with Missing dependencies for SOCKS support.
Sessions and changing the IP
With rotation (a login without -sid-), every new connection gets a new IP. requests.Session keeps the connection open and sends the following requests through it, while requests.get without a session opens a new one each time. Compare:
import requests
PROXY = "http://login-country-us:password@proxy.example.invalid:8080"
PROXIES = {"http": PROXY, "https": PROXY}
URL = "https://api64.ipify.org?format=json"
with requests.Session() as session:
print([session.get(URL, proxies=PROXIES, timeout=(10, 30)).json()["ip"] for _ in range(3)])
print([requests.get(URL, proxies=PROXIES, timeout=(10, 30)).json()["ip"] for _ in range(3)])
The first line of output shows three identical addresses: all the requests went through one connection. In the second, the addresses change: each request has its own connection. If the server closes the session’s connection, the session opens a new one, and with rotation the address changes too.
If the address must hold between requests, for example after signing in to an account or across the steps of a form, take a sticky session string with -sid-: with it, all six requests in the example leave from one address while the session lasts. When to use which mode is covered in sticky session vs rotation.
httpx
You need the package: pip install httpx. httpx takes the proxy as the proxy argument; the old proxies argument was removed in version 0.28, and a client created with it fails.
import httpx
PROXY = "http://login-country-us:password@proxy.example.invalid:8080"
with httpx.Client(proxy=PROXY, timeout=30) as client:
r = client.get("https://api64.ipify.org?format=json")
r.raise_for_status()
print(r.json()["ip"])
The async client takes the same argument:
import asyncio
import httpx
PROXY = "http://login-country-us:password@proxy.example.invalid:8080"
async def main():
async with httpx.AsyncClient(proxy=PROXY, timeout=30) as client:
r = await client.get("https://api64.ipify.org?format=json")
r.raise_for_status()
print(r.json()["ip"])
asyncio.run(main())
An httpx client also keeps connections open, so with rotation the requests of one client may leave from one address. SOCKS5 in httpx needs the extra httpx[socks] package, and without it the client stops with an ImportError; in this article, SOCKS5 is shown with requests.
Playwright: a proxy with a username and password
You need the package and the browser: pip install playwright and playwright install chromium.
from playwright.sync_api import sync_playwright
PROXY = {
"server": "http://proxy.example.invalid:8080",
"username": "login-country-us-sid-k7m2q9x4ab",
"password": "password",
}
with sync_playwright() as p:
browser = p.chromium.launch(proxy=PROXY)
try:
page = browser.new_page()
page.goto("https://api64.ipify.org?format=json", timeout=60000)
print(page.inner_text("body"))
finally:
browser.close()
- The fields. The username and password go in separate
usernameandpasswordfields, andserverholds only the scheme, host and port. - A sticky session. A browser opens several connections even for one page, and with rotation they could leave from different addresses. That is why the example uses a string with
-sid-.
If different tasks need different addresses, set the proxy per context: each context has its own cookies and its own proxy. In the example below, one context exits in the US and the other in Germany:
from playwright.sync_api import sync_playwright
LOGINS = ["login-country-us-sid-k7m2q9x4ab", "login-country-de-sid-p3w8n1c5zd"]
with sync_playwright() as p:
browser = p.chromium.launch()
try:
for login in LOGINS:
context = browser.new_context(proxy={
"server": "http://proxy.example.invalid:8080",
"username": login,
"password": "password",
})
page = context.new_page()
page.goto("https://api64.ipify.org?format=json", timeout=60000)
print(login, page.inner_text("body"))
context.close()
finally:
browser.close()
Why not SOCKS5
Chromium through Playwright does not work with SOCKS5 that requires a username and password. A launch with "server": "socks5://proxy.example.invalid:1080" and the username and password fields stops with Browser does not support socks5 proxy authentication. If you put the credentials inside the socks5:// address instead, the launch succeeds, but the browser does not send them: it offers the proxy only a connection without a login, a proxy that requires one refuses it, and page.goto fails with net::ERR_SOCKS_CONNECTION_FAILED. So use HTTP in Playwright, which is port 8080. We have not tested Firefox or WebKit.
Errors and what they mean
The error number in Connection refused depends on the system: [Errno 61] on macOS, [Errno 111] on Linux.
| Library | Message | What it means |
|---|---|---|
| requests | ProxyError: ... Tunnel connection failed: 407 Proxy Authentication Required | The HTTP proxy did not accept the username or password |
| requests | ConnectionError: ... SOCKS5 authentication failed | The SOCKS5 proxy did not accept the username or password |
| requests | ProxyError: ... Failed to establish a new connection: [Errno 61] Connection refused | The connection to the proxy was not established: a wrong port, or outgoing connections are blocked on your network |
| requests | ProxyError: ... Failed to resolve 'proxy.example.invalid' | The proxy’s address was not found. An example run without replacing the placeholder host ends this way too |
| requests | InvalidSchema: Missing dependencies for SOCKS support. | PySocks is not installed: pip install "requests[socks]" |
| httpx | ProxyError: 407 Proxy Authentication Required | The proxy did not accept the username or password |
| httpx | ConnectError: [Errno 61] Connection refused | The connection to the proxy was not established |
| Playwright | net::ERR_PROXY_CONNECTION_FAILED | The connection to the proxy was not established |
| Playwright | net::ERR_SOCKS_CONNECTION_FAILED | The connection through the SOCKS5 proxy was not established. Credentials written inside the socks5:// address end this way too: Chromium does not send them |
| Playwright | Timeout 60000ms exceeded | The page did not load in time. In our test, a wrong password looked like this too: Chromium did not show a 407 |
If Playwright runs into a timeout, test the same string in requests or curl, where a wrong username or password shows up as a 407. curl’s errors are covered in curl with a proxy.
A proxy does not change a site’s rules. Follow its terms and robots.txt, limit how often you send requests, and do not answer a refusal by changing the IP: see the web scraping proxy guide.
How the examples were run
The examples were run on November 5, 2026 on macOS in Python 3.11 with requests 2.33.1, PySocks 1.7.1, httpx 0.28.1 and Playwright 1.58.0 for Python, through local test HTTP and SOCKS5 proxies with a username and password and a local site, not through the Proxeta gateway. We repeated both Playwright SOCKS5 checks in Playwright 1.62.1 for Node.js with Chromium 151. For the test, the browser was allowed to trust the local site’s certificate. That checks the syntax and how the libraries behave, but not exit IPs or the real network.
Frequently asked questions
How do I use a proxy with a username and password in requests?
Pass a proxies dict in which both keys, http and https, point to an address like http://username:password@host:8080. If you set only http, requests to https:// sites bypass the proxy. For SOCKS5, install requests[socks] and use socks5h://username:password@host:1080.
Why doesn’t Playwright accept SOCKS5 with a password?
Chromium through Playwright does not support signing in to a SOCKS5 proxy with a username and password: the launch stops with “Browser does not support socks5 proxy authentication”. We checked this on November 5, 2026 in Playwright 1.62.1 for Node.js with Chromium 151 and in Playwright 1.58.0 for Python. Use an HTTP proxy, which is port 8080 at Proxeta.
How do I get a new IP for every request in Python?
Take a rotating string, with no -sid- parameter in the login, and open a new connection for every request: for example, call requests.get without a shared session. requests.Session keeps the connection open, and requests over one connection leave from one address.
How do I set different proxies for different Playwright contexts?
Pass the proxy option to browser.new_context rather than chromium.launch: each context then has its own cookies and its own proxy. Give each context its own sticky session string so that all of its connections leave from one address.