A Selenium proxy for Chrome is one argument, --proxy-server=http://HOST:PORT, and for Firefox a handful of network.proxy.* preferences. The catch is authentication: Chrome will not take a username and password in that flag, and WebDriver cannot type into the browser's proxy sign-in prompt. The dependable fix is an IP allowlist; the in-code fix is a ten-line extension.
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://HOST:PORT")
driver = webdriver.Chrome(options=options)
The snippets on this page ran on 2026-09-29 with Selenium 4.49 for Python, driving Chrome 154 and Firefox, against local authenticating HTTP proxies and SOCKS5 proxies.
Before you start: copy your proxy details
- Open your order in the dashboard at https://app.proxyhive.io.
- Copy HOST and PORT, plus USERNAME and PASSWORD if you keep credential auth. Each ISP or datacenter IP is its own endpoint, with separate HTTP, HTTPS and SOCKS5 ports on the order.
- Decide how you will authenticate (next section) before writing code, because it decides how much code there is.
export PROXY_HOST=HOST PROXY_PORT=PORT
Browser sessions that log in are where static ISP proxies fit: one address for the whole term, sold from a single IP.
Selenium proxy authentication: why it breaks, and the options
What we saw in Chrome:
| Approach | Result |
|---|---|
--proxy-server=http://HOST:PORT, proxy needs no credentials | Works |
| Same, proxy wants credentials | Headless: an empty page, no exception. Headed: a sign-in dialog WebDriver cannot reach |
--proxy-server=http://USER:PASS@HOST:PORT | "This site can't be reached" |
Selenium 4 BiDi driver.network.add_auth_handler() | Timed out in Chrome in our runs; worked in Firefox |
So, in order of preference:
- IP allowlist (recommended). Switch the IP to allowlist authentication on the order in the dashboard and add the public IP your Selenium machines use. No credentials, no extension, nothing to leak. Best for servers and CI runners with fixed egress.
- A small auth extension that answers the proxy's challenge. Works anywhere, including on laptops whose IP changes.
- selenium-wire: skip it. The project's repository says it is no longer maintained and was archived on 3 January 2024. It also worked by running its own man-in-the-middle proxy, which is a heavy way to add one header.
Selenium Chrome proxy with an IP allowlist
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server=http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}")
options.add_argument("--headless=new")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://api.ipify.org?format=json")
print(driver.find_element(By.TAG_NAME, "body").text)
finally:
driver.quit()
That is the entire integration. Selenium Manager fetches a matching chromedriver on first run.
Selenium proxy username and password: a Manifest V3 extension
Make a folder proxy-auth-extension with two files. manifest.json:
{
"manifest_version": 3,
"name": "Proxy auth",
"version": "1.0",
"permissions": ["webRequest", "webRequestAuthProvider"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "background.js" }
}
background.js, with your credentials written in when you build the folder (generate it from environment variables in CI rather than committing it):
const USERNAME = 'USERNAME';
const PASSWORD = 'PASSWORD';
chrome.webRequest.onAuthRequired.addListener(
(details) => (details.isProxy ? { authCredentials: { username: USERNAME, password: PASSWORD } } : {}),
{ urls: ['<all_urls>'] },
['blocking'],
);
Load it through WebDriver BiDi:
options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server=http://{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}")
options.enable_bidi = True
options.enable_webextensions = True
driver = webdriver.Chrome(options=options)
driver.webextension.install(path="proxy-auth-extension")
driver.get("https://api.ipify.org?format=json")
Why not --load-extension, which most older guides use? Chrome 137 removed that flag from branded Chrome. We confirmed it: Chrome 154 silently ignored it, while Chrome for Testing 154 still loaded the extension. webextension.install() worked on both.
Selenium Firefox proxy
Firefox takes the proxy as preferences, and Selenium 4's BiDi auth handler does work there:
options = webdriver.FirefoxOptions()
options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.http", os.environ["PROXY_HOST"])
options.set_preference("network.proxy.http_port", int(os.environ["PROXY_PORT"]))
options.set_preference("network.proxy.ssl", os.environ["PROXY_HOST"])
options.set_preference("network.proxy.ssl_port", int(os.environ["PROXY_PORT"]))
options.enable_bidi = True
driver = webdriver.Firefox(options=options)
driver.network.add_auth_handler(os.environ["PROXY_USER"], os.environ["PROXY_PASS"])
driver.get("https://api.ipify.org?format=json")
With an allowlisted IP, drop enable_bidi and the handler. network.proxy.ssl is the proxy for HTTPS sites; it still points at the HTTP port, because HTTPS pages travel through a CONNECT tunnel.
Selenium SOCKS5 proxy
Neither Chrome nor Firefox's preferences send SOCKS5 credentials, so SOCKS5 needs an allowlisted IP. Chrome: --proxy-server=socks5://HOST:SOCKS5_PORT. Firefox:
options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.socks", os.environ["PROXY_HOST"])
options.set_preference("network.proxy.socks_port", SOCKS5_PORT)
options.set_preference("network.proxy.socks_version", 5)
options.set_preference("network.proxy.socks_remote_dns", True)
socks_remote_dns sends hostnames to the proxy instead of resolving them locally; our SOCKS server received api.ipify.org, not an address. More on the trade-off in HTTP vs SOCKS5 proxies.
Verify the exit IP and rotate across IPs
Every snippet ends on https://api.ipify.org?format=json; compare the result with the IP on your order. The proxy is fixed per driver, so rotation in Selenium means one driver per IP: start a driver with the next endpoint, do the session's work, quit. Reusing one IP per session is what a site expects from a real browser anyway. Rotating vs static proxies covers when that is enough.
Timeouts and common errors
driver.set_page_load_timeout(45) stops a dead proxy from hanging a test forever. Retry the get() a couple of times before failing the run.
| Symptom | Cause | Fix |
|---|---|---|
| Blank page, no error (headless Chrome) | Proxy wants credentials Chrome cannot send | Allowlist, or the extension |
| "This site can't be reached" | Credentials inside --proxy-server | Host and port only |
| Extension silently ignored, blank page | --load-extension on branded Chrome 137+ | Use webextension.install() |
ERR_PROXY_CONNECTION_FAILED error page | Wrong host or port | Re-copy the port that matches the scheme |
Status codes returned by the target are explained in proxy error codes.
Next steps
- Playwright takes proxy credentials natively and is worth a look if auth is your main pain.
- Puppeteer sits in between: credentials via
page.authenticate(). - Connection reference: the docs.