Documentation Blog Free tools [email protected]Log in

How to rotate a proxy in Selenium Python (three working methods)

Selenium driver sessions routed through one rotating proxy endpoint, each request leaving from a different exit IP toward the target siteSelenium sessionsone endpointexit IPstargetdriver Asession=a1f9driver Bsession=7c2edriver Csession=e04bgateway:8000user-session-*sticky or per request203.0.113.24198.51.100.7192.0.2.181site.com200 OK

Chrome reads its proxy from a command-line switch when the process starts, so rotating a proxy in Selenium Python means one of three things: relaunch the driver with a new proxy per session, point every session at a rotating gateway that changes the exit IP for you, or intercept traffic locally with selenium-wire. Each has a different failure mode.

Why Selenium IP rotation is a process-lifecycle problem

The proxy you pass as --proxy-server=host:port is applied to the whole browser process at startup. Selenium's own Proxy capability lands in the same place: it is serialised into the session-creation payload and handed to the driver before the first navigation. Chromium documents these as network settings read at launch, and there is no supported public API to swap them for a running profile (Chromium network settings).

That leaves exactly three levers:

  • New process per IP — quit the driver, launch a new one with a different proxy string.
  • Stable proxy string, rotating upstream — the browser keeps one endpoint, and the proxy provider changes the exit IP per request or per session token.
  • Local interception — a proxy running on 127.0.0.1 (selenium-wire, mitmproxy, or your own forwarder) that re-routes upstream on demand. The browser never sees the change.

Everything you will read about Selenium proxy Python rotation is a variation on those three. Pick based on how often you need a new IP and whether you need cookies to survive rotation.

Comparison of a fixed launch-flag proxy versus a rotating gateway across three page loadslaunch flag: proxy fixed for the whole process--proxy-server=Apage 1 · IP Apage 2 · IP Apage 3 · IP Anew IP only after driver.quit() + relaunchrotating endpoint: one string, changing exitsgateway:8000page 1 · IP 1page 2 · IP 2page 3 · IP 3session token pins an IP when you need cookies to stick

Method 1: a new driver per proxy (works with vanilla Selenium)

This is the portable option: no extra dependencies, works on Chrome, Firefox and Edge, and it also resets cookies, cache and storage — which is usually what you want when you rotate identity.

import itertools
from selenium import webdriver
from selenium.webdriver.common.by import By

PROXIES = [
    "http://198.51.100.7:8000",
    "http://203.0.113.24:8000",
    "http://192.0.2.181:8000",
]

URLS = ["https://httpbin.org/ip"] * 3

def make_driver(proxy: str):
    opts = webdriver.ChromeOptions()
    opts.add_argument("--headless=new")
    opts.add_argument(f"--proxy-server={proxy}")
    # keep the proxy on localhost traffic out of the way
    opts.add_argument("--proxy-bypass-list=<-loopback>")
    return webdriver.Chrome(options=opts)

for url, proxy in zip(URLS, itertools.cycle(PROXIES)):
    driver = make_driver(proxy)
    try:
        driver.set_page_load_timeout(30)
        driver.get(url)
        print(proxy, "->", driver.find_element(By.TAG_NAME, "body").text)
    finally:
        driver.quit()

Two costs you should measure before scaling this: browser startup is typically one to three seconds of pure overhead per rotation, and every relaunch throws away warm cache, so the same assets get downloaded again through metered bandwidth. If you rotate every request, most of your proxy spend goes on re-downloading the same CSS and fonts.

Method 2: one rotating endpoint, sticky sessions where needed

The lower-friction pattern is to give the browser a single proxy string and let the network do the rotation. That is exactly what rotating proxies are for: a fresh IP on every request from one endpoint, or a sticky session held for a defined window when you need cookies and a login to survive. Providers usually encode the choice in the username, so the Python side is just string formatting:

import uuid
from selenium import webdriver

USER, PASSWORD, HOST = "your-user", "your-pass", "gateway.example.net:8000"

def sticky_proxy(country="us", minutes=10):
    sid = uuid.uuid4().hex[:8]  # new token = new exit IP
    # username syntax varies by provider - check their docs
    user = f"{USER}-country-{country}-session-{sid}-ttl-{minutes}m"
    return f"http://{user}:{PASSWORD}@{HOST}"

opts = webdriver.ChromeOptions()
opts.add_argument(f"--proxy-server={sticky_proxy()}")  # creds ignored by Chrome, see below
driver = webdriver.Chrome(options=opts)

