Skip to content

How to Use Proxies in Selenium (Chrome and Firefox)

How to use proxies in Selenium for Chrome and Firefox: set the proxy on the driver, log in to an authenticated proxy, rotate IPs, and go headless.

HProxy TeamJuly 3, 2026Updated October 14, 20268 min read

How to Use Proxies in Selenium (Chrome and Firefox)

Almost every Selenium tutorial routes the browser straight out of your own IP, which is fine right up until the target starts counting requests per address. The moment you are scraping at any real scale, testing geo-specific behavior, or driving more than one account, you need proxies in Selenium, and Selenium's proxy handling is quirkier than most people expect.

The short version: a Selenium proxy is set once, when you build the driver, and the way you set it differs between Chrome and Firefox. The part that trips everyone up is authentication, because the flag Chrome uses to accept a proxy quietly refuses a username and password. This guide covers both browsers, the auth fix that actually works, rotation across sessions, the headless caveats, and where a proxy stops helping you.

How do you set a proxy in Selenium?

Set it when you create the driver, not after. In Chrome, add a launch flag: options.add_argument("--proxy-server=http://203.0.113.10:8080") and pass those options to webdriver.Chrome. In Firefox, set the network.proxy.* preferences on the options object instead. Either way the proxy applies to that one browser, for the life of that session.

Set a proxy in Chrome with Selenium

Chrome takes its proxy from a command-line flag, and Selenium lets you add that flag through ChromeOptions. This is the same --proxy-server mechanism from our Chrome proxy guide, driven from code instead of the terminal.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--proxy-server=http://203.0.113.10:8080")

driver = webdriver.Chrome(options=options)
driver.get("https://httpbin.org/ip")   # shows the exit IP the site sees
print(driver.page_source)
driver.quit()

Selenium 4 finds and manages the matching driver binary for you, so there is nothing else to download. For a SOCKS5 proxy, just change the scheme:

options.add_argument("--proxy-server=socks5://198.51.100.42:1080")

One flag applies to the whole browser, so every tab and every request in that Chrome goes through the proxy. If you need different tabs on different proxies, you run separate drivers, which is the rotation pattern further down.

Selenium also exposes a browser-agnostic way to set this: build a Proxy object with ProxyType.MANUAL and set httpProxy, sslProxy, or socksProxy on it, then assign it to options.proxy. The --proxy-server launch flag shown here is the simpler route on Chromium and the one most code uses, but the Proxy capability is the documented, cross-browser equivalent if you prefer to stay off command-line flags.

Set a proxy in Firefox with Selenium

Firefox reads its proxy from its own preferences instead of a launch flag, and Selenium sets those with set_preference on the Firefox options. network.proxy.type = 1 means manual configuration, and then you point each protocol at your proxy.

from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.set_preference("network.proxy.type", 1)             # 1 = manual
options.set_preference("network.proxy.http", "203.0.113.10")
options.set_preference("network.proxy.http_port", 8080)
options.set_preference("network.proxy.ssl", "203.0.113.10")    # HTTPS traffic
options.set_preference("network.proxy.ssl_port", 8080)

driver = webdriver.Firefox(options=options)
driver.get("https://httpbin.org/ip")
driver.quit()

For a SOCKS5 proxy, set the SOCKS preferences instead. The socks_remote_dns line is worth keeping: it resolves DNS through the proxy rather than on your machine, which closes one of the leaks that gives a proxied browser away.

options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.socks", "198.51.100.42")
options.set_preference("network.proxy.socks_port", 1080)
options.set_preference("network.proxy.socks_version", 5)
options.set_preference("network.proxy.socks_remote_dns", True)   # DNS via the proxy

The older FirefoxProfile object did the same job and you will still see it in old code, but setting preferences on the options object is the current way and the one to copy.

The authentication problem, and the two ways that worked

Most paid proxies authenticate with a username and password, and Chrome's --proxy-server flag has no place for them. Per Chromium's documentation, Chrome "will not use any credentials embedded in the proxy settings". In our test, a login written into the flag made Chrome refuse the proxy with ERR_NO_SUPPORTED_PROXIES, and the proxy saw no request at all. With the login left out, the proxy answered every request with 407, and headless Chrome showed no page.

