How to Rotate Proxies in Python: requests, httpx & aiohttp (2026 Guide)

Published June 2, 2026 · 9 min read

If you scrape, crawl, or automate anything at scale in Python, sooner or later the target site notices that every request comes from one IP and shuts you down. Rotating proxies in Python spreads your traffic across many IP addresses so no single one trips a rate limit or earns a ban. This guide shows the exact code for requests, httpx, and aiohttp, plus how to pick between a rotating gateway and a managed IP pool.

Two cross-cutting decisions shape everything below. First: rotating vs sticky sessions — a new IP per request, or one IP held for a multi-step flow. Second: residential vs datacenter IPs — see the proxy-type decision matrix. Get those two right and the code is the easy part.

Two Ways to Rotate

1. Gateway rotation (recommended). You connect to a single endpoint and the provider hands you a fresh exit IP on every new connection. No list to manage, no dead IPs to prune. JIBAO's dynamic residential pool works this way: same host and port, a new IP each request.

2. Self-managed IP list. You hold a list of host:port entries and pick one per request. More control, but you own the health-checking, retries, and ban-tracking. Useful with dedicated datacenter IPs when you need known, stable addresses.

Rotating with requests

The simplest case — route one request through a gateway that rotates for you. Use socks5h:// (the trailing h means DNS is resolved on the proxy side, which avoids leaking lookups):

import requests

# JIBAO rotating residential gateway: a new exit IP per connection
PROXY = "socks5h://USERNAME:[email protected]:10001"

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

for _ in range(5):
    r = requests.get("https://httpbin.org/ip", proxies=proxies, timeout=30)
    print(r.json()["origin"])   # a different IP each loop

Need requests to speak SOCKS5? Install the extra: pip install "requests[socks]". If you prefer an HTTP proxy endpoint, swap the scheme to http:// — the rest is identical.

Rotating from your own IP list

import random
import requests

PROXY_POOL = [
    "socks5h://USERNAME-country-us:[email protected]:10001",
    "socks5h://USERNAME-country-uk:[email protected]:10001",
    "socks5h://USERNAME-country-de:[email protected]:10001",
]

def fetch(url):
    proxy = random.choice(PROXY_POOL)
    return requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)

resp = fetch("https://example.com")
print(resp.status_code)

Sticky sessions when you must keep one IP

Login flows, carts, and paginated results break if the IP changes mid-task. Pin an IP by adding a session token to the username; reuse the same token to keep the same exit IP for its lifetime:

import requests

SESSION_ID = "task-0001"
PROXY = f"socks5h://USERNAME-session-{SESSION_ID}:[email protected]:10001"

s = requests.Session()
s.proxies = {"http": PROXY, "https": PROXY}

s.post("https://example.com/login", data={"u": "me", "p": "secret"})
s.get("https://example.com/dashboard")   # same IP, session intact

Rotating with httpx

httpx gives you a modern client with HTTP/2 and native async. Note that newer versions take a single proxy= argument:

import httpx

PROXY = "socks5h://USERNAME:[email protected]:10001"

with httpx.Client(proxy=PROXY, timeout=30) as client:
    for _ in range(5):
        print(client.get("https://httpbin.org/ip").json()["origin"])

SOCKS support in httpx needs pip install "httpx[socks]". For HTTP/2, add http2=True to the client — it makes your traffic look more like a real browser, which pairs well with TLS fingerprint matching.

Concurrent rotation with aiohttp

For high throughput, fire many requests at once. Each connection through the gateway gets its own exit IP, so concurrency and rotation come together:

import asyncio
import aiohttp
from aiohttp_socks import ProxyConnector

PROXY = "socks5://USERNAME:[email protected]:10001"

async def fetch(url):
    connector = ProxyConnector.from_url(PROXY)
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(url, timeout=aiohttp.ClientTimeout(total=30)) as r:
            return await r.text()

async def main():
    urls = ["https://httpbin.org/ip"] * 20
    results = await asyncio.gather(*(fetch(u) for u in urls))
    print(f"fetched {len(results)} pages")

asyncio.run(main())

Install the SOCKS connector with pip install aiohttp_socks. Build a fresh connector per task so each request opens a new proxied connection.

Retry Logic That Actually Helps

Rotation only pays off if you retry failures on a fresh IP. Back off exponentially and rotate the session token on each attempt:

import requests
from time import sleep

def fetch_with_retry(url, max_retries=4):
    for attempt in range(max_retries):
        proxy = f"socks5h://USERNAME-session-r{attempt}:[email protected]:10001"
        try:
            r = requests.get(url, proxies={"http": proxy, "https": proxy}, timeout=30)
            if r.status_code in (403, 429):
                raise requests.HTTPError(f"blocked: {r.status_code}")
            r.raise_for_status()
            return r.text
        except requests.RequestException:
            sleep(2 ** attempt)        # 1s, 2s, 4s, 8s
    raise RuntimeError(f"failed after {max_retries} tries: {url}")

Verify Your Rotation Is Working

Before a big run, confirm the IP actually changes. Hit an echo endpoint a few times and check the IPs differ. To also confirm your TLS fingerprint is not giving you away, run the request against JIBAO's free JA3/TLS fingerprint checker — a rotating IP with a dead-giveaway Python fingerprint still gets blocked.

Best Practices

Once rotation is solid, the next wall you hit is usually fingerprinting, not IPs. If clean residential IPs still get 403s, read how to bypass TLS fingerprinting with curl_cffi and the full Cloudflare bypass recipe.

Get Rotating Residential Proxies

Get $5 free credit and a rotating residential gateway that hands you a new IP on every request.

Start Free Trial
Universal for All IP Products · Massive Nodes Always Available

Join now & enjoy up to 100% deposit bonus.

New users get $5 USDT instantly, plus an extra first-deposit reward — limited-time offer.