15% off your first purchase with code WELCOME15, up to 25 GB.

See pricing

Integration · Browser automation

How to use a proxy with Selenium (Python)

Selenium sets a proxy's host and port through options.proxy, but neither Selenium's proxy capability nor Chrome's --proxy-server has a field for a username and password. The dependable way to use an authenticated proxy is a small local forwarder: Selenium talks to it without credentials, and it adds them on the way to the ARC gateway.

Reviewed 30 Oct 2026

your proxy, as a URL
http://USERNAME_pool-arcfast_country-de:PASSWORD@proxy.arcproxies.io:8000

The short answer

  • Selenium's Proxy object takes host and port only; it cannot carry proxy credentials.
  • A local mitmdump in upstream mode with --upstream-auth adds the credentials, and --ignore-hosts '.*' keeps TLS untouched end to end.
  • One forwarder per identity: each sticky session or country gets its own local port.
  • selenium-wire, the usual answer in older tutorials, has been archived and unmaintained since January 2024.

Setup

Set up Selenium with a proxy, step by step

  1. 01

    Install Selenium and mitmproxy

    Selenium 4 manages the driver itself through Selenium Manager. mitmproxy provides mitmdump, the command-line forwarder.

    shell
    pip install selenium mitmproxy
  2. 02

    Start a local forwarder with the credentials

    Upstream mode sends every request to the ARC gateway with Basic proxy authentication added. --ignore-hosts '.*' tells mitmproxy not to process any traffic, so HTTPS stays an untouched CONNECT tunnel and no certificate needs installing. The forwarder only listens on localhost.

    shell
    mitmdump --listen-host 127.0.0.1 -p 8081 \  --mode upstream:http://proxy.arcproxies.io:8000 \  --upstream-auth "USERNAME_pool-arcfast_country-de_session-k3f9a1x2_sesstime-10:PASSWORD" \  --ignore-hosts '.*'
  3. 03

    Point Chrome at the forwarder and check the exit IP

    Set both httpProxy and sslProxy: with only httpProxy, HTTPS pages bypass the proxy. The IP echo page should show the gateway's exit, not your own address.

    check_exit.py
    from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.common.proxy import Proxy, ProxyTypeLOCAL = "127.0.0.1:8081"   # the mitmdump forwarder, no credentials neededoptions = webdriver.ChromeOptions()options.add_argument("--headless=new")options.proxy = Proxy({"proxyType": ProxyType.MANUAL, "httpProxy": LOCAL, "sslProxy": LOCAL})driver = webdriver.Chrome(options=options)driver.set_page_load_timeout(30)try:    driver.get("https://api.ipify.org?format=json")    print(driver.find_element(By.TAG_NAME, "body").text)   # {"ip":"…"}finally:    driver.quit()
  4. 04

    One forwarder per session for parallel workers

    A forwarder carries one username, so it carries one identity. For parallel workers, start one per worker on its own port, each with a fresh _session- handle, and stop it when the worker finishes.

    workers.py
    import secretsimport subprocessimport timefrom contextlib import contextmanagerBASE = "USERNAME_pool-arcfast_country-de"   # pool + country; the session is added per worker@contextmanagerdef forwarder(port, username):    proc = subprocess.Popen([        "mitmdump", "--listen-host", "127.0.0.1", "-p", str(port),        "--mode", "upstream:http://proxy.arcproxies.io:8000",        "--upstream-auth", f"{username}:PASSWORD",        "--ignore-hosts", ".*",    ], stdout=subprocess.DEVNULL)    time.sleep(2)                                # give it a moment to bind    try:        yield f"127.0.0.1:{port}"    finally:        proc.terminate()        proc.wait()for worker, port in enumerate(range(8081, 8085)):    user = f"{BASE}_session-{secrets.token_hex(4)}_sesstime-15"    with forwarder(port, user) as local:        ...  # build ChromeOptions with Proxy(httpProxy=local, sslProxy=local) as above
  5. 05

    Geo-target by changing the forwarder's username

    Country and city live in the username, so a market is a forwarder. _country- is a lowercase ISO code; _city- comes from the published list, lowercased without spaces.

    shell
    # Hamburg on port 8091, Paris on port 8092mitmdump --listen-host 127.0.0.1 -p 8091 --mode upstream:http://proxy.arcproxies.io:8000 \  --upstream-auth "USERNAME_pool-arcfast_country-de_city-hamburg:PASSWORD" --ignore-hosts '.*'mitmdump --listen-host 127.0.0.1 -p 8092 --mode upstream:http://proxy.arcproxies.io:8000 \  --upstream-auth "USERNAME_pool-arcfast_country-fr_city-paris:PASSWORD" --ignore-hosts '.*'
  6. 06

    Retry page loads that time out

    Through a proxy, a page that never finishes loading is the usual failure. Set a page-load timeout, catch TimeoutException and WebDriverException, back off, and on the next attempt use a forwarder with a new handle. If the forwarder logs 407 responses, the credentials are wrong and retrying will not help.

    retry.py
    import timefrom selenium.common.exceptions import TimeoutException, WebDriverExceptiondef load(driver, url, attempts=3):    for attempt in range(attempts):        try:            driver.get(url)            # raises TimeoutException after set_page_load_timeout            return driver.page_source        except (TimeoutException, WebDriverException) as err:            print(f"attempt {attempt + 1}: {err.msg}")            time.sleep(2 ** attempt)    raise RuntimeError(f"gave up on {url}")