Two ways worked in our tests.

The zero-code path: IP authentication

If your provider lets you allowlist the IP of the machine running Selenium, do that. The proxy then trusts your server by its address, no username or password rides in the flag at all, and the plain examples above work as written. This is the simplest setup and the one we recommend whenever the machine has a stable IP.

The code path: a two-file extension

When you have to send a username and password (rotating residential gateways almost always work this way), a small Chrome extension can answer the proxy's login for you. This is the extension from our Selenium scraping tutorial. It writes two files, a manifest and a script, and Chrome loads them at start. Set PROXY to the proxy's host and port, and PROXY_USER and PROXY_PASS to its login:

import json
import os
import pathlib

from selenium import webdriver

extension = pathlib.Path("proxy-login").resolve()
extension.mkdir(exist_ok=True)
(extension / "manifest.json").write_text(json.dumps({
    "manifest_version": 3, "name": "Proxy login", "version": "1.0",
    "permissions": ["webRequest", "webRequestAuthProvider"], "host_permissions": ["<all_urls>"],
    "background": {"service_worker": "background.js"},
}))
(extension / "background.js").write_text("""
chrome.webRequest.onAuthRequired.addListener(
  (details, callback) => callback(details.isProxy
    ? {authCredentials: {username: %s, password: %s}} : {}),
  {urls: ["<all_urls>"]}, ["asyncBlocking"]);
""" % (json.dumps(os.environ["PROXY_USER"]), json.dumps(os.environ["PROXY_PASS"])))

login_options = webdriver.ChromeOptions()
login_options.add_argument("--headless=new")
login_options.add_argument("--proxy-server=http://" + os.environ["PROXY"])
login_options.add_argument("--load-extension=" + str(extension))
login_driver = webdriver.Chrome(options=login_options)
login_driver.get("https://hproxy.com/blog/web-scraping-with-python")
if not login_driver.title:  # the extension was not ready for the first page
    login_driver.get("https://hproxy.com/blog/web-scraping-with-python")
print(login_driver.title)
login_driver.quit()

The script answers only when details.isProxy is true. Chrome's reference describes it as "True for Proxy-Authenticate, false for WWW-Authenticate", which keeps your proxy password away from a site's own login prompt. Right after start the extension is sometimes not ready, so the code asks for the first page a second time. On Chrome for Testing 155, two of our three starts needed that second try.

Chrome removed --load-extension from Google's own Chrome builds in version 137. The change "only applies to Chrome branded builds", the Chrome team wrote, and the flag keeps working in Chromium and Chrome for Testing. Our runs used Chrome for Testing 143 and 155. The extension is a Chrome one; for Firefox, allowlist the IP.

Two other ways failed in our tests. selenium-wire, the tool older guides use for this, was archived on January 3, 2024, and on Python 3.12 it failed three ways in a row: pkg_resources was missing, then blinker._saferef, then a certificate error left no page. Selenium's own handler, driver.network.add_auth_handler() in Selenium 4.51.0, timed out on Chrome for Testing 143 and 155, and the proxy saw only requests without a login. Playwright takes proxy credentials in its own API, if you are open to switching driver.

How the extension answers a proxy's login
  1. Chrome

    CONNECT without a login

  2. Proxy

    407: login needed

  3. Extension

    answers with the login

  4. Proxy

    200: tunnel open

Source: HProxy, Chrome for Testing 155 against our own test proxy, October 12, 2026 · hproxy.comProxy.

Rotating proxies across sessions

A Selenium proxy is fixed the instant the driver starts. You cannot swap --proxy-server on a browser that is already open, so the only way to rotate with plain Selenium is one driver per proxy: build it, use it, quit it, move to the next.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

proxies = [
    "http://203.0.113.10:8080",
    "http://203.0.113.11:8080",
    "http://198.51.100.42:8080",
]

