To use a proxy with Selenium in Python and Chrome, add --proxy-server=http://host:port to ChromeOptions before you create the driver. That part is easy. Authentication is where most setups break: Chrome ignores a username and password inside --proxy-server, so http://user:pass@host:port does not log in to the proxy.
Quick answer:
- No auth or IP-allowlisted proxy:
options.add_argument("--proxy-server=http://host:port"). - Username and password proxy: keep
--proxy-serverfor routing, then answer the auth challenge with a Selenium BiDi auth handler or a small Manifest V3 extension. Allowlisting your machine's IP removes the problem completely. - Firefox: assign a
Proxyobject tooptions.proxyonFirefoxOptions. See the Firefox section below. - Do not start new work on Selenium Wire. Its GitHub repository is archived and the project is no longer maintained.
- Rotation: start a new driver for each proxy. Chrome does not switch proxies inside a running session.

Set a Selenium proxy in Python with Chrome
Pass the proxy as a Chrome command-line argument through ChromeOptions:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://proxy.example.com:8080")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://ipv4.unknownproxies.com/ip")
print(driver.find_element("tag name", "body").text)
finally:
driver.quit()
The page should print the proxy's exit IP, not your own. If it prints your own IP, Chrome did not receive the argument. Check that you passed the same options object to webdriver.Chrome().
Timing matters. Chrome reads --proxy-server once, at launch. If you change the proxy string after webdriver.Chrome() returns, the running browser keeps the old route. Set a custom Selenium user agent the same way, before launch, so the browser profile and the route stay the same for the whole session.
Chromium also accepts a bypass list for hosts that must go direct:
options.add_argument("--proxy-server=http://proxy.example.com:8080")
options.add_argument("--proxy-bypass-list=localhost;127.0.0.1")
For a SOCKS5 proxy, use --proxy-server=socks5://proxy.example.com:1080. Chrome supports no authentication methods for SOCKS5, so a SOCKS5 proxy must be allowlisted by IP. Our SOCKS5 vs HTTP proxy comparison explains when each protocol fits.
Why Chrome ignores user:pass in --proxy-server
Many tutorials show this:
# Does not authenticate in Chrome
options.add_argument("--proxy-server=http://USER:PASS@proxy.example.com:8080")
Chromium's proxy support documentation is direct about it: Chrome does not implement credentials embedded in proxy settings and will not use them. The proxy answers with 407 Proxy Authentication Required, and Chrome shows a sign-in dialog or a blank page. WebDriver cannot type into that dialog.
So the fix is not a different URL format. You need one of these:
| Method | Works with | Code needed | Notes |
|---|---|---|---|
| IP allowlisting | HTTP and SOCKS5 | None | Simplest. Your machine needs a stable public IP |
| Selenium BiDi auth handler | HTTP proxies | A few lines | Needs a recent Selenium 4 release and enable_bidi |
| Manifest V3 extension | HTTP proxies | Two small files | Branded Chrome 137+ ignores --load-extension |
| Local forwarding proxy | HTTP and SOCKS5 | Separate process | The forwarder adds credentials upstream |
| Selenium Wire | Not recommended | Archived, no longer maintained |
Authenticated proxy options for Selenium Chrome
Before you change browser code, confirm the credentials work outside Selenium:
curl -x "http://USERNAME:PASSWORD@proxy.example.com:8080" https://ipv4.unknownproxies.com/ip
If curl fails, the problem is the host, port, credentials, or plan, not Selenium. Our guide to fixing 407 Proxy Authentication Required covers that layer. If curl works and Chrome does not, continue below.
Option 1: IP allowlisting
If your proxy plan lets you allowlist IP addresses, add the public IP of the machine that runs Chrome. Then use the plain --proxy-server=http://host:port setup above with no credentials at all. This works in headless mode, with SOCKS5, and in every Chrome version.
It is a poor fit when your scripts run on machines with changing IPs, such as some CI runners or laptops on different networks.
Option 2: Selenium BiDi auth handler
Selenium 4 has a WebDriver BiDi network module. In recent Python releases, driver.network.add_auth_handler(username, password) answers HTTP authentication challenges for you. The method and its remove_auth_handler(callback_id) counterpart are listed in the Selenium Python API reference, and the BiDi network documentation says BiDi must be enabled in the browser options. As of October 2026 the latest release on PyPI is Selenium 4.51.0, which needs Python 3.10 or newer.
import os
from selenium import webdriver
options = webdriver.ChromeOptions()
options.enable_bidi = True
options.add_argument("--proxy-server=http://proxy.example.com:8080")
driver = webdriver.Chrome(options=options)
handler_id = driver.network.add_auth_handler(
os.environ["PROXY_USER"],
os.environ["PROXY_PASS"],
)
try:
driver.get("https://ipv4.unknownproxies.com/ip")
print(driver.find_element("tag name", "body").text)
finally:
driver.network.remove_auth_handler(handler_id)
driver.quit()
Two limits. First, the Python BiDi API changed across Selenium 4 releases, so older versions may not have add_auth_handler. Upgrade with pip install -U selenium and check the method exists. Second, Selenium's documentation shows the handler answering a website's HTTP auth prompt, not a proxy 407. Confirm on your Chrome version that it also answers your proxy's challenge before you rely on it. The exit-IP check above shows the result in one request.
Option 3: Manifest V3 extension
A small extension can answer the proxy challenge with chrome.webRequest.onAuthRequired. Current Chrome needs Manifest V3: Chrome 139 removed Manifest V2 support completely, as the Manifest V2 deprecation timeline states. Many older Selenium proxy-auth snippets use "manifest_version": 2 and no longer load.
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:
const USERNAME = "PROXY_USER";
const PASSWORD = "PROXY_PASS";
chrome.webRequest.onAuthRequired.addListener(
(details, callback) => {
if (details.isProxy) {
callback({ authCredentials: { username: USERNAME, password: PASSWORD } });
} else {
callback({});
}
},
{ urls: ["<all_urls>"] },
["asyncBlocking"]
);
The webRequest API reference documents the webRequestAuthProvider permission, asyncBlocking, and the isProxy field. The isProxy check makes sure the extension only answers the proxy, not website logins.
Load the folder and keep --proxy-server for routing:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.binary_location = "/path/to/chrome-for-testing/chrome"
options.add_argument("--proxy-server=http://proxy.example.com:8080")
options.add_argument("--load-extension=/abs/path/to/proxy-auth-extension")
driver = webdriver.Chrome(options=options)
The catch: starting in Chrome 137, branded Google Chrome ignores --load-extension. This also breaks Selenium's extension loading in branded Chrome. Chrome for Testing and Chromium still accept the flag, which is why the example sets binary_location. Selenium Manager can download Chrome for Testing for you when you request a browser version that is not installed.
Generate the extension folder from environment variables at runtime, and delete it afterwards. Do not commit credentials inside background.js.
Option 4: Local forwarding proxy
Run a small proxy on 127.0.0.1 that accepts connections from Chrome without auth and forwards them to the real proxy with credentials. Chrome then uses --proxy-server=http://127.0.0.1:PORT. This works in every browser and Chrome version, but it is one more process to run and monitor. Bind it to localhost only, or anyone on your network can use your proxy account.
Selenium Wire is archived
Selenium Wire used to be the standard answer for authenticated proxies. Its GitHub repository was archived on January 3, 2024, with the notice "Selenium Wire is no longer being maintained", and the last PyPI release is 5.1.0 from October 2022. Existing scripts may still run, but new Selenium and Chrome releases receive no fixes. Prefer one of the options above for new code.
ChromeOptions vs the Selenium Proxy object
Selenium also has a browser-neutral Proxy class. In current Selenium 4, assign it to options.proxy, as the Selenium options documentation shows:
from selenium import webdriver
from selenium.webdriver.common.proxy import Proxy, ProxyType
options = webdriver.ChromeOptions()
options.proxy = Proxy({
"proxyType": ProxyType.MANUAL,
"httpProxy": "proxy.example.com:8080",
"sslProxy": "proxy.example.com:8080",
})
driver = webdriver.Chrome(options=options)
The Proxy constructor takes a dict. These keys come from the Selenium Python Proxy reference:
| Key | What it sets |
|---|---|
proxyType |
ProxyType.MANUAL, PAC, AUTODETECT, SYSTEM, or DIRECT |
httpProxy, sslProxy |
host:port for HTTP and HTTPS traffic |
socksProxy, socksVersion |
host:port and an int such as 5 for a SOCKS proxy |
noProxy |
Hosts that skip the proxy, as a string or a list of strings |
proxyAutoconfigUrl |
PAC file URL, used with ProxyType.PAC |
Older tutorials pass desired_capabilities to webdriver.Chrome(). Current Selenium 4 releases do not accept that argument, so that code fails with a TypeError. The Proxy object helps when you share one configuration between Chrome and Firefox. For Chrome only, --proxy-server is shorter and easier to debug. Neither one adds proxy authentication.
Selenium proxy in Python with Firefox
Firefox uses the same Proxy object. Set it on FirefoxOptions before you create the driver:
from selenium import webdriver
from selenium.webdriver.common.proxy import Proxy, ProxyType
options = webdriver.FirefoxOptions()
options.proxy = Proxy({
"proxyType": ProxyType.MANUAL,
"httpProxy": "proxy.example.com:8080",
"sslProxy": "proxy.example.com:8080",
"noProxy": ["localhost", "127.0.0.1"],
})
driver = webdriver.Firefox(options=options)
try:
driver.get("https://ipv4.unknownproxies.com/ip")
print(driver.find_element("tag name", "body").text)
finally:
driver.quit()
Authentication has the same shape as in Chrome. Credentials in the proxy address do not log in, and Selenium maintainers said in a Selenium issue about Python proxy auth that Firefox does not support proxy authentication through the proxy settings either. Use IP allowlisting, a local forwarding proxy, or the BiDi auth handler from Option 2 with options.enable_bidi = True. The Manifest V3 extension route is Chrome-specific.
Proxy formats Selenium and Chrome accept
| Proxy format | Example | Where it works |
|---|---|---|
| Host and port | proxy.example.com:8080 |
Proxy object fields, --proxy-server (HTTP assumed) |
| HTTP URL | http://proxy.example.com:8080 |
--proxy-server |
| SOCKS5 URL | socks5://proxy.example.com:1080 |
--proxy-server, IP-allowlisted proxies only |
| Auth URL | http://user:pass@proxy.example.com:8080 |
curl and Python requests, not Chrome |
Proxy dashboards often give lines like host:port:username:password. Split the host and port for --proxy-server and keep the credentials for your auth handler or extension. The proxy converter rewrites whole lists between these formats.
Rotating proxies with Selenium
Selenium does not rotate a browser's proxy per request. Treat each proxy as part of one browser session and start a new driver to change it:
from contextlib import suppress
from selenium import webdriver
proxies = [
"http://proxy-1.example.com:8080",
"http://proxy-2.example.com:8080",
"http://proxy-3.example.com:8080",
]
for proxy in proxies:
options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server={proxy}")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com")
# Run one bounded workflow for this proxy.
finally:
with suppress(Exception):
driver.quit()
This is slower than changing a proxy in an HTTP client such as Python Requests, but it is more reliable. A browser session holds cookies, local storage, service workers, and open connections. If you change the network route in the middle of a flow, the site sees one session jump between IPs.
You can also keep one endpoint and let the provider rotate behind it. A rotating gateway gives a new IP per connection, and a sticky session keeps one IP for a set time. Our guide to sticky vs rotating proxies explains which session type fits logins, carts, and multi-step forms. If you need several proxies inside one browser process, Playwright supports a proxy per browser context; see the Playwright proxy reference. Puppeteer does the same with one browser context per proxy.
Residential vs ISP proxies for Selenium
| Workflow | Better fit | Why |
|---|---|---|
| Localized public page checks | Residential | Broad geography and consumer IPs |
| Repeated account workflow | ISP | Stable dedicated IPs and consistent sessions |
| Many independent pages | Residential | Rotation spreads load where the site allows it |
| QA from one market | ISP or sticky residential | Consistency matters more than rotation |
| Retail and ticketing checkout tests | ISP or sticky residential | One IP has to last the whole checkout |
| Retail monitoring | Depends | Stability, latency, and site tolerance all matter |
Unknown Proxies sells both residential proxies and ISP proxies. If the site expects the same visitor across several browser actions, a stable session matters more than a large list. If each page view is independent, rotation reduces how often one IP repeats.
Debugging a Selenium proxy
Change one thing at a time:
- Test the proxy outside Selenium with
curl. - Launch Chrome with one proxy and open an IP echo page.
- Confirm HTTPS pages load, not only plain HTTP.
- Check whether the proxy returns
407. - Run a visible browser with no extensions or custom profile while you debug.
- Add a bypass list only for hosts that must go direct.
- Compare the same target with and without the proxy.
| Symptom | Likely cause | First fix |
|---|---|---|
| Pages never load | Wrong host, port, or protocol, or the proxy is down | Test with curl |
| Sign-in dialog or blank page | Chrome ignored user:pass in --proxy-server |
Use allowlisting, a BiDi auth handler, or an MV3 extension |
| Extension does nothing | Branded Chrome 137+ ignored --load-extension, or the manifest is V2 |
Use Chrome for Testing and Manifest V3 |
TypeError on desired_capabilities |
Old tutorial code | Use options.proxy or --proxy-server |
| Only localhost breaks | Chrome sends local traffic through the proxy | Add --proxy-bypass-list |
| Target returns 403 or 429 | Site rules, rate limit, or session mismatch | Slow down and debug behavior, not the proxy setting |
A 403 or 429 from the target means the request went through the proxy and the site chose not to serve it. The proxy setting is working at that point.

