Selenium proxy setup and authentication
Chrome's proxy setting takes no password, so this guide runs a small local forwarder that adds your netdash username and password, and points Selenium's browser at it.
- Updated
- Reading time
- 5 min
- Checked against
- 6 sources
A browser's proxy setting has no place for a password, so a Selenium proxy setup needs a second piece that supplies your netdash username and password. This guide uses a small local forwarder written in Python: Chrome sends its traffic to the forwarder on 127.0.0.1, and the forwarder passes it on to the netdash gateway with your credentials added. It works with any Selenium 4 release and needs nothing beyond the Python standard library. All examples use the Scale plan on the Americas gateway, scale-us.netdash.io:2000; other plans differ only in host and port, listed in the proxy format reference.
Why credentials don't go in --proxy-server
Chromium's proxy documentation says Chrome doesn't use credentials embedded in proxy settings; proxy authentication goes through its normal credential flow instead. In our check with headless Chrome, a --proxy-server value of the form http://user:pass@host:port made Chrome reject the proxy setting with net::ERR_NO_SUPPORTED_PROXIES, and nothing reached the gateway. With the host and port alone and nobody answering the challenge, the gateway's 407 Proxy Authentication Required ended the page load with net::ERR_INVALID_AUTH_CREDENTIALS. A visible browser may show a sign-in prompt instead, which a WebDriver session can't fill in.
Selenium's Python bindings document a WebDriver BiDi handler for this, driver.network.add_auth_handler(). In our test (Selenium 4.36 on Python 3.9, Chrome 155) it didn't answer the gateway's 407: the handler raised an error and the page load timed out. Until a version passes the same test, this guide uses the forwarder below, and we'll update the page when that changes.
Selenium proxy authentication with a local forwarder
Save the forwarder as netdash_forwarder.py next to your tests:
import asyncio
import base64
import threading
HOP_BY_HOP = ("proxy-authorization:", "proxy-connection:", "connection:", "keep-alive:")
def start_forwarder(username: str, password: str, gateway: str = "scale-us.netdash.io:2000") -> str:
"""Start a local proxy on 127.0.0.1 that forwards to the netdash gateway with credentials.
Returns its URL (for example http://127.0.0.1:53211) to pass to the browser. It adds the
Proxy-Authorization header and passes everything else through: HTTPS stays a CONNECT tunnel,
so the forwarder never sees page content. It runs until your script exits.
"""
host, port = gateway.rsplit(":", 1)
token = base64.b64encode(f"{username}:{password}".encode()).decode()
async def pipe(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
try:
while True:
data = await reader.read(65536)
if not data:
break
writer.write(data)
await writer.drain()
except (ConnectionError, asyncio.CancelledError):
pass
finally:
writer.close()
async def handle(client_reader: asyncio.StreamReader, client_writer: asyncio.StreamWriter) -> None:
try:
head = await client_reader.readuntil(b"\r\n\r\n")
upstream = asyncio.open_connection(host, int(port))
upstream_reader, upstream_writer = await asyncio.wait_for(upstream, timeout=30)
except (asyncio.IncompleteReadError, asyncio.LimitOverrunError, asyncio.TimeoutError, OSError):
client_writer.close()
return
lines = head.decode("latin-1").split("\r\n")
request_line = lines[0]
headers = [h for h in lines[1:] if h and not h.lower().startswith(HOP_BY_HOP)]
headers.insert(0, f"Proxy-Authorization: Basic {token}")
if not request_line.startswith("CONNECT "):
headers.append("Connection: close") # one request per connection, each with credentials
upstream_writer.write(("\r\n".join([request_line, *headers]) + "\r\n\r\n").encode("latin-1"))
await asyncio.gather(pipe(client_reader, upstream_writer), pipe(upstream_reader, client_writer))
loop = asyncio.new_event_loop()
server = loop.run_until_complete(asyncio.start_server(handle, "127.0.0.1", 0))
threading.Thread(target=loop.run_forever, daemon=True).start()
return f"http://127.0.0.1:{server.sockets[0].getsockname()[1]}"Then start one forwarder per username and give Chrome its address:
Checked against a local test proxyimport os
from selenium import webdriver
from netdash_forwarder import start_forwarder
username = f"{os.environ['NETDASH_USERNAME']}-cc-US"
local_proxy = start_forwarder(username, os.environ["NETDASH_PASSWORD"], "scale-us.netdash.io:2000")
options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server={local_proxy}")
driver = webdriver.Chrome(options=options)
try:
driver.set_page_load_timeout(30)
driver.get("https://api.ipify.org?format=json")
print(driver.find_element("tag name", "body").text)
finally:
driver.quit()The page shows the exit IP the website saw, for example {"ip":"203.0.113.10"}. Here is what each part does:
start_forwarder()listens on a free port on127.0.0.1and returns its address, for examplehttp://127.0.0.1:53211.- For every connection from the browser, it opens a connection to the gateway and adds a
Proxy-Authorizationheader with your username and password. The gateway never has to send a407challenge. - HTTPS pages go through a CONNECT tunnel: the forwarder passes the encrypted bytes through and never sees page content, and certificates verify normally.
- Plain-HTTP requests are sent one per connection, so each one carries the credentials.
- The username carries the exit country (
-cc-US), and the password is read fromNETDASH_PASSWORD.
Credentials
Keep NETDASH_USERNAME and NETDASH_PASSWORD in a .env file or your secret manager, never in source control or in a chat with an assistant. Don't log the username and password together. The forwarder accepts connections from your own machine only, but any program running on it could use the forwarder while your script runs.
Exit countries and sessions
The exit country and the sticky session are part of the username, and a forwarder always uses the username it was started with. Start one forwarder and one driver per country or per sticky session, and quit the driver to end the session:
Checked against a local test proxyimport os
import secrets
from typing import Optional
from selenium import webdriver
from netdash_forwarder import start_forwarder
def netdash_driver(country: str = "US", session_id: Optional[str] = None, ttl_minutes: int = 30,
gateway: str = "scale-us.netdash.io:2000") -> webdriver.Chrome:
"""Chrome through netdash: rotating without session_id, sticky with one."""
cc = country.upper()
cc = "GB" if cc == "UK" else cc
username = f"{os.environ['NETDASH_USERNAME']}-cc-{cc}"
if session_id is not None:
username += f"-session-{session_id}-ttl-{ttl_minutes}"
local_proxy = start_forwarder(username, os.environ["NETDASH_PASSWORD"], gateway)
options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server={local_proxy}")
driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(30)
return driver
# A sticky German session through the Europe gateway: one exit IP for the whole test
driver = netdash_driver("DE", session_id=secrets.token_hex(4), gateway="scale-eu.netdash.io:2000")
try:
for _ in range(2):
driver.get("https://api.ipify.org?format=json")
print(driver.find_element("tag name", "body").text) # the same exit IP both times
finally:
driver.quit()A sticky session keeps the same exit IP for the TTL you set while that IP stays available; the dashboard presets are 15 minutes, 30 minutes, 12 hours and 7 days. Use GB for the United Kingdom, and the -eu gateway for exits in Europe. Rotating and sticky sessions explains how to choose.
Other browsers, Selenium Grid and remote browsers
The forwarder is an ordinary proxy without authentication, so other browsers can use it through their own proxy settings; we tested the examples with Chrome. The proxy applies where the browser runs. On Selenium Grid or another remote setup, the browser runs on the node, so the forwarder has to run on that node as well, and the node needs outbound access to the gateway's port (2000 for Scale). Authentication is username and password only, so it doesn't matter which IP address the node connects from.
Approaches we don't recommend
Don't put credentials in the proxy URL: Chrome rejects the setting or ignores the credentials. Don't disable certificate checks to make a proxy work; netdash tunnels HTTPS with CONNECT, so certificates verify normally. Avoid unmaintained wrappers that intercept and decrypt browser traffic with their own certificate in order to add proxy credentials.
Errors and fixes
| Symptom | Cause | Fix |
|---|---|---|
net::ERR_NO_SUPPORTED_PROXIES | Credentials inside --proxy-server | Pass the forwarder's address, which has no credentials |
net::ERR_INVALID_AUTH_CREDENTIALS | Chrome points at the gateway directly, or the password or username parameters are wrong | Pass the forwarder's address to --proxy-server; check the password and the username parameters (-cc-, -session-...-ttl-...) |
net::ERR_PROXY_CONNECTION_FAILED or net::ERR_CONNECTION_CLOSED on every page | The forwarder can't reach the gateway: wrong host and port pair, or the network blocks the outbound port | Use the host and port from the endpoint table; allow outbound TCP to the gateway port |
TimeoutException from driver.get() | The page didn't load within the page load timeout | Check the gateway first with the ipify page, then raise the timeout for slow pages |
| Wrong exit country, or the country doesn't change | One forwarder and driver reused across countries | Start a new forwarder and driver per country or session; use GB, not UK |
netdash forwards your traffic unchanged. It doesn't solve CAPTCHAs, render JavaScript or retry requests. Targeting is country-level.
Automate only sites and workflows you are permitted to use, respect each site's terms and robots.txt, and keep your request rate reasonable. Don't access content behind logins or paywalls without authorization, and test only systems you own or are authorized to test. netdash does not solve CAPTCHAs: if a site rate-limits or challenges your session, slow down, check its terms, or use its official API. Read how we expect netdash to be used.
Next steps
Starting a new test suite? Playwright accepts the proxy username and password in its proxy option, for the whole browser or per context; see the Playwright proxy setup. The Puppeteer proxy setup covers page.authenticate, and browser automation shows the workflows these tools are used for. Every other tool is in the proxy integration guides. If you haven't generated credentials yet, the quickstart walks you through it.
- Playwright proxy setup
Proxy credentials per browser or per context, for Node.js and Python.
- Rotating and sticky sessions
Session IDs, TTL presets and when to rotate instead.
- Localization testing
Language, currency and geo-redirects as visitors in each country see them.
Stuck? See troubleshooting or email support with your username (without the password) and the error message.
Email supportSources
Third-party behaviour described on this page was checked against these documents.
- 01Chromium docs: Proxy support in Chrome (credentials embedded in proxy settings are not used)Accessed 2026-10-09
- 02Selenium docs: Browser options, proxyAccessed 2026-10-09
- 03Selenium docs: WebDriver BiDi network features (authentication handlers)Accessed 2026-10-09
- 04Selenium Python API: selenium.webdriver.common.bidi.network (add_auth_handler)Accessed 2026-10-09
- 05Python docs: asyncio streams (start_server, open_connection)Accessed 2026-10-09
- 06Playwright docs: Network, HTTP proxy (per browser or per browser context, optional username and password)Accessed 2026-10-09
Spotted something out of date? Tell us.