Back to blog

Guide

Selenium Proxy Setup: Chrome, Firefox and Authenticated Proxies

How to configure a proxy in Selenium for Chrome and Firefox, why authenticated proxies are genuinely hard in Selenium, and the three workarounds that actually work.

Selenium can use a proxy in one line, and cannot use an authenticated proxy at all without a workaround. That gap is the whole story of Selenium proxy configuration, and it is worth knowing before you start rather than after a 407.

from selenium import webdriver
 
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://gateway.example.com:8000")
 
driver = webdriver.Chrome(options=options)
driver.get("https://api.ipify.org")
print(driver.find_element("tag name", "body").text)
driver.quit()

That works for an unauthenticated proxy. Add credentials to the URL and it silently does not.

Selenium Proxy Setup

Why credentials in the flag do not work

Chromium's --proxy-server flag parses a host and a port. Credentials written as http://user:pass@host:port are discarded. The proxy then issues a 407 challenge, Chrome raises a native authentication dialog, and Selenium cannot touch it — that dialog is browser chrome, not page content, so there is no element to find.

This is not a Selenium bug and there is no flag that fixes it. You need one of three approaches.

Workaround 1: IP allowlisting (simplest)

If your provider supports authenticating by IP instead of credentials, the problem disappears:

options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://gateway.example.com:8000")
driver = webdriver.Chrome(options=options)

Add your server's public IP to the provider's allowlist and no credentials are needed. This is the cleanest answer whenever your egress address is stable, and it is worth choosing a provider partly on whether it is offered. See proxy authentication methods.

The limitation is a dynamic IP — a laptop on changing networks, or autoscaling infrastructure. Then you need one of the next two.

Workaround 2: a generated Chrome extension

Chrome extensions can supply proxy credentials programmatically. You build a tiny one at runtime and load it:

import os, zipfile, tempfile
from selenium import webdriver
 
def proxy_auth_extension(host, port, user, password):
    manifest = """
    {
      "manifest_version": 3,
      "name": "Proxy Auth",
      "version": "1.0",
      "permissions": ["proxy", "webRequest", "webRequestAuthProvider"],
      "host_permissions": ["<all_urls>"],
      "background": { "service_worker": "background.js" }
    }
    """
    background = """
    chrome.proxy.settings.set({
      value: {
        mode: "fixed_servers",
        rules: {
          singleProxy: { scheme: "http", host: "%s", port: parseInt(%s) },
          bypassList: ["localhost", "127.0.0.1"]
        }
      },
      scope: "regular"
    });
 
    chrome.webRequest.onAuthRequired.addListener(
      () => ({ authCredentials: { username: "%s", password: "%s" } }),
      { urls: ["<all_urls>"] },
      ["blocking"]
    );
    """ % (host, port, user, password)
 
    path = os.path.join(tempfile.mkdtemp(), "proxy_auth.zip")
    with zipfile.ZipFile(path, "w") as zf:
        zf.writestr("manifest.json", manifest)
        zf.writestr("background.js", background)
    return path
 
options = webdriver.ChromeOptions()
options.add_extension(
    proxy_auth_extension(
        "gateway.example.com", 8000,
        os.environ["PROXY_USER"], os.environ["PROXY_PASS"],
    )
)
driver = webdriver.Chrome(options=options)

This works and needs no third-party library. Two caveats: extensions do not load in headless mode in some Chrome versions, and manifest and permission requirements change between Chrome releases — so treat this as something to re-verify after browser upgrades rather than set-and-forget.

Workaround 3: selenium-wire

selenium-wire extends Selenium with request interception and native proxy authentication:

from seleniumwire import webdriver
 
options = {
    "proxy": {
        "http":  "http://user:pass@gateway.example.com:8000",
        "https": "http://user:pass@gateway.example.com:8000",
        "no_proxy": "localhost,127.0.0.1",
    }
}
 
driver = webdriver.Chrome(seleniumwire_options=options)
driver.get("https://api.ipify.org")

Most convenient of the three. The trade-off is architectural: it works by running a local intercepting proxy between the browser and your upstream proxy, so you have added a component that can fail, and TLS is terminated locally. Fine in most cases, worth knowing in regulated environments.

Firefox

Firefox has real proxy preferences, and unlike Chrome it prompts for credentials in a way that is at least addressable:

from selenium import webdriver
 
options = webdriver.FirefoxOptions()
options.set_preference("network.proxy.type", 1)          # 1 = manual
options.set_preference("network.proxy.http", "gateway.example.com")
options.set_preference("network.proxy.http_port", 8000)
options.set_preference("network.proxy.ssl", "gateway.example.com")
options.set_preference("network.proxy.ssl_port", 8000)
options.set_preference("network.proxy.no_proxies_on", "localhost, 127.0.0.1")
 
