---
title: "Selenium Proxy in Python: Chrome, Edge, Auth and SOCKS5"
description: "To use a proxy in Selenium, pass --proxy-server in ChromeOptions or EdgeOptions. We cover user:pass auth via CDP, SOCKS5, Firefox, Java and rotation."
url: https://proxynet.io/blog/selenium
date: 2024-04-23
updated: 2026-09-25
author: "PXNET"
category: "Integration"
lang: en
---

# Selenium Proxy in Python: Chrome, Edge, Auth and SOCKS5

Your Selenium script works, but you need the pages as a visitor in Germany sees them, or the job must run from a server the site already rate-limits. You add a proxy and get a blank page with no error. The cause is usually the password: the WebDriver standard has no field for proxy credentials, so `user:pass@host:port` is either rejected or silently ignored.

This guide covers the proxy setup for Chrome, Edge and Firefox, the authentication methods that work today, SOCKS5, HTTPS, rotation and the error messages. Every Python example was run with Selenium 4.49, Chrome 154, Edge 153 and Firefox 156 on Windows 11 against a local test proxy that requires a username and password.

> **Note: Short answer**
>
> Add `--proxy-server=http://host:port` to `ChromeOptions` (or `EdgeOptions`) and start the driver with those options. Chrome ignores credentials in that address, so either whitelist your server's IP on the proxy, or answer the proxy's `407` challenge through the Chrome DevTools Protocol with the short helper below. Selenium Wire is archived and Chrome 137+ ignores command-line extensions, so the older recipes no longer work.

## What is Selenium?

Selenium drives a real web browser from code, mostly to test web applications and also to collect data from pages that only render with JavaScript. It opens pages, clicks, types, submits forms and reads the page content the way a user would. Your code calls the Selenium library, the library talks to a driver (ChromeDriver, msedgedriver or geckodriver), and the driver controls the browser through the W3C WebDriver protocol.

Selenium supports Chrome, Edge, Firefox and Safari, with official bindings for Python, Java, C#, Ruby and JavaScript. Since version 4.6 it ships with [Selenium Manager](https://www.selenium.dev/documentation/selenium_manager/), which finds the installed browser and downloads the matching driver. Still choosing a tool? See [Playwright vs Selenium](/blog/playwright-vs-selenium).

## How does a proxy work in Selenium?

In Selenium the proxy belongs to the browser session, not to a single request:

1. You put the proxy into the browser options, as the Chrome argument `--proxy-server` or as a `Proxy` object (the `proxy` capability of the WebDriver standard).
2. The driver starts the browser with that setting, and it applies to every tab until `driver.quit()`.
3. For an `https://` page the browser sends `CONNECT host:443` to the proxy and opens an encrypted tunnel. For an `http://` page it sends the full URL to the proxy.
4. If the proxy needs a login, it answers `407 Proxy Authentication Required`. This is the step the WebDriver standard does not cover.
5. The target site sees the proxy's exit IP. For a different proxy, you start a new browser.

Chrome also sends its own background traffic (updates, account services) through the proxy, which counts on a plan billed per gigabyte.

## How do you use Selenium with a proxy in Python?

**Step 1: Install Selenium.** Selenium 4.49 needs Python 3.10 or later:

```bash
pip install selenium
```

**Step 2: Get a proxy server.** Choose a product on the [proxy page](/proxy); the Endpoint Generator in the Proxynet dashboard gives you the gateway address, the port and a username that carries the country, city and session you picked. HTTP and SOCKS5 use separate ports on the same gateway.

**Step 3: The WebDriver.** The earlier version of this guide said to download ChromeDriver by hand and pass `executable_path`. That parameter is gone: Selenium Manager downloads the driver on the first run. If the machine needs a proxy to reach the internet, set `SE_PROXY` for Selenium Manager's downloads.

**Step 4: Start the browser with the proxy.** For a proxy that does not ask for a password (an IP whitelist, covered below):

