Tutorials

How to Use a Proxy With Selenium (Python)

To route Selenium through a proxy in Python, you pass a proxy URL to the browser's launch options — for Chrome that's --proxy-server=scheme://host:port on ChromeOptions. That one line works for proxies with no authentication. The catch is that username/password authentication can't be passed through --proxy-server, and a commercial endpoint like ours always asks for credentials. This guide covers the honest options — a local forwarder, selenium-wire, or an extension — plus remote DNS and rotation. Placeholders are HOST:PORT, USERNAME and PASSWORD; the real values are on your dashboard.

The simple case: an unauthenticated proxy

If a proxy needs no credentials, point Chrome at it directly:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://127.0.0.1:8899")
# SOCKS5 works too (Chrome resolves DNS through the proxy):
# options.add_argument("--proxy-server=socks5://127.0.0.1:1080")

driver = webdriver.Chrome(options=options)
driver.get("https://api.ipify.org?format=json")
print(driver.page_source)
driver.quit()

The printed IP should be your proxy's egress, not your own. For Firefox, set the proxy through preferences instead of a flag (options.set_preference("network.proxy.type", 1), then network.proxy.http/network.proxy.ssl and their _port keys). Firefox has the same limitation: no way to preload proxy credentials.

Authenticated proxies: three ways that work

When the proxy asks for credentials, Chrome shows a native auth dialog that WebDriver can't fill, and the page just hangs. Your options:

1. A local forwarder (recommended). Run a small proxy on your machine that accepts Chrome without auth and adds the credentials upstream. Chrome keeps its own TLS stack, so the target sees a real Chrome fingerprint. With pproxy:

# pip install pproxy   (listen on localhost only)
pproxy -l http://127.0.0.1:8899 -r "http://HOST:PORT#USERNAME:PASSWORD"

Then use the unauthenticated snippet above with --proxy-server=http://127.0.0.1:8899. Any forwarder that supports an authenticated upstream works the same way (for example gost -L http://127.0.0.1:8899 -F http://USERNAME:PASSWORD@HOST:PORT). Bind it to 127.0.0.1, never 0.0.0.0, or anyone who can reach the port spends your traffic.

2. selenium-wire. selenium-wire wraps Selenium with a built-in relay that injects the credentials. Two honest caveats: the project is archived and no longer maintained (current installs typically need blinker<1.8 pinned), and it decrypts and re-encrypts HTTPS itself, so the target sees selenium-wire's TLS fingerprint rather than Chrome's — a real problem on bot-protected sites.

# pip install selenium-wire "blinker<1.8"
from seleniumwire import webdriver

proxy = "http://USERNAME:PASSWORD@HOST:PORT"
seleniumwire_options = {
    "proxy": {
        "http": proxy,
        "https": proxy,
        "no_proxy": "localhost,127.0.0.1",
    }
}

driver = webdriver.Chrome(seleniumwire_options=seleniumwire_options)
driver.get("https://api.ipify.org?format=json")
print(driver.page_source)
driver.quit()

3. An auth extension. A small extension that answers the proxy's challenge through webRequest.onAuthRequired also works, but recent Google Chrome builds no longer load unpacked extensions from the command line (Chrome for Testing still does), so it is the most fragile of the three.

Don't leak your DNS

A common mistake is resolving the target hostname on your own machine and only tunneling the connection. That leaks which sites you're visiting and can expose your real location through DNS. With an HTTP proxy — including the local forwarder above — this takes care of itself: the hostname travels inside the CONNECT request, so the proxy does the lookup. If your endpoint also speaks SOCKS5 — o2co's does, on the same port as HTTP — selenium-wire accepts socks5h://USERNAME:PASSWORD@HOST:PORT too; the trailing h is what pushes resolution to the proxy, while plain socks5:// resolves locally. The same principle applies everywhere — see our SOCKS5 setup guide for the details.

Rotating IPs per session

Selenium drives a full browser, so each session is heavy — you won't rotate per request the way you might with Python requests. Instead, treat each browser session as one identity: a fresh driver (fresh cookies) per independent task. The country rides on the username as a lowercase suffix, so with a forwarder you run one local port per country:

# German egress on local port 8801
pproxy -l http://127.0.0.1:8801 -r "http://HOST:PORT#USERNAME-country-de:PASSWORD"

# then, in Python
options.add_argument("--proxy-server=http://127.0.0.1:8801")

A new driver doesn't guarantee a new IP on our endpoint: with the plain username the gateway picks the exit per source machine, keeps it while traffic keeps flowing, and moves on after about a minute idle. Don't reach for the -rotate suffix here — it gives every new connection a different exit, which splits one page's sub-requests across addresses; it is meant for crawlers fetching independent pages. If one IP must persist across a multi-step flow (login → navigate → submit), ask for sticky mode on your account, which holds a machine's exit for a fixed window — the trade-offs are in sticky vs rotating sessions.

Headless detection — a real concern

A proxy changes your IP, but it does nothing about the dozens of other signals a real browser leaks. Default headless Chrome advertises itself through the navigator.webdriver flag, a distinctive user agent, and missing or inconsistent fingerprint properties. Anti-bot systems weigh all of these together with IP reputation, so a clean residential IP behind an obviously-automated browser still gets flagged. We break down what sites actually check — and what helps — in how websites detect proxies.

Practical mitigations when running Selenium at scale:

  • Run a real (non-headless) browser, or new headless mode, with a realistic window size.
  • Use a credible, current user agent that matches the browser version you're actually running.
  • Throttle and randomize timing — humans don't click instantly or at fixed intervals.
  • Match your proxy's geography to a plausible locale, timezone, and language for that IP.

Picking the right proxy type

For browser automation against protected targets, residential or ISP IPs usually outperform plain datacenter ranges because they carry more trust. For internal tools, staging sites, or APIs that don't fingerprint aggressively, datacenter IPs are faster and cheaper. The broader strategy — proxy type, rotation, and pacing — is covered in proxies for web scraping.

Responsible use

Automating a browser through a proxy is a legitimate technique for testing, monitoring, and collecting public data, but it isn't a license to ignore the rules. Respect a site's terms of service and robots.txt, don't collect personal data without a lawful basis, and rate-limit yourself so you're not degrading the service for everyone else. Good automation is polite automation — it also gets blocked far less often. If you can choose your tooling, Playwright takes proxy credentials natively and skips the forwarder entirely. Get 1 GB free to test the setup, or see pricing.