for proxy in proxies:
    options = Options()
    options.add_argument(f"--proxy-server={proxy}")
    driver = webdriver.Chrome(options=options)
    try:
        driver.get("https://httpbin.org/ip")
        # ... your work for this identity ...
    finally:
        driver.quit()   # always quit, or you leak browsers

A fresh driver per identity also drops the cookies and local storage of the previous run, so the new IP starts without the old session's history.

For most people the simpler answer is to not rotate in code at all. Point Selenium at a single rotating residential gateway, one host and port, and let the pool hand you a different exit on each connection. Your script stays a plain one-proxy script and the rotation happens upstream. It is also the pattern that behaves best, because how you rotate matters as much as whether you rotate, which we get into in avoiding IP bans while scraping and the broader proxies for web scraping guide.

Headless caveats

Everything above works the same with headless Chrome, which you turn on with the current flag:

options.add_argument("--headless=new")
options.add_argument("--proxy-server=http://203.0.113.10:8080")

Two things change once there is no visible window. First, nobody can answer a login prompt, so solve the login before the run: use IP authentication or the extension above. In our headless test, a proxy that asked for a login and got none left Chrome with no page at all. Second, a headless run is still an automated run, and the page can tell, as the next section explains.

What a site can see

Through a proxy, a site sees a different address, and nothing else about the browser changes. Automation itself is visible by design: Selenium-driven Chrome sets navigator.webdriver to true, a property the W3C WebDriver standard defines so a page can tell it is being automated. Whether a site accepts automated browsers is that site's decision, and its terms and robots.txt apply whatever address a request comes from. We show no way to mask automation or to slip past a site's defenses.

Where to point Selenium

Our residential proxies put a rotating gateway behind one host and port, which suits Selenium: one proxy in your options and no rotation code to maintain. If you can authenticate by IP, allowlist your machine and skip the extension entirely.

Before a run, our proxy checker shows a proxy's exit IP, anonymity grade and speed from your own connection. For suites that provision their own IPs before a run, the REST endpoints cover ordering and plan generation with no dashboard involved. Use addresses you are allowed to use, since each site's own rules apply whatever the address.

Sources

Frequently asked questions

How do I set a proxy in Selenium with Python?

Set it when you build the driver. For Chrome, add a --proxy-server launch argument through ChromeOptions and pass those options to webdriver.Chrome. For Firefox, set network.proxy.type to 1 plus the network.proxy.http and http_port preferences on the options object. The proxy then applies to that browser for the whole session.

Why does my proxy username and password not work in Selenium?

Because Chrome's --proxy-server flag takes no username or password. Chromium's documentation says Chrome will not use credentials embedded in the proxy settings, and in our test a login written into the flag made Chrome refuse the proxy with ERR_NO_SUPPORTED_PROXIES. Allowlist your machine's IP with the provider, or load a small Chrome extension that answers the proxy's login request.

How do I use an authenticated proxy in Selenium?

Allow your machine's IP at the proxy, so it asks for no login, or load a two-file Chrome extension that answers the proxy's login request. The extension worked on Chrome for Testing 143 and 155 in our tests, with one retry for the first page. selenium-wire, the older answer, is archived and failed to run on Python 3.12 in our test, and the login handler of Selenium 4.51 timed out on both Chrome versions.

Can I rotate proxies within a single Selenium session?

Not with plain Selenium. The --proxy-server flag is read once at startup and cannot change on a running browser, so rotation means creating a new driver per proxy, or pointing Selenium at one rotating residential gateway and letting the pool change the exit IP for you on each connection.

Does a proxy stop Selenium from being detected?

No. A proxy changes the address a site sees. Automation is visible by design: Selenium-driven Chrome sets navigator.webdriver to true, a property the W3C WebDriver standard defines so a page can tell it is automated. Whether a site accepts automated browsers is that site's decision, and this page gives no way to hide automation.

Get proxies that are alive right now

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup

HProxy.

Honest guides and comparisons on proxies, scraping and staying unblocked, from the team that runs the network.

RSS feed