```python
from selenium import webdriver
from selenium.webdriver.common.by import By

PROXY = "http://pr.proxynet.io:8000"

options = webdriver.ChromeOptions()
options.add_argument(f"--proxy-server={PROXY}")
options.add_argument("--headless")  # remove to watch the browser

driver = webdriver.Chrome(options=options)
try:
    driver.get("https://api.ipify.org/?format=json")
    print(driver.find_element(By.TAG_NAME, "body").text)  # {"ip": "<exit IP>"}
finally:
    driver.quit()
```

The printed IP should be the proxy's exit IP. Selenium's `Proxy` object does the same job (`options.proxy = Proxy({"proxyType": ProxyType.MANUAL, "httpProxy": "host:port", "sslProxy": "host:port"})`), and in our test both forms routed the traffic the same way.

## How do you use a proxy with a username and password in Selenium?

The [W3C WebDriver proxy definition](https://www.w3.org/TR/webdriver2/#proxy) has keys for proxy addresses but none for a username or password, and [Chromium's proxy documentation](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md) says Chrome does not use credentials embedded in the proxy settings. There is no `--proxy-auth` argument either; the old code in this post used one. Our tests against a proxy that requires a login:

| What we tried | What happened |
|---|---|
| `--proxy-server=http://host:port`, no credentials | The proxy returned `407`; no exception, blank page |
| `--proxy-server=http://user:pass@host:port` | `ERR_NO_SUPPORTED_PROXIES` error page, no exception |
| `user:pass@host:port` in the `Proxy` object | The page loaded, but Chrome connected directly, from our own IP |
| Selenium BiDi `driver.network.add_auth_handler()`, Chrome | Answering the challenge failed with `Invalid InterceptionId`, then a timeout |
| CDP `Fetch.continueWithAuth` (helper below) | Worked in Chrome 154 and Edge 153 |
| Port without a login (IP whitelist) | Worked |

The third row is the dangerous one: the script seems to work while the traffic skips the proxy.

### Route 1: IP whitelist

Add the public IP of the machine that runs the script to the proxy's allowed list, and the proxy stops asking for a password. The Step 4 code then works unchanged, and so do the Edge and Firefox versions. Proxynet accepts up to 10 IPs. This suits a server with a fixed IP, not a laptop whose IP changes; see [Proxy Authentication: User:Pass vs IP Whitelist](/blog/proxy-authentication-methods).

### Route 2: answer the 407 over the Chrome DevTools Protocol

Chrome and Edge expose the [Fetch domain of the DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/tot/Fetch/). With `handleAuthRequests` on, the browser pauses a request that needs credentials and fires `Fetch.authRequired`, whose `source` is `Server` or `Proxy`. The helper answers only proxy challenges; `websocket-client` comes with Selenium.

```python
# proxy_auth.py
import json
import threading
import urllib.request

import websocket  # installed together with selenium (websocket-client)

def attach_proxy_auth(driver, username, password):
    """Answers the tab's proxy 407 challenges over the Chrome DevTools Protocol."""
    caps = driver.capabilities
    vendor = caps.get("goog:chromeOptions") or caps.get("ms:edgeOptions")  # Chrome or Edge
    address = vendor["debuggerAddress"]
    with urllib.request.urlopen(f"http://{address}/json") as resp:
        targets = json.load(resp)
    page = next(t for t in targets if t["type"] == "page")
    ws = websocket.create_connection(page["webSocketDebuggerUrl"], suppress_origin=True)

    counter = {"id": 0}

    def send(method, params=None):
        counter["id"] += 1
        ws.send(json.dumps({"id": counter["id"], "method": method, "params": params or {}}))

    def listen():
        while True:
            try:
                msg = json.loads(ws.recv())
            except Exception:
                return  # the browser was closed
            method = msg.get("method")
            params = msg.get("params", {})
            if method == "Fetch.requestPaused":
                send("Fetch.continueRequest", {"requestId": params["requestId"]})
            elif method == "Fetch.authRequired":
                if params["authChallenge"]["source"] == "Proxy":
                    answer = {"response": "ProvideCredentials",
                              "username": username, "password": password}
                else:
                    answer = {"response": "Default"}  # the site's own login is not ours to answer
                send("Fetch.continueWithAuth", {"requestId": params["requestId"],
                                                "authChallengeResponse": answer})

    send("Fetch.enable", {"handleAuthRequests": True})
    while json.loads(ws.recv()).get("id") != counter["id"]:
        pass  # wait until interception is active before the first driver.get()
    threading.Thread(target=listen, daemon=True).start()
    return ws
```

Using it takes one line after the driver starts:

```python
from selenium import webdriver
from proxy_auth import attach_proxy_auth

options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://pr.proxynet.io:8000")
driver = webdriver.Chrome(options=options)
attach_proxy_auth(driver, "user", "pass")
driver.get("https://api.ipify.org/?format=json")
```

Limits: the helper covers only the tab it attached to, so a new window starts without it. With a wrong password Chrome shows `ERR_TOO_MANY_RETRIES`.

### What about Selenium Wire and proxy extensions?

The [Selenium Wire repository](https://github.com/wkeeling/selenium-wire) was archived on 3 January 2024, and in a fresh Python 3.13 environment `from seleniumwire import webdriver` failed for us with `ModuleNotFoundError: No module named 'pkg_resources'`. Auth extensions depended on the `--load-extension` switch, which official Chrome builds ignore from version 137; only Chrome for Testing and Chromium still accept it. SeleniumBase accepts `proxy="user:pass@host:port"` and handles the login itself; see [How to Use Proxy with SeleniumBase?](/blog/how-to-use-proxy-with-seleniumbase).

## Which proxy method should you use in Selenium?

| Method | Chrome / Edge | Firefox | Password | Status in 2026 |
|---|---|---|---|---|
| `--proxy-server` argument | Yes | No, use preferences | No | Current |
| `Proxy` object | Yes | Yes | Ignored | Current |
| IP whitelist | Yes | Yes | Not needed | Current |
| CDP helper | Yes | No | Yes | Tested, works |
| BiDi `add_auth_handler` | Failed in Chrome | Login prompt error | In theory | Not reliable yet |
| Selenium Wire | Import fails | Import fails | Yes | Archived |
| Auth extension | Chrome for Testing only | Separate format | Yes | Broken in Chrome 137+ |

## How do you set a proxy for Microsoft Edge in Selenium?

Edge is built on Chromium, so `EdgeOptions` takes the same argument, and Selenium Manager downloads msedgedriver:

```python
from selenium import webdriver

options = webdriver.EdgeOptions()
options.add_argument("--proxy-server=http://pr.proxynet.io:8000")
driver = webdriver.Edge(options=options)
```

The CDP helper works with Edge too; with Edge 153 and the password-protected test proxy, the page loaded through the proxy.

## How do you set a proxy in Selenium with Firefox?

Firefox does not read `--proxy-server`. Set its network preferences, or pass the `Proxy` object, which geckodriver turns into the same preferences. Firefox was not installed on our test machine; Selenium Manager downloaded Firefox 156 on the first run.

```python
from selenium import webdriver

options = webdriver.FirefoxOptions()
options.set_preference("network.proxy.type", 1)  # 1 = manual configuration
options.set_preference("network.proxy.http", "pr.proxynet.io")
options.set_preference("network.proxy.http_port", 8000)
options.set_preference("network.proxy.ssl", "pr.proxynet.io")  # used for https:// pages
options.set_preference("network.proxy.ssl_port", 8000)
driver = webdriver.Firefox(options=options)
```

For SOCKS5, set `network.proxy.socks`, `network.proxy.socks_port` and `network.proxy.socks_version` to `5`. Firefox then sends the domain name to the proxy, because `network.proxy.socks5_remote_dns` is on by default; the older `network.proxy.socks_remote_dns`, still found in many tutorials, made no difference for SOCKS5 in Firefox 156, while setting `socks5_remote_dns` to `False` made Firefox resolve names locally. Firefox has no preference for proxy credentials and the CDP helper does not apply to it. Selenium's BiDi `add_auth_handler` did send our credentials, but the page load still failed with `UnexpectedAlertPresentException` for the proxy login prompt, so use an IP whitelist.

## Can Selenium use a SOCKS5 proxy?

Yes, without a password: `--proxy-server=socks5://host:port`. In our test Chrome sent the domain name to the proxy instead of resolving it locally; Chromium's documentation confirms that with SOCKS5, name resolution always happens on the proxy side.

With a password, no: Chromium supports no SOCKS5 authentication, and `driver.get()` raised `net::ERR_SOCKS_CONNECTION_FAILED`. The CDP helper cannot help, because SOCKS has no `407` to answer. Use the SOCKS5 port with a whitelist, or the HTTP port with the helper. More in [What Is a SOCKS5 Proxy?](/blog/socks5-proxy-101) and [SOCKS vs. HTTP Proxy](/blog/socks-vs-http-proxy); Proxynet's [SOCKS5 Proxy](https://proxynet.io/socks5-proxy) runs on its own port of the same gateway.

## Does Selenium work with HTTPS sites through a proxy?

Yes. The proxy address keeps the `http://` scheme even when every page is `https://`: the browser opens a `CONNECT` tunnel and TLS runs end to end between the browser and the site, so the proxy sees the host name but not the content. That is why a `Proxy` object needs `sslProxy` as well as `httpProxy`; with only `httpProxy`, our `https://` pages went out directly.

An `https://` scheme in `--proxy-server` encrypts the connection to the proxy itself; see [HTTPS Proxy](https://proxynet.io/https-proxy).

## How do you integrate proxies with Selenium using Java?

Java uses the same `Proxy` object. The earlier example here put `user:pass@` into `setHttpProxy`, which in our test made Chrome skip the proxy, so the corrected version relies on an IP whitelist. It also drops `System.setProperty("webdriver.chrome.driver", …)`, because Selenium Manager finds the driver. Selenium 4 needs Java 11 or later.

```java
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;

public class SeleniumProxyExample {
    public static void main(String[] args) {
        // No credentials here: the machine's IP is on the proxy's whitelist
        String proxyAddress = "pr.proxynet.io:8000";

        Proxy proxy = new Proxy();
        proxy.setHttpProxy(proxyAddress);
        proxy.setSslProxy(proxyAddress);

        ChromeOptions options = new ChromeOptions();
        options.setProxy(proxy);

        // Selenium Manager finds or downloads the matching ChromeDriver
        WebDriver driver = new ChromeDriver(options);
        try {
            driver.get("https://api.ipify.org/?format=json");
            System.out.println(driver.getPageSource());
        } finally {
            driver.quit();
        }
    }
}
```

`options.addArguments("--proxy-server=http://pr.proxynet.io:8000")` is the equivalent of the Python argument. We wrote this example from Selenium's documentation and did not run it, because the test machine has only Java 8.

## How do you rotate residential proxies in Selenium?

The proxy is fixed for the life of a browser, so rotation means starting a new browser:

- **Rotating gateway.** With [Rotating Proxy](https://proxynet.io/rotating-proxy) the exit IP changes on the gateway side and your code keeps one address. A page load's connections can leave from different IPs: fine for product lists, a problem for a login flow.
- **Sticky sessions.** With [Sticky Proxy](https://proxynet.io/sticky-proxy) the IP stays the same for 1 to 60 minutes. Give each browser its own session username from the Endpoint Generator: one browser, one session, one exit IP.

Both work with [Residential Proxy](https://proxynet.io/residential-proxy) IPs; see [What Is IP Rotation?](/blog/ip-rotation-explained) and [How to Rotate Proxies in Python](/blog/how-to-rotate-proxies-in-python).

## Full example: parallel browsers with retries

The script reads six JavaScript-rendered pages of quotes.toscrape.com, a practice site. It runs three Chrome instances at once, starts a fresh browser on every attempt, blocks images to save traffic and retries temporary failures with backoff. Errors a retry cannot fix stop the task at once, including those that only show Chrome's error page.

```python
import random
import re
import time
from concurrent.futures import ThreadPoolExecutor, as_completed

from selenium import webdriver
from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from proxy_auth import attach_proxy_auth

PROXY_SERVER = "http://pr.proxynet.io:8000"
PROXY_USER = "user"
PROXY_PASS = "pass"
URLS = [f"https://quotes.toscrape.com/js/page/{n}/" for n in range(1, 7)]
WORKERS = 3  # browsers open at the same time
ATTEMPTS = 3
# A retry will not fix these: check the address, port and credentials first
FATAL = ("ERR_PROXY_CONNECTION_FAILED", "ERR_NO_SUPPORTED_PROXIES",
         "ERR_SOCKS_CONNECTION_FAILED", "ERR_TOO_MANY_RETRIES")

def new_driver():
    options = webdriver.ChromeOptions()
    options.add_argument(f"--proxy-server={PROXY_SERVER}")
    options.add_argument("--headless")
    # Skip images: less traffic through the proxy
    options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
    driver = webdriver.Chrome(options=options)
    driver.set_page_load_timeout(30)
    attach_proxy_auth(driver, PROXY_USER, PROXY_PASS)
    return driver

def scrape(url):
    for attempt in range(ATTEMPTS):
        driver = new_driver()  # a fresh browser, and a fresh proxy connection, on every attempt
        try:
            driver.get(url)
            # Some proxy errors open Chrome's error page without raising an exception
            if driver.execute_script("return document.body.className") == "neterror":
                code = re.search(r"ERR_[A-Z_]+", driver.page_source)
                raise WebDriverException(code.group(0) if code else "Chrome error page")
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, "div.quote span.text"))
            )
            quotes = driver.find_elements(By.CSS_SELECTOR, "div.quote span.text")
            return url, [q.text for q in quotes]
        except (TimeoutException, WebDriverException) as exc:
            if any(code in str(exc) for code in FATAL) or attempt == ATTEMPTS - 1:
                raise
            time.sleep(2**attempt + random.random())  # exponential backoff with jitter
        finally:
            driver.quit()

with ThreadPoolExecutor(max_workers=WORKERS) as pool:
    futures = [pool.submit(scrape, url) for url in URLS]
    for future in as_completed(futures):
        try:
            url, quotes = future.result()
            print(f"{url}: {len(quotes)} quotes")
        except Exception as exc:
            print("failed:", str(exc).splitlines()[0])
```

All six pages returned 10 quotes each in about 22 seconds; a closed port stopped every task with `net::ERR_PROXY_CONNECTION_FAILED` and a wrong password with `ERR_TOO_MANY_RETRIES`. Keep the worker count modest: each worker is a full Chrome, and too many parallel sessions lead to [429 Too Many Requests](/blog/http-429-too-many-requests).

## How do you check that Selenium really uses the proxy?

Open an IP echo page such as `api.ipify.org` in the proxied browser and compare it with your own IP; if they match, the traffic skips the proxy. Your dashboard should also show traffic. WebRTC can still reveal the local IP; see [WebRTC and DNS Leaks](/blog/webrtc-dns-leak). A full routine is in [How to Test a Proxy](/blog/how-to-test-a-proxy), and a one-line terminal check in [cURL with Proxy](/blog/curl-proxy).

## Common Selenium proxy errors and how to fix them

Messages from our tests. Chrome codes appear in the exception (`unknown error: net::ERR_…`) or only on the error page.

| Message | Cause | Fix |
|---|---|---|
| Blank page, empty `driver.title`, no exception | Proxy answered `407`, no credentials sent | Whitelist the IP or use the CDP helper |
| `ERR_NO_SUPPORTED_PROXIES` (error page) | Credentials inside `--proxy-server` | Remove `user:pass@` from the argument |
| `net::ERR_PROXY_CONNECTION_FAILED` | Nothing listens at that host and port | Check address, port and firewall |
| `net::ERR_TUNNEL_CONNECTION_FAILED` | The proxy could not open the tunnel | Check the target host, then the proxy |
| `net::ERR_SOCKS_CONNECTION_FAILED` | SOCKS5 proxy wants a password | HTTP port with the helper, or a whitelist |
| `ERR_TOO_MANY_RETRIES` (error page) | The proxy rejected the helper's credentials | Copy the username and password again |
| `TypeError: WebDriver.__init__() got an unexpected keyword argument 'executable_path'` | Selenium 3 code | Remove it; Selenium Manager finds the driver |
| `… unexpected keyword argument 'chrome_options'` | Same, older name | Use `options=` |
| `Timed out receiving message from renderer` | A page load never finished | Check the proxy, then retry |

Connection errors from plain Python HTTP clients are read line by line in [Max Retries Exceeded With URL](/blog/max-retries-exceeded-with-url).

## Use cases

- **Price monitoring** on JavaScript-heavy shops, from the market you sell in: [competitor price tracking](/blog/competitor-price-tracking) and [price monitoring](/price-monitoring).
- **Localisation and QA**: loading your own site from several countries before a release, see [app testing](/app-testing).
- **Ad verification**: checking your ads in the regions you pay for, see [ad verification](/ad-verification).
- **Data collection** from pages that need JavaScript: [data scraping](/data-scraping).

A proxy changes where the request comes from, not what the site allows: read its [robots.txt](/blog/robots-txt) and terms, keep request rates low and prefer an official API. It also does not hide automation; a browser under WebDriver control reports `navigator.webdriver` as `true`, as the standard requires.

## Decision guide

| Need | Recommendation |
|---|---|
| Server with a fixed IP | IP whitelist + `--proxy-server`, no password in code |
| Laptop or CI runner with a changing IP | HTTP port + the CDP helper |
| Firefox | Network preferences + IP whitelist |
| SOCKS5 | `socks5://` in `--proxy-server`, whitelist only |
| Login or checkout flow | Sticky session, one browser per session |
| Many independent pages | Rotating gateway |
| A different proxy per page in one browser | Playwright's per-context proxy, see [Playwright with a proxy](/blog/playwright-proxy) |

## Frequently asked questions

### Can Selenium use a proxy with a username and password?

Not through the standard options, which have no credential fields. In Chrome and Edge the DevTools helper in this post answers the proxy's `407`; in any browser an IP whitelist removes the need for a password.

### Does Selenium Wire still work in 2026?

It was archived on 3 January 2024, and in a fresh Python 3.13 environment it did not import for us, because it relies on `pkg_resources`, which current setuptools no longer ships. Use the helper or a whitelist.

### How do I change the proxy without restarting the browser?

The proxy is fixed when the session starts: call `driver.quit()` and start a new driver. For a new IP per page inside one browser, use a rotating gateway, or Playwright, which takes a proxy per context.

### Does a proxy work in headless mode?

Yes. `--proxy-server` and the CDP helper behave the same with and without `--headless`; all examples in this post ran headless.

### Can I use a SOCKS5 proxy with authentication in Selenium?

Not in Chrome or Edge, which support no SOCKS5 authentication; Firefox 156 also offered our proxy only the no-password method. Use SOCKS5 with a whitelist, or the HTTP port with a password.

### Why does the site still show my real IP?

Usually the proxy was never applied: credentials inside the `Proxy` object make Chrome connect directly, and a `Proxy` object without `sslProxy` leaves `https://` pages unproxied. If the IP is right but the location looks wrong, test for WebRTC leaks.

## Summary

Selenium sets the proxy once per browser session: `--proxy-server` for Chrome and Edge, preferences or the `Proxy` object for Firefox. For authentication, use an IP whitelist or, in Chrome and Edge, the DevTools helper that answers the `407`; Selenium Wire and command-line extensions are dead ends. SOCKS5 works only without a password, and rotation means one browser per IP or session. When the setup is ready, pick a plan on the [Proxynet proxy page](/proxy) and take the gateway details from the Endpoint Generator.
