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

  1. Open your order in the dashboard at https://app.proxyhive.io.
  2. 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.
  3. 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:

ApproachResult
--proxy-server=http://HOST:PORT, proxy needs no credentialsWorks
Same, proxy wants credentialsHeadless: 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:

  1. 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.
  2. A small auth extension that answers the proxy's challenge. Works anywhere, including on laptops whose IP changes.
  3. 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.

SymptomCauseFix
Blank page, no error (headless Chrome)Proxy wants credentials Chrome cannot sendAllowlist, or the extension
"This site can't be reached"Credentials inside --proxy-serverHost and port only
Extension silently ignored, blank page--load-extension on branded Chrome 137+Use webextension.install()
ERR_PROXY_CONNECTION_FAILED error pageWrong host or portRe-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.