One nuance that trips people up in browser automation: a per-request rotating endpoint will send the HTML, the JS bundle and the XHR calls of a single page load out through different IPs. Plenty of sites treat that as an obvious anomaly, and any session cookie issued mid-page breaks. For Selenium, prefer a sticky session for the lifetime of a page or a task, then rotate the token. Per-request rotation belongs to HTTP clients — the pattern we cover in rotating proxies in plain Python.

Geo consistency matters too. If you pin a US exit through residential IPs, keep the browser's timezone, Accept-Language and locale aligned with it. An IP in Ohio with a Europe/Berlin timezone is a cheaper signal to detect than the IP itself.

Method 3: Selenium proxy authentication (Chrome and Firefox)

Chrome discards the user:pass@ part of --proxy-server, so authenticated proxies need one of four approaches.

IP allowlisting

If your provider supports whitelisting the machine's public IP, use it. No credentials, no extension, no interception — the most reliable option for long-running scrapers on fixed infrastructure.

A tiny extension that answers the auth prompt

Chrome exposes chrome.webRequest.onAuthRequired, so a five-line extension can supply credentials. Generate it at runtime:

import json, pathlib, tempfile
from selenium import webdriver

def auth_extension(user, password):
    d = pathlib.Path(tempfile.mkdtemp())
    (d / "manifest.json").write_text(json.dumps({
        "manifest_version": 3,
        "name": "proxy-auth", "version": "1.0",
        "permissions": ["webRequest", "webRequestAuthProvider"],
        "host_permissions": ["<all_urls>"],
        "background": {"service_worker": "bg.js"},
    }))
    (d / "bg.js").write_text(
        "chrome.webRequest.onAuthRequired.addListener("
        f"() => ({{authCredentials: {{username: '{user}', password: '{password}'}}}}),"
        "{urls: ['<all_urls>']}, ['blocking']);"
    )
    return str(d)

opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://gateway.example.net:8000")
opts.add_argument(f"--load-extension={auth_extension('user', 'pass')}")
driver = webdriver.Chrome(options=opts)

Rebuild the extension directory whenever you rotate credentials or session tokens, and remember that extensions require the newer headless mode (--headless=new).

selenium-wire and its maintained alternatives

selenium-wire wraps the driver in a local mitm proxy, which is why it can both authenticate upstream and swap driver.proxy between navigations without relaunching the browser. It is also archived — the repository has been read-only since January 2024 (wkeeling/selenium-wire) — and it pins older blinker and pyOpenSSL versions to work on current Pythons. Use it for short-lived jobs, not for a pipeline you must maintain for a year. SeleniumBase accepts --proxy=user:pass@host:port directly and is actively maintained; most rotating proxy Python GitHub wrappers you will find are thin layers over one of these two ideas.

Firefox

For Selenium Firefox proxy authentication in Python, set the proxy through profile preferences (network.proxy.type = 1, network.proxy.http, network.proxy.ssl, network.proxy.http_port) or options.proxy. Firefox then raises a basic-auth dialog that Selenium cannot always dismiss reliably, so the practical fixes are an add-on that handles onAuthRequired, or a local credential-injecting forwarder that the browser talks to without auth. The same trick answers how to set proxy in Selenium WebDriver Java: build a org.openqa.selenium.Proxy, set setHttpProxy(), attach it to the options — and handle credentials outside the driver.

Which method to choose

ApproachHow the IP changesAuth supportCost per rotationBest for
New driver per proxyProcess relaunchExtension or allowlist1-3s + cold cacheCoarse rotation, one identity per task
Rotating gateway endpointUpstream, per request or per session tokenCredentials in username (needs extension on Chrome)Near zeroMost scraping and Selenium IP rotation at scale
Local interception (selenium-wire)driver.proxy reassigned mid-sessionNativeNear zero, plus mitm certificate setupAd-hoc jobs, debugging, request inspection
Scraping APIHandled by the providerBearer keyNoneContent extraction where you do not need clicks

Rotation cadence and retry logic that actually helps

  1. Rotate on outcome, not on a timer. A 403, a challenge page or a redirect to a bot-check URL is a rotation signal. A successful 200 is not.
  2. Pin one IP per logical session. Login, cart and pagination flows need the same exit IP; a new token per task, not per request.
  3. Cap retries at three per URL and change something between attempts — IP, then locale, then rendering strategy. Repeating the identical request is spend without information.
  4. Quarantine bad exits. If you manage a static list, track failures per proxy and drop it for an hour after two consecutive timeouts.
  5. Block heavy resources. Images, media and fonts are usually most of the bytes. Blocking them through CDP (Network.setBlockedURLs) or --blink-settings=imagesEnabled=false is the single biggest cost win in browser scraping.
  6. Log the attempt trail. Proxy used, status, bytes, wall time. Without it you cannot tell an IP problem from a fingerprint problem — and they need opposite fixes.

