Setting up a Selenium proxy looks like a one-line job until the proxy asks for a username and password. Chrome has no command-line flag for proxy credentials, the popular selenium-wire package was archived in January 2024, and since Chrome 137 the old trick of loading a small authentication extension no longer works in regular Chrome. This guide shows what does work today with ChromeDriver and Python: the --proxy-server flag, Selenium’s own authentication handler, the extension method in Chrome for Testing, Firefox, rotation, and fixes for the common errors, with code you can copy.
The short version
Point Chrome at the proxy with --proxy-server=http://, turn on WebDriver BiDi with options.enable_bidi = True, and register your credentials with driver.network. before the first page load. Use an HTTP proxy endpoint for authenticated sessions, because Chrome cannot send a username and password to a SOCKS5 proxy, and start a new driver when you need a clean IP.
How a Selenium Proxy Works with ChromeDriver
The browser connects through the proxy, not the driverSelenium does not send web traffic itself. Your Python script talks to ChromeDriver, ChromeDriver starts and controls Chrome, and Chrome loads the pages. So a proxy for Selenium is really a proxy for the browser: every page, script, image and API call the browser makes goes through it, while the local connection between your script and ChromeDriver stays direct. That is why the proxy settings live in the browser options you hand to the driver, and why anything the browser supports, or does not support, applies to your automation too.
Three pieces of information describe the proxy: the host and port of the gateway, the protocol (HTTP or SOCKS5), and the credentials. With ProxyEmpire the gateway is v2.proxyempire.io:5000, the credentials come from your dashboard, and the username can also carry location and session options, so one endpoint gives you any country or a sticky IP without changing code. The table below shows which part is easy and which is not.
| Need | Chrome option | Works in Selenium today? |
|---|---|---|
| Proxy host and port | --proxy-server or the proxy capability | Yes, in every browser build |
| Username and password (HTTP proxy) | No flag exists | Yes, with the BiDi auth handler or an extension in Chrome for Testing |
| Username and password (SOCKS5 proxy) | No flag exists | No, Chrome does not support SOCKS5 authentication |
| Different proxy per browser | One driver per proxy | Yes, start separate drivers |
If you only need an unauthenticated proxy, the next section is all you need. If your provider asks for a login, which almost every commercial residential or mobile proxy does, read the authentication section as well. New to automated browsers in general? Our headless browser guide compares Selenium with Playwright and Puppeteer.
Set a Chrome Proxy in Selenium Without a Password
One argument or one capabilityThe quickest way to set a ChromeDriver proxy is Chrome’s own command-line switch. It takes a scheme, host and port, and applies to every request the browser makes.
from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://v2.proxyempire.io:5000")
options.add_argument("--headless") # optional
driver = webdriver.Chrome(options=options)
driver.get("https://api.ipify.org?format=json")
print(driver.find_element(By.TAG_NAME, "body").text)
driver.quit()
Selenium also has a browser-neutral way to say the same thing: the proxy capability defined by the WebDriver standard. It is useful when the same test suite runs on Chrome, Edge and Firefox, or on a Selenium Grid where you would rather not pass browser-specific flags.
from selenium import webdriver
from selenium.webdriver.common.proxy import Proxy, ProxyType
proxy = Proxy({
"proxyType": ProxyType.MANUAL,
"httpProxy": "v2.proxyempire.io:5000",
"sslProxy": "v2.proxyempire.io:5000",
})
options = webdriver.ChromeOptions()
options.proxy = proxy
driver = webdriver.Chrome(options=options)
Two details catch people out. First, do not put a username and password in the flag, as in http://user:pass@host:port: Chrome ignores the credentials and then shows a sign-in prompt, which a script cannot answer. Second, since Selenium 4.6 you do not need to download ChromeDriver by hand; Selenium Manager finds or fetches the right driver for the installed browser. That download uses your normal connection, not the proxy, so it does not count against your proxy traffic.
If the proxy recognises your machine’s IP address instead of a password, the code above is complete. For everything else, keep reading. The same flag works for a normal Chrome window too; our guide to Chrome proxy settings covers manual setup on Windows and macOS.
Selenium Proxy Authentication with the BiDi Auth Handler
The supported way to send a username and passwordRecent versions of Selenium can answer the browser’s authentication challenge for you. The feature runs on WebDriver BiDi, the newer two-way protocol that lets your script listen to browser events while the page loads. When the proxy replies with HTTP 407 and asks for credentials, the browser raises an “auth required” event, and a handler registered by your script supplies the username and password. No extension, no man-in-the-middle proxy and no sign-in prompt.
from selenium import webdriver
from selenium.webdriver.common.by import By
PROXY = "http://v2.proxyempire.io:5000"
USERNAME = "YOUR_USERNAME" # from the ProxyEmpire dashboard
PASSWORD = "YOUR_PASSWORD"
options = webdriver.ChromeOptions()
options.enable_bidi = True
options.add_argument(f"--proxy-server={PROXY}")
driver = webdriver.Chrome(options=options)
handler_id = driver.network.add_auth_handler(USERNAME, PASSWORD)
try:
driver.get("https://api.ipify.org?format=json")
print("Exit IP:", driver.find_element(By.TAG_NAME, "body").text)
finally:
driver.network.remove_auth_handler(handler_id)
driver.quit()
Follow a few rules and it behaves predictably:
- Upgrade Selenium first. The Python
add_auth_handlermethod is part of Selenium’s BiDi network module; runpip install -U seleniumand use a current Chrome. - Enable BiDi before creating the driver.
options.enable_bidi = Truehas to be set on the options you pass in; turning it on later does nothing. - Register the handler before the first
driver.get(). The browser asks for credentials on its first proxied request; if nobody answers, the page fails. - Use the HTTP endpoint. The handler answers HTTP proxy challenges. Chrome never asks for SOCKS5 credentials, so an authenticated SOCKS5 proxy cannot work in Chrome with any method.
- Keep credentials out of source control. Read them from environment variables, for example
os.environ.["PROXY_USER"]
Because the handler lives in your script rather than in a browser profile, it also works in headless mode and on CI servers. Test it once with the IP check above: if the printed address is a residential IP in the country you chose, rather than your own, the proxy and the credentials are both working.
Key takeaways
- Selenium sends traffic through the browser, so proxy settings go in the browser options.
--proxy-serversets host and port; it never carries a username or password.- For credentials, enable BiDi and call
driver.network.before loading a page.add_auth_handler() - Chrome cannot authenticate to SOCKS5 proxies; use the HTTP endpoint with the same login.
The Chrome Extension Method After Chrome 137
Still useful, but not in regular ChromeFor years the standard answer to Selenium proxy authentication was to generate a tiny Chrome extension on the fly. The extension sets the proxy with the chrome.proxy API and answers the login challenge from a webRequest. listener, and Selenium loads it when the browser starts. Most tutorials, and many packages that promise “authenticated proxies for Selenium”, still work this way.
That changed with Chrome 137. Google removed the --load-extension command-line switch from branded Chrome builds, which is exactly what ChromeDriver uses to load an unpacked extension. In regular Chrome the log now says the switch is not allowed and the extension is silently skipped, so the browser falls back to a sign-in prompt or a failed page. A temporary switch that restored the old behaviour has since been removed as well.
The switch still works in Chrome for Testing and in open-source Chromium, which is what Google recommends for automation anyway. If you already depend on an extension, for example to change headers as well as authenticate, point Selenium at one of those builds:
options = webdriver.ChromeOptions()
options.binary_location = r"C:\tools\chrome-for-testing\chrome.exe"
options.add_argument(r"--load-extension=C:\tools\proxy-auth-ext")
driver = webdriver.Chrome(options=options)
The extension folder needs a Manifest V3 manifest.json with the proxy, webRequest and webRequest permissions, and a service worker that calls chrome.proxy. and returns your credentials from onAuthRequired. Manifest V2 extensions, which most older snippets generate, are no longer supported in current Chrome. For new projects the BiDi handler is simpler: no files to generate, no special browser build, and nothing to rewrite when Chrome changes its extension rules again.
What Happened to selenium-wire
An archived project and its replacementsselenium-wire was the other classic answer. It ran a local man-in-the-middle proxy between the browser and your upstream proxy, so it could add credentials, rewrite headers and let you inspect every request from Python. The original article on this page recommended it too. Its author archived the repository on 3 January 2024, so it no longer receives fixes for new Chrome, Selenium or Python versions, and its bundled certificate handling makes it fragile on modern sites.
Almost everything people used it for now has a supported replacement in Selenium itself:
| You used selenium-wire for | Use now |
|---|---|
| Proxy username and password | driver.network. with BiDi |
| Changing or blocking requests | BiDi request handlers in driver.network |
| Reading requests and responses | BiDi network events, or Chrome DevTools logging |
| A different upstream proxy per test | A separate driver with its own --proxy-server |
If you are starting a new scraper rather than maintaining a test suite, it is also worth looking at Playwright, which accepts a proxy server, username and password directly when you launch the browser or create a context. Our Python web scraping guide shows Playwright, requests and Scrapy with the same proxy.
Use a Proxy with Selenium and Firefox
Preferences for the host, BiDi for the loginFirefox reads its proxy settings from preferences rather than command-line flags, and GeckoDriver lets you set them on the options object. Proxy type 1 means manual configuration; set the same host and port for plain HTTP and for HTTPS.
from selenium import webdriver
options = webdriver.FirefoxOptions()
options.enable_bidi = True
options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.http", "v2.proxyempire.io")
options.set_preference("network.proxy.http_port", 5000)
options.set_preference("network.proxy.ssl", "v2.proxyempire.io")
options.set_preference("network.proxy.ssl_port", 5000)
driver = webdriver.Firefox(options=options)
handler_id = driver.network.add_auth_handler("YOUR_USERNAME", "YOUR_PASSWORD")
driver.get("https://api.ipify.org?format=json")
Firefox supports WebDriver BiDi natively, so the same authentication handler applies. As with Chrome, run the IP check once on your Firefox version before you scale up, and keep the handler registration before the first page load. The proxy capability from the Chrome section works in Firefox too, if you prefer one configuration for both browsers.
Rotating and Sticky IPs in Selenium
Choose per job, not per requestA browser is not a simple HTTP client. One page view triggers dozens of requests for scripts, styles, images and APIs, and Chrome keeps connections to the proxy open and reuses them. That shapes how rotation behaves in Selenium:
- Sticky sessions keep the same exit IP for as long as you use the session ID, with no fixed time limit on ProxyEmpire. Use them for anything with continuity: logging in to your own test account, a multi-step form, a basket, or paging through results that must come from one visitor.
- Rotating sessions give a fresh IP for new connections. In a browser, the reliable way to get a clean identity is a new driver with a new session, not a new page, because cookies, cache and open connections carry over inside one browser.
- Location targeting goes in the username generated in the dashboard. Rotating residential and mobile proxies target country, region, city, ZIP, ISP and ASN at no extra charge, so one gateway serves every market you test.
import os
from selenium import webdriver
def new_driver(username):
options = webdriver.ChromeOptions()
options.enable_bidi = True
options.add_argument("--proxy-server=http://v2.proxyempire.io:5000")
options.add_argument("--headless")
options.add_experimental_option(
"prefs", {"profile.managed_default_content_settings.images": 2})
driver = webdriver.Chrome(options=options)
driver.network.add_auth_handler(username, os.environ["PROXY_PASS"])
return driver
for user in [os.environ["PROXY_USER_US"], os.environ["PROXY_USER_DE"]]:
d = new_driver(user)
d.get("https://www.example.com/pricing")
print(d.title)
d.quit()
The prefs line turns off images, which cuts proxy traffic substantially on product and search pages while leaving the HTML and data you need. Close every driver with quit() in a finally block; abandoned headless browsers keep connections open and keep using traffic. ProxyEmpire has no limit on concurrent sessions, so you can run as many browsers in parallel as your machine or grid can handle. The sticky vs rotating guide goes deeper into the trade-offs, and mobile proxies are the right choice when you need to see a site the way 4G and 5G users see it.
Selenium Proxy Errors and How to Fix Them
What the error page is telling you| Error or symptom | Likely cause | Fix |
|---|---|---|
| ERR_ | Wrong host or port, or a firewall blocking outbound traffic to the proxy | Check the gateway address and port; test with curl from the same machine |
| ERR_ | The proxy refused the tunnel: failed login, or a destination that is blocked | Check credentials; banking, payment and government sites are blocked by default |
| A sign-in prompt, or a 407 page | No auth handler answered the challenge | Set enable_bidi and register the handler before get() |
| ERR_ | A SOCKS5 proxy with credentials, or a malformed proxy string | Use http:// and the auth handler |
| “–load-extension is not allowed” | Chrome 137 or newer ignores extension loading | Switch to the BiDi handler or Chrome for Testing |
| Your own IP in the IP check | The proxy option never reached the browser | Pass the options object to webdriver.Chrome(); check for typos in the flag |
| Same IP on every page | A sticky session, or connection reuse in one browser | Use a new driver and session for a new identity |
When the cause is not obvious, separate the proxy from Selenium. Run curl -x http:// from the same machine: if curl works and the browser does not, the problem is in your Selenium options; if curl fails too, it is the network, the credentials or the destination. ProxyEmpire’s support team is available 24/7 if you need a second pair of eyes on a connection.
Finally, a proxy changes where your traffic comes from, not how your automation behaves. Sites still notice hundreds of identical requests a second, so add realistic waits, reuse sessions where it makes sense and respect robots.txt and the site’s terms. Proxies are for lawful use; for background on collecting public data, see our web scraping basics.
ProxyEmpire proxies are for lawful use. Some site categories, such as banking, payment and government websites, are blocked by default on all products.
Selenium Proxy FAQ
Straight answersHow do I set a proxy in Selenium with Python?
Add --proxy-server= to ChromeOptions, or set the proxy capability, and pass the options to webdriver.Chrome(). For proxies with a login, also enable BiDi and register driver.network..
Can I put the username and password in the proxy URL?
Not in Chrome. It ignores credentials in --proxy-server and shows a sign-in prompt instead. Use the BiDi authentication handler, or an extension in Chrome for Testing.
Why did my proxy authentication extension stop working?
Chrome 137 removed the --load-extension switch from branded Chrome builds, so ChromeDriver can no longer load unpacked extensions there. It still works in Chrome for Testing and Chromium.
Does Selenium support SOCKS5 proxies?
Yes, without a login: use --proxy-server=. Chrome cannot authenticate to SOCKS5 proxies, so for authenticated sessions use the HTTP endpoint with the same credentials.
Is selenium-wire still maintained?
No. The repository was archived in January 2024. Selenium’s built-in BiDi network features now cover proxy authentication and request interception.
How do I use a different proxy for each browser?
Start a separate driver for each proxy or identity. Proxy settings apply to the whole browser, and one browser keeps cookies and connections between pages.
Which proxies work best with Selenium?
Rotating residential proxies suit most automation and testing across locations; mobile proxies suit mobile-first sites; datacenter proxies are the cheapest option for sites that do not filter by IP type.
References
Primary documentation- Selenium: WebDriver BiDi network featuresAuthentication, request and response handlers
- Selenium: Browser optionsThe proxy capability and other shared options
- Selenium: Chrome specific functionalityArguments, extensions and browser location
- Chromium Extensions: removing –load-extension in Chrome buildsGoogle’s announcement for Chrome 137
- SeleniumHQ issue 15788Extension loading after the Chrome change
- selenium-wire on GitHubArchived on 3 January 2024
Proxies for your Selenium automation
One gateway for more than 30 million residential IPs and 4 million mobile IPs, sticky sessions with no fixed time limit, targeting by country, city, ZIP, ISP and ASN at no extra cost, no concurrent session limit and 24/7 support from real people. Try it for $1.97.



