Python proxy with requests, httpx and aiohttp
By Mechelle Henderson · Published: 7 September 2026 · 6 min read
TL;DR: All three clients take an authenticated proxy, but the syntax differs. requests: a proxies dict with user:pass in the URL. httpx: the singular proxy= argument (not proxies= anymore). aiohttp: a plain proxy URL plus a separate proxy_auth=BasicAuth(...). Point them at a rotating residential gateway and change the sticky-session token to rotate IPs.
requests
The classic client. Put the credentials in the proxy URL and pass a proxies dict — use a Session so the proxy applies to every call:
import requests
PROXY = "http://YOUR_USERNAME:[email protected]:41080"
session = requests.Session()
session.proxies = {"http": PROXY, "https": PROXY}
r = session.get("https://ip.sb")
print(r.text.strip()) # a Roam residential exit IP
Both keys use the same http:// URL on purpose: the scheme names the proxy's protocol, not the target's, so this still proxies your HTTPS requests correctly.
httpx (sync and async)
Modern httpx uses the singular proxy= argument on the client. If you've seen proxies= in an older tutorial, it's gone — that plural form was removed:
import httpx
PROXY = "http://YOUR_USERNAME:[email protected]:41080"
# Synchronous
with httpx.Client(proxy=PROXY) as client:
print(client.get("https://ip.sb").text.strip())
# Asynchronous — same argument
import asyncio
async def main():
async with httpx.AsyncClient(proxy=PROXY) as client:
r = await client.get("https://ip.sb")
print(r.text.strip())
asyncio.run(main())
For SOCKS5 instead of HTTP, install httpx[socks] and use a socks5:// URL. Over HTTP nothing extra is needed.
aiohttp
aiohttp is the odd one out: it does not reliably read credentials from the proxy URL. Pass the proxy as a bare URL and the credentials separately as proxy_auth:
import aiohttp, asyncio
GATEWAY = "http://gw.roamproxy.com:41080"
AUTH = aiohttp.BasicAuth("YOUR_USERNAME", "YOUR_PASSWORD")
async def main():
async with aiohttp.ClientSession() as session:
async with session.get("https://ip.sb", proxy=GATEWAY, proxy_auth=AUTH) as resp:
print((await resp.text()).strip())
asyncio.run(main())
aiohttp supports only HTTP proxies out of the box. If you searched for "aiohttp proxy socks5," you need the separate aiohttp-socks package — plain aiohttp will not do SOCKS. Over Roam's HTTP gateway, the snippet above is all you need.
Rotating IPs
With a rotating residential proxy there's no proxy list to manage. Keep the one gateway and change the sticky-session token in the username — each distinct session is a different residential exit IP:
import itertools, requests
BASE_USER, PASSWORD = "YOUR_USERNAME", "YOUR_PASSWORD"
counter = itertools.count()
def proxy_for_new_ip():
user = f"{BASE_USER}_session_{next(counter)}"
url = f"http://{user}:{PASSWORD}@gw.roamproxy.com:41080"
return {"http": url, "https": url}
for _ in range(3):
r = requests.get("https://ip.sb", proxies=proxy_for_new_ip())
print(r.text.strip()) # a different IP each time
Reuse the same token when several requests need to share an IP (a login, a paginated flow); generate a new one for a fresh IP. See what are rotating proxies for how the session string works, and how to avoid getting blocked for pacing.
When a plain HTTP client isn't enough
These libraries send a scripted request with a Python TLS fingerprint. A clean residential IP fixes reputation blocks, but a site fingerprinting your TLS handshake will still flag the request. When that happens, give your requests a browser-accurate fingerprint with curl_cffi, or, if the page genuinely needs JavaScript, drive a real browser through the same proxy with Playwright or Puppeteer. To match the fix to the specific defense, see how to get past Cloudflare.
FAQ
How do I set a proxy in Python requests?
Pass a proxies dict with the credentials in the URL: proxies = {"http": "http://user:[email protected]:41080", "https": "http://user:[email protected]:41080"}, then requests.get(url, proxies=proxies). The same http:// URL is used for both keys — it names the proxy's own protocol, not the target's, so it correctly proxies HTTPS targets too.
Is it proxy or proxies in httpx?
Current httpx uses the singular proxy= argument on the client: httpx.Client(proxy="http://user:[email protected]:41080"). The old plural proxies= dict was deprecated and removed, so tutorials using proxies= are out of date. httpx.AsyncClient takes the same proxy= argument.
How do I authenticate a proxy in aiohttp?
aiohttp does not reliably read credentials from the proxy URL. Pass the proxy as a plain URL and the credentials separately: session.get(url, proxy="http://gw.roamproxy.com:41080", proxy_auth=aiohttp.BasicAuth("user", "pass")). aiohttp also supports only HTTP proxies natively — for SOCKS you need the aiohttp-socks package.
How do I rotate IPs across Python requests?
With a rotating residential proxy you keep one gateway and vary the sticky-session token in the username. Build a fresh username like USERNAME_session_5 per request or per client, and each distinct session maps to a different residential exit IP — no proxy list to maintain.
Every one of these clients wants a plain host:port proxy plus credentials — exactly what Roam gives you. Rotating residential at $2/GB and static residential at $4/IP per month, over HTTP and SOCKS5, with sticky sessions for per-request rotation. Create an account and get 300MB of free trial traffic to point your first script at a residential exit.