Headless mode and proxy testing
Headless mode hides the proxy sign-in dialog, so a failed login looks like an empty page. Get the proxy working in a visible browser first:
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://proxy.example.com:8080")
# Do not add --headless until the proxy works.
driver = webdriver.Chrome(options=options)
Then enable headless mode and test again:
options.add_argument("--headless=new")
If the visible browser works and headless fails, compare flags, extensions, and how the auth step runs. Keep the same proxy during the test so you do not confuse proxy quality with browser mode.
Session and rate-limit guidelines
One driver.get() loads HTML, scripts, images, fonts, and XHR calls, all through the same proxy. Browser automation sends many more requests than one HTTP call.
- Keep concurrency low until you know what the site tolerates.
- Use explicit waits instead of fast reload loops.
- Keep one sticky session for a multi-step workflow.
- Rotate only between independent tasks.
- Log the proxy ID, session ID, status code, and failure reason.
If the site starts returning 429, back off and honor Retry-After; our HTTP 429 Too Many Requests guide covers backoff in detail.
FAQ
How do I use a proxy with Selenium in Python and Chrome?
Create webdriver.ChromeOptions(), call options.add_argument("--proxy-server=http://host:port"), and pass the options to webdriver.Chrome(options=options). Open an IP echo page to confirm the exit IP.
Why does Chrome ignore the username and password in my proxy URL?
Chrome does not use credentials embedded in proxy settings. Chromium's documentation says so directly. Use IP allowlisting, a Selenium BiDi auth handler, a Manifest V3 extension, or a local forwarding proxy.
How do I set a proxy in Selenium with Firefox in Python?
Create webdriver.FirefoxOptions(), assign options.proxy = Proxy({"proxyType": ProxyType.MANUAL, "httpProxy": "host:port", "sslProxy": "host:port"}), and pass the options to webdriver.Firefox(options=options). Like Chrome, it does not log in with credentials in the address.
Is Selenium Wire still maintained?
No. The GitHub repository was archived on January 3, 2024, and the last release, 5.1.0, is from October 2022. Old scripts may still run, but use another auth method for new projects.
Can I use a SOCKS5 proxy with authentication in Selenium Chrome?
Not directly. Chrome supports no SOCKS5 authentication methods. Use IP allowlisting, an HTTP endpoint from the same provider, or a local forwarder that handles the upstream login.
Can I change the proxy without restarting Selenium?
Not with --proxy-server. Chrome reads it at launch. Start a new driver per proxy, or use a rotating gateway endpoint that changes the IP on the provider side.
Why does my proxy work with curl but not Chrome?
curl accepts user:pass@host:port, and Chrome does not. If the credentials work in curl, the proxy is fine. Fix the auth step in the browser instead.
Final thoughts
Set --proxy-server before launch, verify the exit IP, and solve authentication on purpose instead of with a URL Chrome ignores. IP allowlisting is the least fragile choice. BiDi auth handlers and Manifest V3 extensions work when you need username and password auth. For stable dedicated sessions in browser automation, see ISP proxy pricing.