How to use an authenticated proxy in Selenium, Playwright and Puppeteer
Proxy Compare wrote a proxy requiring Basic authentication that logs whether each request carried matching credentials, then attached it to Selenium 4.47.0, selenium-wire, SeleniumBase, Playwright, Patchright and Camoufox on 28 Aug 2026. Two of nine configurations never sent the password at all.
3 Sept 2026 · 5 min read
Point in time. The figures below were read on 28 Aug 2026 and are not updated after publication. For current numbers see the comparison table.

You have a proxy that needs a username and password. Attaching it should be one line, and in most of these tools it is.
The reason it is not always one line is worth knowing before you start.
Chrome's --proxy-server flag has never accepted credentials. It takes a
host and a port, and browser proxy authentication was designed around a dialog
box that a person clicks, not a string a script passes. Every tool below is
working around that same constraint, and they solve it in three different
places: the framework, a local helper proxy, or the page itself.
Which is why the obvious syntax fails, why it fails silently, and why the libraries that get it right do not agree on how.
Every result below comes from a proxy we wrote that logs whether the credentials actually arrived.
The short version
| Tool | How | Verified |
|---|---|---|
| selenium-wire | seleniumwire_options={"proxy": {...}} | yes |
| SeleniumBase | proxy="user:pass@host:port" | yes, not with uc=True |
| Playwright, Patchright, Camoufox | proxy={"server","username","password"} | yes |
| Puppeteer | await page.authenticate({...}) | yes |
Selenium, --proxy-server | credentials in the URL | no, do not |
Start with the one that does not work
options.add_argument(f"--proxy-server=http://{user}:{password}@{host}:{port}")
This is the top answer nearly everywhere and our proxy received zero requests from it.
Not unauthenticated requests. None. Chrome does not strip the credentials and connect anyway; it fails to parse the value as a proxy and routes to something that is not there. What you see is:
This site can't be reached
No 407, no mention of a proxy, no mention of authentication. Identical to a typo'd hostname, which is what makes it expensive.
It is not your password's punctuation. We tested a password containing colons
and a plain simplepass, and dropped the scheme, and all three sent zero
requests. The same flag without credentials works exactly as documented: four
requests arrive and all get a 407.
One reassurance: it fails closed. The page does not load unproxied, so traffic that must not go direct did not.
selenium-wire
Works, and was the cleanest pure-Selenium result: 12 requests, 12 authenticated.
from seleniumwire import webdriver
sw = {"proxy": {
"http": "http://user:pass@gate.example.com:7000",
"https": "http://user:pass@gate.example.com:7000",
"no_proxy": "localhost,127.0.0.1",
}}
driver = webdriver.Chrome(options=options, seleniumwire_options=sw)
It works by running its own local proxy and doing the upstream authentication itself, which is why the credentials never have to reach a Chrome flag.
Two pieces of friction on a current Python, and you will hit both:
It imports pkg_resources, removed from setuptools 81 and later, so a fresh
environment fails at import with ModuleNotFoundError: No module named 'pkg_resources'. Pinning setuptools<81 fixes it.
That pin then collides with SeleniumBase, which declares setuptools>=84.0.0.
The two libraries cannot both have their requirement satisfied in one
environment. SeleniumBase kept working for us on setuptools 80 despite the
declared floor, but if you need both, expect to hear about it from pip.
SeleniumBase
Also works, with a much simpler call, and one configuration that does not.
from seleniumbase import Driver
driver = Driver(headless=True, proxy="user:pass@gate.example.com:7000")
A single string, where the Playwright family wants separate fields. That is where a password containing a colon or an at sign breaks it, and the format converter will encode one correctly.
The exception is UC mode:
| Configuration | Requests | Authenticated | Page |
|---|---|---|---|
headless, uc off | 5 | 1 | loaded |
headed, uc off | 7 | 1 | loaded |
headless, uc=True | 16 | 0 | failed |
headed, uc=True | 17 | 0 | failed |
With UC mode on, the credentials are not sent. Sixteen or seventeen requests
reach the proxy carrying no Proxy-Authorization header at all, and we got the
same result on three consecutive runs of each.
UC mode is the reason most people choose SeleniumBase, and an authenticated proxy is the normal case, so this intersection is not an edge. It also stacks with the launch cost we measured separately, where UC mode took seven times longer to start.
We make no claim this is intended, and we tested one version.
Playwright, Patchright and Camoufox
All three take the same shape and all three worked:
browser = pw.chromium.launch(
proxy={"server": "http://gate.example.com:7000",
"username": "user-session-abc123",
"password": "secret"},
)
Separate fields, no string parsing, no ambiguity about where the colon goes. If an authenticated proxy is in your path and you are choosing a tool, this is the strongest argument for the Playwright family over the Selenium family.
Puppeteer
Credentials go on the page, not in the launch arguments:
const page = await browser.newPage();
await page.authenticate({ username: user, password: pass });
Puppeteer reproduces the Chrome failure too, and unlike Selenium it names it:
--proxy-server with credentials net::ERR_BLOCKED_BY_CLIENT
--proxy-server without net::ERR_INVALID_AUTH_CREDENTIALS
Two different faults: the first is the proxy having been removed from the path,
the second is the proxy answering and rejecting you. Selenium gives you a blank
page for both, because Puppeteer throws on navigation while Selenium navigates
successfully to an error page, so driver.title reads as the site you asked
for.
Which field does the token go in
Not a tool question, and the commonest cause of a 407 once your syntax is right. The session or sticky-IP token sits in the username at some providers and the password at others, and some take no credentials at all and want your address on an allowlist.
We record what each provider states about both, with the date we read it, on its provider page, and print that a provider says nothing where it says nothing.
Proving it worked
Every failure on this page produced either a plausible error or a successful-looking page. Your script's output cannot distinguish them.
Count requests at the proxy. A provider usage dashboard does it: run a small job and see whether the number moved. Our test proxy logs one line per request with whether the credentials matched, and it is in the repository with the scripts above.
There is a second thing worth watching there. A plain Chrome launch opens
connections to three Google endpoints through your proxy before it reaches your
page, and --disable-background-networking did not stop them. On metered
bandwidth that is billable traffic on every launch, which we costed in
how to cut your scraping bandwidth bill.
The proxy behind it is the recurring cost, and per-gigabyte prices vary more between providers than these tools vary between each other. What each charges, with the date it was read, is on the provider table.
Limitations
One version of each library, one machine, one day. Puppeteer ran against its own bundled Chrome 152 rather than the installed 151 everything else used, so its request count is not comparable, though whether credentials arrived is.
We did not establish why UC mode drops them, only that it did, three times out of three. We did not test SOCKS5 authentication, which behaves differently from HTTP Basic and deserves its own measurement rather than an assumption.
Sources
- selenium-wire PyPI, read 28 Aug 2026
selenium-wire 5.1.0
- seleniumbase PyPI, read 28 Aug 2026
seleniumbase 4.52.4
Questions
How do I use a proxy with a username and password in Selenium?
Not through --proxy-server, which cannot carry credentials. Two options worked in our test: selenium-wire, which runs a local proxy of its own and authenticates upstream, and SeleniumBase, which takes a user:pass@host:port string directly. Both loaded the page and both sent the credentials.
Why does --proxy-server with a username and password not work?
Because Chrome cannot parse it, and the failure is worse than being ignored. Our proxy received zero requests from that configuration, meaning Chrome stopped using the proxy entirely rather than connecting without credentials. The page shows a generic site cannot be reached that names neither a proxy nor authentication.
Does SeleniumBase support proxy authentication?
In three of four configurations, yes. Plain headless and plain headed both authenticated. With uc=True it did not: sixteen requests reached our proxy carrying no credentials, in three consecutive runs, headed and headless alike. If you need UC mode and an authenticated proxy, test that combination before relying on it.
How do I use an authenticated proxy in Puppeteer?
Call page.authenticate with a username and password rather than putting them in the launch arguments. That worked. Credentials in --proxy-server produced net::ERR_BLOCKED_BY_CLIENT and our proxy received nothing, the same failure Selenium has but with a readable name attached.
How can I tell whether my proxy is actually being used?
Count requests at the proxy rather than reading your script's output. Every failure here produced either a plausible-looking error or a successful-looking page, and the only reliable signal was the proxy log. A provider usage dashboard works for this too: run a small job and see whether the number moved.