Why Selenium cannot pass proxy credentials directly

Selenium's proxy setting is the WebDriver proxy capability: a proxy type plus httpProxy, sslProxy and similar host:port fields. There is no username or password field, and Chrome's own --proxy-server switch has none either. When the gateway asks for credentials, a plain Selenium session has nothing to answer with.

ApproachStatusNotes
Local forwarder (mitmdump upstream mode)Works; maintainedBrowser-agnostic, no TLS interception with --ignore-hosts. One process per identity.
WebDriver BiDi auth handler (driver.network.add_auth_handler)In Selenium 4's Python APIThe BiDi spec covers 407 challenges. It did not complete a proxy challenge in our test with Selenium 4.52 and Chrome, so test it on your versions before relying on it.
selenium-wireArchived January 2024Read-only and unmaintained. Avoid for new code.

Rotating vs sticky sessions with Selenium

A browser page load opens many connections, and a rotating username lets each one take a different exit. For Selenium, use a sticky session per worker: {{USER}}_session-<handle>_sesstime-<minutes>, with minutes that cover the whole task. For a new IP, start a new forwarder with a new handle rather than changing the old one.

Concurrency is then a question of how many forwarders and Chrome instances the machine can run, and how many requests per minute the target tolerates. Handling 429 rate limits covers backing off when it says no.

Firefox, Edge and other browsers

The forwarder is an ordinary unauthenticated HTTP proxy on localhost, so the same Proxy object works with webdriver.Firefox and webdriver.Edge. The proxy capability is part of the WebDriver standard, not a Chrome feature.

The problem

Common pitfalls

Setting only httpProxy

Without sslProxy, Chrome sends HTTPS pages directly, from your own IP. Set both to the forwarder's address.

Exposing the forwarder

Bind it to 127.0.0.1. A forwarder on 0.0.0.0 is an open proxy that spends your bandwidth for anyone who finds it.

Letting mitmproxy intercept TLS

Without --ignore-hosts '.*', mitmproxy decrypts HTTPS and Chrome rejects its certificate. Passing traffic through untouched is what you want here.

Browser background traffic

Chrome's own update and account checks also go through the proxy and count as bandwidth. They showed up in our forwarder logs within seconds of launch.

FAQ

Questions

Something not covered? Write to contact@arcproxies.io.

How do I use an authenticated proxy with Selenium in Python?

Run a local forwarder that adds the credentials, for example mitmdump --mode upstream:http://host:port --upstream-auth user:pass --ignore-hosts '.*', and point Selenium's Proxy object (httpProxy and sslProxy) at it. Selenium itself has no field for proxy credentials.

Is selenium-wire still maintained?

No. Its repository was archived on 3 January 2024 and the README says it is no longer maintained. It may still run, but it will not get fixes for new browser or Python versions.

Can I put the username and password in --proxy-server?

No. Chrome's --proxy-server takes a scheme, host and port. Credentials in that string are not used to answer the proxy's 407 challenge.

How do I rotate IPs in Selenium?

Start each worker with its own forwarder and a fresh _session- handle in the username, and quit both when the task ends. The next task gets a new handle and therefore a new exit IP.

Does this work with SOCKS5?

The forwarder talks to the gateway over HTTP, and the gateway also speaks SOCKS5 on the same port. For Selenium there is no advantage to SOCKS5: HTTPS still runs end to end inside a CONNECT tunnel.

Sources & review

Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.

  1. [1]Selenium: Browser options: proxy
  2. [2]Selenium: WebDriver BiDi network features
  3. [3]mitmproxy: Options: upstream_auth, ignore_hosts, mode
  4. [4]GitHub, wkeeling/selenium-wire: Selenium Wire README (archived)

Next

What people build with it

Use caseProxies for QA, app and localisation testingSee your own site the way a user in Tokyo, São Paulo or Munich sees it — currency, language, consent banner, CDN edge and all — before they report it.Use caseProxies for web scraping and data collectionA second provider that just works — Scrapy, requests, Crawlee or Playwright, self-serve, no sales call, bandwidth that never expires.GlossaryHTTP CONNECTHTTP CONNECT is the HTTP request method a client sends to a proxy to ask for a tunnel to a host and port; once the proxy answers with a 2xx status it blindly forwards bytes in both directions, which is how HTTPS passes through an HTTP proxy with TLS intact end to end (RFC 9110, section 9.3.6).GlossarySticky sessionA sticky session is a setting on a rotating proxy that keeps the same exit IP address for a chosen period, usually by attaching a session identifier to the connection, so a multi-step task appears to come from one address from start to finish.GuideHandling 429 rate limitsWhat HTTP 429 means, how to read Retry-After, and how to retry politely with exponential backoff, jitter and per-host concurrency limits. With code.IntegrationPlaywrightSet up an authenticated proxy in Playwright for Python and Node: launch and per-context proxies, sticky sessions, geo-targeting and handling proxy errors.Use caseProxies for QA, app and localisation testingSee your own site the way a user in Tokyo, São Paulo or Munich sees it — currency, language, consent banner, CDN edge and all — before they report it.

Get started

Get credentials and paste them in.

Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.