Skip to content

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:

Checked against a local test proxy
netdash_forwarder.py
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 proxy
chrome_proxy.py
import 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 on 127.0.0.1 and returns its address, for example http://127.0.0.1:53211.
  • For every connection from the browser, it opens a connection to the gateway and adds a Proxy-Authorization header with your username and password. The gateway never has to send a 407 challenge.
  • 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 from NETDASH_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 proxy
netdash_driver.py
import 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

SymptomCauseFix
net::ERR_NO_SUPPORTED_PROXIESCredentials inside --proxy-serverPass the forwarder's address, which has no credentials
net::ERR_INVALID_AUTH_CREDENTIALSChrome points at the gateway directly, or the password or username parameters are wrongPass 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 pageThe forwarder can't reach the gateway: wrong host and port pair, or the network blocks the outbound portUse 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 timeoutCheck the gateway first with the ipify page, then raise the timeout for slow pages
Wrong exit country, or the country doesn't changeOne forwarder and driver reused across countriesStart 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.

Stuck? See troubleshooting or email support with your username (without the password) and the error message.

Email support

Sources

Third-party behaviour described on this page was checked against these documents.

  1. 01Chromium docs: Proxy support in Chrome (credentials embedded in proxy settings are not used)Accessed 2026-10-09
  2. 02Selenium docs: Browser options, proxyAccessed 2026-10-09
  3. 03Selenium docs: WebDriver BiDi network features (authentication handlers)Accessed 2026-10-09
  4. 04Selenium Python API: selenium.webdriver.common.bidi.network (add_auth_handler)Accessed 2026-10-09
  5. 05Python docs: asyncio streams (start_server, open_connection)Accessed 2026-10-09
  6. 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.

Privacy choices

We use cookies and similar technologies that this site needs to work and to remember your choice. We also use Google Analytics to understand which pages and features people use: in the EEA, the UK and Switzerland only with your consent. Live chat loads only if you open it. You can change your choice at any time under “Privacy choices” in the footer. Privacy Policy

Privacy choices

Choose what netdash.io may use. You can change this at any time under “Privacy choices” in the footer.

Necessary (always on)

Keeps the site working and secure, and remembers your privacy choices (nd_consent, 12 months).

In the EEA, the UK and Switzerland it stays off until you switch it on; elsewhere it is on unless you switch it off here. Google Analytics 4 measures page views, clicks on links and buttons, use of the price calculator, connection builder and code examples, scroll depth, file downloads, and page-performance metrics. It sets the cookies _ga and _ga_96MPNM6GHH, which last up to 12 months. Google processes this data, which may involve transfer to the United States. We do not send your name, email address or account details to Google Analytics. With this switched on we also remember a referral code or campaign you arrived with (nd_attr, 30 days), so a partner can be credited if you sign up.

Support chat (loads only when you open it)

Our live chat is provided by LiveChat. It loads only when you open the chat window. LiveChat then sets its own cookies and processes the messages you send and technical data about your browser.