![]()
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:
- Quick reference: which proxy method to use
- Using Proxies With Selenium
- Using Authenticated Proxies With Selenium (CDP)
- Using Authenticated Proxies With selenium-wire (Legacy)
- Integrating Proxy APIs
- Proxies, TLS Fingerprints and Cloudflare
- Frequently Asked Questions
Need help scraping the web?
Then check out ScrapeOps, the complete toolkit for web scraping.
Quick reference: which proxy method to use
| Scenario | Recommended 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 complexity | Use your provider's IP-whitelisting feature so credentials aren't needed |
| Rotating proxies | Use 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 them | mitmproxy 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()
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,
seleniumbaseships aDriver(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 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.
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.webdriverproperty (defaults totruein automated Chrome). - Mouse-movement and timing patterns.
- The
--headlessflag (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
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: