Skip to main content

Using Proxies With Python Selenium

UpdatedMay 13, 2026

Using Proxies With Python Selenium (2026 Guide)

TL;DR — For an unauthenticated proxy, pass --proxy-server=http://host:port via Chrome options. For a username/password proxy in 2026, intercept the auth challenge via the Chrome DevTools Protocol (Fetch.continueWithAuth); the older "in-memory Chrome extension" trick has become unreliable in recent Chrome versions and selenium-wire no longer imports on modern Python. For high-throughput rotation, use a gateway-style proxy provider.

Selenium is a powerful browser automation library that allows you to build bots and scrapers that can load and interact with web pages in the browser. As a result, Selenium remains popular amongst the Python web scraping community in 2026 — though for many scraping workloads, undetected-chromedriver, nodriver and Playwright have taken over for everything where Cloudflare or DataDome is in the picture.

In this guide for The Python Selenium Web Scraping Playbook, we will look at how to integrate proxies into our Python Selenium scraper using Selenium 4.x syntax. There are several types of proxy you need to integrate differently with Selenium, and we walk through each:

Need help scraping the web?

Then check out ScrapeOps, the complete toolkit for web scraping.


Quick reference: which proxy method to use

ScenarioRecommended method
Unauthenticated proxy (single IP)--proxy-server argument on ChromeOptions / FirefoxOptions
Authenticated proxy (username/password)Chrome DevTools Protocol via Fetch.continueWithAuth (see below)
Authenticated proxy without code complexityUse your provider's IP-whitelisting feature so credentials aren't needed
Rotating proxiesUse a gateway-style provider; rotate server-side rather than restarting the browser per request
Smart proxy API (Cloudflare/DataDome bypass)Proxy port integration — e.g. ScrapeOps Proxy Aggregator at proxy.scrapeops.io:5353
Need to inspect/modify requests as well as proxy themmitmproxy in front of Selenium, or Network.* CDP commands (selenium-wire no longer imports on modern Python)

Using Proxies With Selenium

The simplest type of proxy to integrate with Python Selenium is an unauthenticated HTTP proxy expressed as an IP address and port. For example:


"198.51.100.7:8080"

Depending on which type of browser you are using the integration method is slightly different. Both examples below use Selenium 4.x syntax — note that the chrome_options= and executable_path= keyword arguments shown in older guides are deprecated and don't work on Selenium 4.10+. Use options= and the Service object instead.

Integrating Proxy With Selenium Chrome Browser

To integrate an unauthenticated proxy into a Selenium scraper that uses Chrome, set the --proxy-server argument on ChromeOptions:


from selenium import webdriver
from selenium.webdriver.chrome.options import Options

PROXY = "198.51.100.7:8080"

options = Options()
options.add_argument(f"--proxy-server=http://{PROXY}")

# Selenium 4.6+ ships with Selenium Manager, so no Service/driver-path needed.
driver = webdriver.Chrome(options=options)
driver.get("https://httpbin.org/ip")
print(driver.find_element("tag name", "body").text)
driver.quit()

The same --proxy-server flag accepts HTTPS and SOCKS proxies — prefix it with the right scheme: http://, https://, socks5://. When you run the script you'll see Selenium reporting the proxy IP back from httpbin:


{
"origin": "198.51.100.7:8080"
}


Integrating Proxy With Selenium Firefox Browser

For Firefox, set the proxy in FirefoxOptions directly — the legacy Proxy/ProxyType API still works but the options.proxy approach is cleaner:


from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.proxy import Proxy, ProxyType

proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = "198.51.100.7:8080"
proxy.ssl_proxy = "198.51.100.7:8080"

options = Options()
options.proxy = proxy

driver = webdriver.Firefox(options=options)
driver.get("https://httpbin.org/ip")
driver.quit()

HTTP Proxy Authentication

These two methods only work when the proxy doesn't require username/password authentication. Chrome blocks the http://user:pass@host:port URL form in headless mode, so credentials need a different approach — see the next section.


Using Authenticated Proxies With Selenium (CDP)

The above method doesn't work if you need to use proxies that require username and password authentication. Most commercial proxy providers sell access to their pools via a single endpoint authenticated with a username/password pair:


http://USERNAME:PASSWORD@proxy-server:8080

In 2026 the cleanest way to answer the proxy auth challenge is via the Chrome DevTools Protocol — specifically the Fetch.continueWithAuth event. The widely-shared "build an in-memory Chrome extension" trick has become unreliable in recent Chrome versions: Chrome silently disables --load-extension for non-test invocations, and Manifest V3's service-worker lifecycle means the webRequest.onAuthRequired listener often hasn't started by the time the first navigation happens. The CDP approach has none of those timing concerns.


