Selenium proxy authentication: the working fix
By Mechelle Henderson · Published: 7 September 2026 · 7 min read
TL;DR: Chrome ignores user:pass in --proxy-server and pops a login dialog Selenium can't fill. The reliable fix is a tiny runtime Chrome extension that sets the proxy and answers onAuthRequired with your credentials — it works with --headless=new. If you'd rather not build one, Selenium Wire does the same in one line. Both send you out through a real residential IP.
Why the obvious way doesn't work
The natural attempt is to pass the proxy on the command line:
# This does NOT authenticate — Chrome shows a login popup and hangs.
options.add_argument("--proxy-server=http://USER:[email protected]:41080")
Chrome does not read credentials from --proxy-server. When the proxy replies 407 Proxy Authentication Required, Chrome opens a native dialog — and Selenium can't type into an OS-level dialog, so the request just stalls. You have to hand Chrome the credentials another way.
The reliable fix: a runtime auth extension
Chrome answers the 407 itself if an extension provides the credentials through chrome.webRequest.onAuthRequired. You don't need to ship a real extension — build one in memory from two small files, zip it, and load it. This has no third-party runtime dependency and works headless.
import zipfile
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
USER, PASSWORD = "YOUR_USERNAME", "YOUR_PASSWORD"
HOST, PORT = "gw.roamproxy.com", 41080
manifest = """{
"name": "Roam Proxy Auth",
"version": "1.0.0",
"manifest_version": 3,
"permissions": ["proxy", "webRequest", "webRequestAuthProvider"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "background.js" }
}"""
background = f"""
chrome.proxy.settings.set({{
value: {{
mode: "fixed_servers",
rules: {{ singleProxy: {{ scheme: "http", host: "{HOST}", port: {PORT} }} }}
}},
scope: "regular"
}});
chrome.webRequest.onAuthRequired.addListener(
() => ({{ authCredentials: {{ username: "{USER}", password: "{PASSWORD}" }} }}),
{{ urls: ["<all_urls>"] }},
["blocking"]
);
"""
with zipfile.ZipFile("roam_proxy_auth.zip", "w") as zp:
zp.writestr("manifest.json", manifest)
zp.writestr("background.js", background)
options = Options()
options.add_argument("--headless=new") # extensions load only in NEW headless
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_extension("roam_proxy_auth.zip")
driver = webdriver.Chrome(options=options)
driver.get("https://ip.sb")
print(driver.find_element("tag name", "body").text)
driver.quit()
The printed IP should be a Roam residential exit, not your machine's. The webRequestAuthProvider permission is what lets a Manifest V3 extension answer the auth challenge — without it the listener never fires.
Use --headless=new, not the old --headless. The old headless mode does not load extensions, which is the single most common reason "it works on my desktop but 407s on the server."
The one-liner: Selenium Wire
If a third-party dependency is fine, Selenium Wire handles proxy auth for you — it runs a local proxy that injects the credentials, so you just pass the URL:
from seleniumwire import webdriver # pip install selenium-wire
seleniumwire_options = {
"proxy": {
"http": "http://YOUR_USERNAME:[email protected]:41080",
"https": "http://YOUR_USERNAME:[email protected]:41080",
"no_proxy": "localhost,127.0.0.1",
}
}
driver = webdriver.Chrome(seleniumwire_options=seleniumwire_options)
driver.get("https://ip.sb")
print(driver.find_element("tag name", "body").text)
driver.quit()
It's the least code, at the cost of an extra dependency and a local proxy hop. For a long-running scraper on a server, the extension method keeps the stack smaller.
One IP per browser, and rotation
Each Chrome instance you launch is one identity with one exit IP. To run several identities, launch several drivers, and give each a different sticky-session token in the username (USERNAME_session_1, USERNAME_session_2, …) so each gets a distinct, stable residential IP. Change the token when you want a fresh IP. See what are rotating proxies for how the session string works.
Unauthenticated proxies
If your proxy is IP-whitelisted and needs no login, none of this applies — plain --proxy-server=http://host:port is enough. The whole dance above exists only because a username and password are involved, which is how per-request billing and sticky sessions work at Roam.
When the IP isn't the problem
A clean residential IP clears reputation blocks. It won't clear a JavaScript challenge that needs a real browser — which is exactly what Selenium already gives you — or a TLS fingerprint mismatch on scripted HTTP calls made outside the browser. If you're scripting plain HTTP instead of driving a browser, see curl_cffi for a browser-accurate fingerprint; to pick the right layer against a specific defense, see how to get past Cloudflare. Prefer Playwright? The same proxy drops into Playwright and Puppeteer with first-class auth support.
FAQ
Why does Selenium ignore my proxy username and password?
Chrome does not read credentials from the --proxy-server flag. When the proxy answers with 407 Proxy Authentication Required, Chrome shows a native login dialog that Selenium cannot type into, so the request stalls. You have to supply the credentials another way — a runtime extension that answers the onAuthRequired event, or a wrapper like Selenium Wire that proxies the traffic itself.
How do I use an authenticated proxy in Selenium without a third-party library?
Build a tiny Chrome extension at runtime with two files — a manifest and a background script that calls chrome.proxy.settings.set and returns authCredentials from chrome.webRequest.onAuthRequired — zip them, and load the zip with options.add_extension(). Chrome then answers the proxy's 407 challenge itself and no dialog appears. This works headless with --headless=new.
Does the extension method work in headless Selenium?
Yes, with the new headless mode. Add --headless=new (not the old --headless) so extensions load, and keep --no-sandbox and --disable-dev-shm-usage on servers. The old headless mode did not load extensions, which is why authenticated-proxy setups used to fail only when headless.
Can I just use a SOCKS5 proxy to avoid the dialog?
Only if the proxy needs no username and password. Chrome accepts --proxy-server=socks5://host:port for an unauthenticated proxy, but it still cannot send SOCKS5 credentials from the flag. With Roam you have a username and password, so use the extension or Selenium Wire over HTTP rather than trying to embed credentials in a SOCKS URL.
Roam gives you a plain host:port gateway with a username and password — everything above turns that into a working Selenium session. Rotating residential at $2/GB and static residential at $4/IP per month, over HTTP and SOCKS5, with sticky sessions for one IP per browser. Create an account and get 300MB of free trial traffic to point your first driver at a residential exit.