driver = webdriver.Firefox(options=options)

For SOCKS5, set network.proxy.socks, network.proxy.socks_port, network.proxy.socks_version to 5, and — importantly — network.proxy.socks_remote_dns to True, so hostnames resolve at the proxy rather than locally. Same distinction as socks5h in cURL and Python requests.

Rotation

The proxy is fixed for the life of a driver session, so rotation means new sessions:

def fetch_with_fresh_ip(url):
    options = webdriver.ChromeOptions()
    options.add_argument("--proxy-server=http://gateway.example.com:8000")
    driver = webdriver.Chrome(options=options)
    try:
        driver.get(url)
        return driver.page_source
    finally:
        driver.quit()

A rotating gateway gives each new session a different exit. Driver startup is expensive — often a second or more — so batch as much work as possible per session rather than restarting per URL. Proxy rotation strategies covers when per-request rotation is actually needed.

Matching the browser to the exit

As always, worth more than IP quality on its own:

options.add_argument("--lang=de-DE")
options.add_experimental_option("prefs", {
    "intl.accept_languages": "de-DE,de",
})

Timezone is harder in plain Selenium than in Playwright — it generally requires a CDP call or accepting the host's timezone. That is one concrete reason to prefer Playwright for detection-sensitive work: locale and timezone_id are arguments on the context. See anti-bot detection in 2026.

Verifying the exit

driver.get("https://api.ipify.org")
print(driver.find_element("tag name", "body").text.strip())

Run it in the deployed environment. Locally it passes whether or not the proxy is working, because your machine's IP is already residential.

Common errors

Native auth dialog appears. Credentials in --proxy-server. Use one of the three workarounds.

ERR_PROXY_CONNECTION_FAILED. Wrong host or port, or proxy unreachable.

Extension not loading. Headless mode, or a manifest version Chrome no longer accepts. Verify after Chrome upgrades.

Works in Firefox, fails in Chrome. Almost always the authentication difference.

selenium-wire certificate warnings. Expected — it terminates TLS locally. Install its CA or scope trust to your test environment.

Blocked despite a working proxy. Selenium-driven browsers expose automation markers, and headless is detectable independently of the IP. The proxy is the IP layer only.

Should you use Selenium for this?

Honestly: if the choice is open, Playwright is the better tool for proxied browser automation. It takes authenticated proxies natively, supports a different proxy per browser context, and sets locale and timezone in the same call — no extension generation, no interception layer.

Selenium remains right when you have an existing suite, need a browser Playwright does not drive, or have infrastructure built around Grid. In that case, prefer IP allowlisting and skip the authentication problem entirely.

FlameProxies supports both credential and IP-allowlist authentication across 80M-plus residential IPs in 180-plus countries at $0.50/GB, dropping to $0.45/GB above 1TB — the allowlist option is what makes Selenium straightforward. Current rates are on the pricing page.

Frequently asked questions

How do I set a proxy in Selenium?
For Chrome, add the proxy as a browser argument: options.add_argument('--proxy-server=http://host:port'). For Firefox, set the network.proxy preferences on a FirefoxProfile or use the Proxy capability. Both configure an unauthenticated proxy; credentials need one of the workarounds below.
How do I use an authenticated proxy with Selenium?
Selenium has no built-in mechanism for it, which is the honest answer. Three workarounds work: use IP allowlisting instead of credentials, which is by far the simplest; generate a small Chrome extension that supplies the credentials programmatically; or use selenium-wire, a third-party library that adds proxy authentication support.
Why does user:pass in --proxy-server not work in Selenium?
Because Chromium ignores credentials in that flag entirely. It parses only the host and port, so the proxy issues a 407 challenge and nothing in the driver answers it. Chrome then shows a native authentication dialog, which Selenium cannot interact with because it is browser chrome rather than page content.
Is selenium-wire still the best option for authenticated proxies?
It is the most convenient if you are committed to Selenium, since it handles authentication in one configuration block. But it works by running a local intercepting proxy, which adds a component to your stack. If you are able to switch, Playwright supports authenticated proxies natively and is a smaller change than most people expect.
How do I rotate proxies in Selenium?
Point at a rotating gateway endpoint and start a new driver session per exit, since the proxy is fixed for the life of a driver. Driver startup is expensive, so batch work per session rather than restarting per request.
Should I use Selenium or Playwright for proxied scraping?
Playwright, if the choice is open. It supports authenticated proxies natively, allows a different proxy per browser context, and sets locale and timezone in the same call. Selenium is the right answer when you have an existing Selenium suite or need a browser Playwright does not drive.