import json
import threading

import requests # already in your scraping stack
import websocket # pip install websocket-client
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


class ChromeWithAuthProxy:
"""Selenium 4 Chrome driver that handles authenticated HTTP proxies via CDP."""

def __init__(self, proxy_host, proxy_port, proxy_user, proxy_pass):
self.proxy_host = proxy_host
self.proxy_port = proxy_port
self.proxy_user = proxy_user
self.proxy_pass = proxy_pass
self._ws = None
self._cmd_id = 0
self._lock = threading.Lock()

def _cdp_send(self, method, params=None):
with self._lock:
self._cmd_id += 1
msg = {"id": self._cmd_id, "method": method}
if params is not None:
msg["params"] = params
self._ws.send(json.dumps(msg))

def _cdp_loop(self):
while True:
try:
msg = json.loads(self._ws.recv())
except Exception:
return
if msg.get("method") == "Fetch.authRequired":
self._cdp_send("Fetch.continueWithAuth", {
"requestId": msg["params"]["requestId"],
"authChallengeResponse": {
"response": "ProvideCredentials",
"username": self.proxy_user,
"password": self.proxy_pass,
},
})
elif msg.get("method") == "Fetch.requestPaused":
self._cdp_send("Fetch.continueRequest", {
"requestId": msg["params"]["requestId"],
})

def new_driver(self):
options = Options()
options.add_argument(f"--proxy-server=http://{self.proxy_host}:{self.proxy_port}")
options.add_argument("--remote-allow-origins=*") # allow our CDP websocket
driver = webdriver.Chrome(options=options)

# Open a CDP websocket to the page target and enable Fetch with auth handling.
debugger_address = driver.capabilities["goog:chromeOptions"]["debuggerAddress"]
targets = requests.get(f"http://{debugger_address}/json", timeout=10).json()
page_target = next(t for t in targets if t["type"] == "page")
self._ws = websocket.create_connection(page_target["webSocketDebuggerUrl"])
threading.Thread(target=self._cdp_loop, daemon=True).start()
self._cdp_send("Fetch.enable", {
"handleAuthRequests": True,
"patterns": [{"urlPattern": "*"}],
})
return driver


# Usage:
proxy = ChromeWithAuthProxy(
proxy_host="proxy-server.example.com",
proxy_port=8080,
proxy_user="USERNAME",
proxy_pass="PASSWORD",
)
driver = proxy.new_driver()
driver.get("https://httpbin.org/ip")
print(driver.find_element("tag name", "body").text)
driver.quit()

A few notes on this pattern:

  • Works the same in headless and non-headless Chrome.
  • No extension files on disk — the credentials live in your Python process, not a temp zip Chrome could pick up.
  • The --remote-allow-origins=* flag is required for Selenium 4 to allow our local CDP websocket connection.
  • If you'd rather not write this wrapper yourself, seleniumbase ships a Driver(proxy="user:pass@host:port", ...) factory that handles the same problem with a battery-included extension approach.

Using Authenticated Proxies With selenium-wire (Legacy)

selenium-wire is broken on modern Python

