Selenium proxy authentication in Chrome: username and password with Python (tested)
Chrome refuses credentials in --proxy-server, the old extension trick and Selenium Wire no longer work, and the new BiDi auth handler has one catch on the first page. Working Python code for Selenium 4, sticky sessions, SOCKS5 and every error, tested on Chrome 155.

Here is a Selenium script that sends Chrome through an authenticated proxy and prints the IP the website sees:
from selenium import webdriver
from selenium.webdriver.common.by import By
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://geo.gw.nyxon.sh:8080")
options.add_argument("--headless=new")
options.enable_bidi = True
driver = webdriver.Chrome(options=options)
driver.network.add_auth_handler("USERNAME-country-us", "PASSWORD")
driver.browsing_context.navigate(
context=driver.current_window_handle,
url="https://api.ipify.org?format=json",
wait="complete",
)
print(driver.find_element(By.TAG_NAME, "body").text)
driver.get("https://example.com/")
print(driver.title)
driver.quit()Two lines in it look odd, and both matter: the first page loads with browsing_context.navigate instead of driver.get, and only then does the script switch to driver.get. The rest of this guide explains why, shows what goes wrong with the usual alternatives, and covers sticky sessions, bypass lists, SOCKS5 and the error you get for each mistake. Every snippet was run with Selenium 4.51 and Chrome 155 on Python 3.14, headless.
Why credentials in --proxy-server do not work
The first thing most people try is putting the username and password in the proxy URL:
options.add_argument("--proxy-server=http://USERNAME:[email protected]:8080")Chrome does not accept credentials in that switch. It refuses to use the proxy at all, and every page shows the "This site can't be reached" screen with this code at the bottom:
ERR_NO_SUPPORTED_PROXIESSelenium does not raise an exception here. driver.get returns normally and leaves you on Chrome's error page, so a script that only checks for exceptions carries on as if nothing happened.
Without credentials, headless Chrome shows a blank page
Drop the credentials and keep the address, and the proxy answers every connection with 407 Proxy Authentication Required. A visible Chrome window would pop up a login box. Headless Chrome has nobody to ask, so it gives up and renders an empty document, again without an exception:
<html><head></head><body></body></html>If your scraper suddenly collects empty pages after adding a proxy, this is the usual cause.
The BiDi auth handler
Recent Selenium 4 releases can answer that login challenge themselves, through WebDriver BiDi, the newer two-way protocol between Selenium and the browser. Two lines turn it on: options.enable_bidi = True before the browser starts, and driver.network.add_auth_handler with the proxy username and password after it starts. When the proxy asks for credentials, Selenium supplies them.
If driver.network has no add_auth_handler, your Selenium is too old for this; upgrade it with pip install -U selenium.
The first page must load through BiDi
This is the catch. In our tests, when the first proxy challenge arrived during driver.get, the handler's answer never reached Chrome. The page sat there until the page load timeout and Selenium raised:
TimeoutException: Message: timeout: Timed out receiving message from rendererLoading the same page with driver.browsing_context.navigate worked every time. Navigate is a BiDi command rather than a classic WebDriver one, and with it the handler's answer got through on every run.
You only need it once per browser. After the first successful login, Chrome remembers the credentials and sends them with every new connection to the same proxy without being asked, so plain driver.get works for the rest of the session. In the test above, the proxy saw the credentials on every later connection, to every site, with no further challenges.
A small helper keeps that tidy:
def open_first(driver, url):
driver.browsing_context.navigate(
context=driver.current_window_handle, url=url, wait="complete"
)Countries and sticky sessions: one browser, one username
NyxonProxies reads targeting and session options from the username, such as USERNAME-country-de for a German IP, or a session ID and duration to keep the same IP. Because Chrome caches the credentials, the username you start with is the username for the whole browser session. In our test, removing the handler and adding one with a different country changed nothing; the next site still went out as the first username.
So to change country or IP, start a new browser. A function that builds the username, starts Chrome and logs in on the first page:
import secrets
from selenium import webdriver
from selenium.webdriver.common.by import By
GATEWAY = "geo.gw.nyxon.sh:8080"
PASSWORD = "PASSWORD"
def new_browser(country, minutes=10):
sid = secrets.token_hex(4)
user = f"USERNAME-country-{country}-session-{sid}-time-{minutes}"
options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server=http://{GATEWAY}")
options.add_argument("--headless=new")
options.enable_bidi = True
driver = webdriver.Chrome(options=options)
driver.network.add_auth_handler(user, PASSWORD)
driver.browsing_context.navigate(
context=driver.current_window_handle,
url="https://api.ipify.org?format=json",
wait="complete",
)
return driver
for country in ["us", "de", "gb"]:
driver = new_browser(country)
print(country, driver.find_element(By.TAG_NAME, "body").text)
driver.quit()The -time- value is in minutes, and residential sessions last up to 120 minutes. The Generate page builds the same username for you, and the sessions guide explains each option.
Why every page comes from the same IP
Even without a session ID, consecutive pages on one site usually leave from the same IP. Chrome keeps its connection to a site open and reuses it, and an HTTPS connection through a proxy is a tunnel that stays on one exit IP for as long as it is open. In our test, three page loads of the same URL went through a single tunnel.
That is usually what you want, since a real visitor does not change IP between clicks. If you need a fresh IP for each page, use a new browser per page with new_browser above.
Sending some sites around the proxy
To keep internal or monitoring hosts off the proxy, list them in --proxy-bypass-list, separated by semicolons. A leading *. matches subdomains:
options.add_argument("--proxy-bypass-list=*.internal.example;status.example.com")We checked it with a public host in the list and another outside it: the listed host loaded directly, and only the other one reached the proxy.
SOCKS5 in Selenium
Chrome supports SOCKS5 proxies only without a password. With the handler and a SOCKS5 address, the proxy rejects Chrome's login-free connection and the page fails with ERR_SOCKS_CONNECTION_FAILED. With credentials in a socks5:// address, you get ERR_NO_SUPPORTED_PROXIES again.
Use the HTTP port, 8080, with Selenium. It still carries HTTPS sites end to end: Chrome opens a tunnel with CONNECT and runs TLS straight through it to the website. The SOCKS5 vs HTTP guide covers when SOCKS5 is worth it in other tools.
Old methods that no longer work
Two workarounds still fill search results, and both are dead ends now.
The first is a small Chrome extension that answers the login challenge, loaded with the --load-extension switch. Google Chrome now ignores that switch. With Chrome 155 the extension never loads, the page stays blank, and Chrome's log says:
--load-extension is not allowed in Google Chrome, ignoring.The second is Selenium Wire, a library that ran its own proxy in front of the browser. Its GitHub repository was archived on 3 January 2024, and its README says: "Selenium Wire is no longer being maintained."
If you are starting a new project rather than fixing an old one, note that Playwright takes proxy credentials directly at launch, with no handler needed; see the Playwright proxy guide. Puppeteer has its own method, covered in the Puppeteer proxy guide.
Errors and what they mean
- ERR_NO_SUPPORTED_PROXIES: credentials inside
--proxy-server, HTTP or SOCKS5. Move them to the handler. - A blank page and no error: no credentials reached the proxy. Check that the handler was added.
- TimeoutException on the first page: the first page was loaded with
driver.get. Usebrowsing_context.navigate. - ERR_TOO_MANY_RETRIES: wrong username or password. Chrome retried several times and gave up.
- ERR_PROXY_CONNECTION_FAILED: wrong host or port, or a firewall blocking the connection.
- ERR_SOCKS_CONNECTION_FAILED: a SOCKS5 proxy that requires a password. Use the HTTP port.
Most of these arrive as exceptions in the form unknown error: net::ERR_..., from navigate and from driver.get alike. Two are quiet: with ERR_NO_SUPPORTED_PROXIES and the blank page, driver.get returns normally, so check the page as well as the exceptions. The proxy error codes guide covers the status codes behind these errors.
FAQ
Can Selenium use a proxy with a username and password in Chrome? Yes. Enable BiDi, add the credentials with driver.network.add_auth_handler, and load the first page with browsing_context.navigate.
Does this work in headless mode? Yes. Every test in this guide ran with --headless=new.
How do I change the proxy country without restarting Chrome? You cannot with one browser, because Chrome keeps using the credentials that first worked. Start a new browser with the new username.
Do I still need Selenium Wire or a proxy extension? No. Selenium Wire is no longer maintained, Google Chrome ignores --load-extension, and the BiDi handler covers what they did for proxy logins.
Try it
Create a NyxonProxies account, link Discord under Freebies for 250MB of free Residential Budget traffic, and copy your username and password from the Generate page into the script above. Prices for every proxy type are on the pricing page.