The honest cost math: rotating browsers vs a scraping API

Residential bandwidth is metered, and a headless browser pulls everything a real one does. Take a JS-heavy page that transfers roughly 2.5 MB of assets. At $0.80/GB that is about $0.002 per page in bandwidth alone, before your compute — and blocked attempts consume the same bytes, so a 20% block rate pushes the effective figure closer to $0.0019. Strip images and fonts down to ~400 KB and the same page costs about $0.00023.

Now compare with fetching the same page through the Web Scraping API: $0.0002 per page for HTML/Markdown, $0.001 with JS rendering, proxies and retries included, and failures cost nothing under the pay-per-success rule.

curl https://api.quanticdata.io/v1/scrape \
  -H "Authorization: Bearer $QD_API_KEY" \
  -d '{ "url": "https://example.com", "render": true }'

{ "success": true,
  "data": { "markdown": "# Example Domain\n..." },
  "usage": { "cost_usd": 0.001 },
  "retries": [ { "attempt": 1, "outcome": "ok" } ] }

The point is not that Selenium is wrong — it is that rotation is only cheap when it is coupled to a cost model. Keep the browser for flows that genuinely need clicks, form state or authenticated navigation. Move plain content extraction to a per-success API and your proxy bill stops scaling with retries.

When an agent tool replaces the rotation code entirely

If the consumer of the data is an LLM or an agent, the whole proxy layer can move behind a tool call. Our MCP server exposes search, scrape, map, crawl, batch and SEO audit as tools to Claude, Cursor, Windsurf and VS Code, with residential proxies underneath and one JSON envelope out; the same primitives are available as REST through the Web Data API for AI. Agents get structured pages without owning a proxy pool, a Chrome install or a rotation policy.

For the cases where a real browser is unavoidable — multi-step checkouts, dashboards behind a login — the trade-off is worth stating plainly: you are paying for a process, not a request. Rotate identities at task boundaries, keep the fingerprint consistent with the exit IP, and instrument every attempt. That combination fixes far more blocks than adding proxies to a list. If you are new to the wider picture, our primer on browser automation covers where Selenium fits against CDP-native drivers.

Sources & further reading

FAQ

Quick answers on how to rotate proxy in selenium python.

Something else? Ask us →

How do I rotate proxies in Selenium Python?

Either relaunch the driver with a different --proxy-server value for each identity, or point every session at a rotating proxy endpoint and change the session token to get a new exit IP. selenium-wire is the third option: it lets you reassign driver.proxy between page loads without restarting the browser.

Can Selenium change the proxy without restarting the browser?

Not with vanilla Selenium. Chrome reads the proxy at process launch and offers no supported API to change it mid-session. The workarounds are a local intercepting proxy such as selenium-wire, or a gateway endpoint whose upstream IP changes while the browser's proxy string stays identical.

Does Selenium support proxy authentication?

Not natively for Chrome — credentials embedded in --proxy-server are ignored. Use IP allowlisting where your provider supports it, a small MV3 extension answering chrome.webRequest.onAuthRequired, or a wrapper such as selenium-wire or SeleniumBase that handles user:pass@host:port for you.

How do I set a proxy in Selenium WebDriver Java?

Create an org.openqa.selenium.Proxy, call setHttpProxy("host:port") and setSslProxy(), then attach it via ChromeOptions.setProxy(proxy). Authentication is not handled by the driver in Java either, so pair it with IP allowlisting or an auth extension exactly as you would in Python.

How do I handle Firefox proxy authentication in Selenium Python?

Set network.proxy.type, network.proxy.http and network.proxy.ssl in a Firefox profile or use options.proxy. Firefox then prompts for credentials, which Selenium cannot reliably dismiss, so add an add-on that handles the auth event or run a local forwarder that injects the credentials upstream.

How often should I rotate IPs when scraping with Selenium?

Rotate on failure signals rather than on a fixed interval: blocks, challenge pages and unexpected redirects. Keep one IP for the whole of a logical session so cookies survive, and rotate at task boundaries. Aggressive per-request rotation inside a browser page load often looks more suspicious than a stable IP.

Rotate IPs without babysitting a proxy list

Point Selenium at one rotating endpoint from $0.50/GB with sticky sessions up to 120 minutes, or skip the browser entirely and fetch pages through the Web Scraping API from $0.0002 each — pay per success, so failed calls cost nothing. $2 of free usage every month, no card required.

Related reading