selenium-wire was archived upstream and as of 2026 the package no longer imports on recent versions of blinker (blinker 1.8+ removed the internal _saferef module that selenium-wire's vendored mitmproxy depended on). For new projects, use the CDP-based approach above — it doesn't depend on selenium-wire and works on Chrome 148+.

If you have an existing codebase pinned to old blinker and selenium-wire==5.1.0 it will still run, but pinning yourself to a fork or yanked dependency tree is not a long-term solution. For request/response inspection (which is what selenium-wire was best at) we now recommend mitmproxy in front of vanilla Selenium, or the CDP Network domain via driver.execute_cdp_cmd("Network.enable", ...).

For deeper coverage of selenium-wire's broader features, see our Selenium Wire guide.


Integrating Proxy APIs

Over the last few years there has been a huge surge in proxy providers that offer smart proxy solutions that handle all the proxy rotation, header selection, ban detection and retries on their end. These smart APIs typically provide their proxy services in a API endpoint format.

However, these proxy API endpoints don't integrate well with headless browsers when the website is using relative links — Selenium will try to attach the relative URL onto the proxy API endpoint, not the website's root URL, so some pages don't load correctly.

As a result, when integrating proxy APIs into your Selenium scrapers it is recommended that you use the provider's proxy port integration over the API endpoint integration where one is available.

For example, the ScrapeOps Proxy Aggregator offers a proxy port integration for exactly this. The proxy port is a light front-end for the API with all the same functionality and performance as the endpoint, but you integrate it like any normal proxy.

The cleanest 2026 way to wire that proxy port into Selenium is with the same CDP-based wrapper from the previous section (because the proxy port also needs auth):


# Re-uses the ChromeWithAuthProxy class defined earlier in this guide.

SCRAPEOPS_API_KEY = "YOUR_API_KEY"

scrapeops = ChromeWithAuthProxy(
proxy_host="proxy.scrapeops.io",
proxy_port=5353,
proxy_user="scrapeops", # add .country=us etc. via .headless_browser_mode=true
proxy_pass=SCRAPEOPS_API_KEY,
)
driver = scrapeops.new_driver()
driver.get("http://quotes.toscrape.com/")
print(driver.title)
driver.quit()

Full integration docs for Python Selenium and the ScrapeOps Proxy Aggregator are here.

tip

To use the ScrapeOps Proxy Aggregator, you first need an API key which you can get by signing up for a free account here which gives you 1,000 free API credits.


Proxies, TLS Fingerprints and Cloudflare

A common source of confusion: adding a proxy to Selenium does not make a Cloudflare-protected site work. Cloudflare's bot detection looks at many signals beyond the IP address — most importantly:

  • The TLS/JA3 fingerprint of the browser (default ChromeDriver-controlled Chrome produces a subtly different fingerprint than a user-launched Chrome).
  • The navigator.webdriver property (defaults to true in automated Chrome).
  • Mouse-movement and timing patterns.
  • The --headless flag (Cloudflare can detect headless mode in some configurations).

If your Selenium scraper is getting 403s from Cloudflare even with good residential proxies, the proxy isn't the problem. Reach for one of:

  • undetected-chromedriver — patches ChromeDriver to remove the most obvious automation flags. The most widely-deployed option in the Selenium ecosystem.
  • nodriver — the successor to undetected-chromedriver from the same author, uses Chrome DevTools Protocol directly instead of Selenium. Faster and harder to fingerprint.
  • A smart proxy API with a Cloudflare bypass (the ScrapeOps Proxy Aggregator handles this server-side so your Selenium client only sees the final HTML).

See the How To Bypass Cloudflare guide for a full comparison.


Frequently Asked Questions

How do I use a proxy with Selenium in Python?

For an unauthenticated HTTP/SOCKS proxy, pass it via Chrome options: options.add_argument('--proxy-server=http://1.2.3.4:8080'), then create the driver with webdriver.Chrome(options=options). For authenticated proxies, intercept the proxy auth challenge via the Chrome DevTools Protocol with Fetch.continueWithAuth — Chrome blocks the user:pass@host URL form in headless mode and the in-memory extension trick has become unreliable in recent Chrome versions.

Selenium doesn't support username/password proxy authentication directly. The two robust approaches in 2026 are (1) the Chrome DevTools Protocol via Fetch.continueWithAuth (see the ChromeWithAuthProxy code sample in this guide), or (2) using your proxy provider's IP-whitelisting feature so credentials aren't needed at the client.

Not on modern Python. The selenium-wire package was archived upstream and breaks at import time on recent versions of blinker because of a removed internal module. For new projects, use the Chrome DevTools Protocol-based wrapper from this guide for authenticated proxies. If you need request/response inspection (which selenium-wire was best at), use mitmproxy in front of vanilla Selenium or the CDP Network domain directly.

Selenium proxies are set at WebDriver construction time, so the simplest way to rotate is to start a new browser session per proxy (or per batch). For high-throughput rotation, use a gateway-style proxy provider that rotates server-side — you connect to a single endpoint and the provider issues a different exit IP per request.

A proxy alone usually isn't enough. Cloudflare also checks TLS/JA3 fingerprint, navigator.webdriver, headless flags, and behavioural signals. Pair the proxy with undetected-chromedriver or nodriver, or use a smart proxy API that solves the Cloudflare challenge for you.

Point Selenium at https://httpbin.org/ip (or https://api.ipify.org) and assert that the returned IP matches your proxy's exit IP. You can also visit https://tls.peet.ws to see the TLS fingerprint that your proxy and browser combination presents — a useful debugging step when you suspect TLS detection is the actual block.


More Web Scraping Tutorials

So that's how you can use both authenticated and unauthenticated proxies with Selenium to scrape websites without getting blocked.

If you would like to learn more about Web Scraping with Selenium, then be sure to check out The Selenium Web Scraping Playbook.

Or check out one of our more in-depth guides: