Here's the trap every browser-automation developer falls into exactly once: you add --proxy-server=http://user:pass@host:port to Chrome's launch flags, the browser starts fine, and then every page hangs on an authentication popup your script can't see or click. Chrome silently strips credentials from the proxy flag. No error, no log line, just a modal dialog in a browser nobody is watching.
This guide is the complete map of proxy authentication methods that actually work in Selenium and Playwright in 2026 — which to use, which to avoid, and the exact code for each. (For proxy selection and rotation strategy in these tools, see Using Proxies with Playwright & Puppeteer — this article is purely about getting credentials through.)
Command-line HTTP clients parse credentials out of proxy URLs and answer the proxy's 407 Proxy Authentication Required with a Proxy-Authorization header automatically. Chromium does not: --proxy-server accepts only scheme://host:port, and when the 407 comes back it shows an interactive dialog. In headless mode there's not even a dialog — the request just fails.
So the question is never "how do I put the password in the URL" — it's "which layer answers the 407 for me." Four working answers, best first.
Playwright solved the problem properly: it answers proxy auth challenges itself via CDP. Credentials go in the launch or context options:
# Python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": "http://us.jibaoproxy.com:913",
"username": "USERNAME-session-job1",
"password": "PASSWORD",
}
)
// Node.js - and per-context, which is where it gets powerful:
const browser = await chromium.launch({
proxy: { server: 'per-context' } // declare intent at launch
});
const ctx = await browser.newContext({
proxy: {
server: 'http://us.jibaoproxy.com:913',
username: 'USERNAME-session-job2',
password: 'PASSWORD',
}
});
Per-context proxies mean one browser process can run N contexts on N different sticky sessions — the foundation of every serious Playwright scraping setup. If you're on Playwright, you're done; skip to the verification section. Everything below exists because Selenium has no equivalent.
selenium-wire wraps Selenium with a local MITM proxy that handles upstream authentication:
from seleniumwire import webdriver # pip install selenium-wire
options = {
"proxy": {
"http": "http://USERNAME:[email protected]:913",
"https": "https://USERNAME:[email protected]:913",
}
}
driver = webdriver.Chrome(seleniumwire_options=options)
driver.get("https://example.com")
Caveats you should know before production:
The classic approach: a tiny generated extension that sets the proxy and answers auth via chrome.webRequest.onAuthRequired. No MITM, no extra process, works in plain Selenium:
import zipfile, json
HOST, PORT = "us.jibaoproxy.com", 913
USER, PASS = "USERNAME-session-sel1", "PASSWORD"
manifest = {
"version": "1.0.0", "manifest_version": 2, "name": "Proxy Auth",
"permissions": ["proxy", "webRequest", "webRequestBlocking", ""],
"background": {"scripts": ["background.js"]},
}
background = f"""
chrome.proxy.settings.set({{value: {{mode: "fixed_servers", rules: {{
singleProxy: {{scheme: "http", host: "{HOST}", port: {PORT}}}
}}}}, scope: "regular"}}, () => {{}});
chrome.webRequest.onAuthRequired.addListener(
() => ({{authCredentials: {{username: "{USER}", password: "{PASS}"}}}}),
{{urls: [""]}}, ["blocking"]
);
"""
with zipfile.ZipFile("proxy_auth.zip", "w") as zp:
zp.writestr("manifest.json", json.dumps(manifest))
zp.writestr("background.js", background)
from selenium import webdriver
opts = webdriver.ChromeOptions()
opts.add_extension("proxy_auth.zip")
driver = webdriver.Chrome(options=opts)
Two gotchas: extensions don't load in classic headless mode — use --headless=new (Chrome 109+) or run headed; and Manifest V2 background scripts still work for sideloaded automation extensions but watch Chrome's MV2 deprecation timeline — the MV3 equivalent needs a service worker and the same onAuthRequired listener.
Run a tiny local proxy that holds the credentials; point the browser at localhost with no auth at all. Works with every browser, every driver, every language, headless or not:
# Using pproxy (pip install pproxy) - terminal 1:
pproxy -l http://127.0.0.1:8899 \
-r "http://USERNAME:[email protected]:913"
# Your script - terminal 2: plain Selenium, no auth needed
opts = webdriver.ChromeOptions()
opts.add_argument("--proxy-server=http://127.0.0.1:8899")
driver = webdriver.Chrome(options=opts)
This is also the cleanest answer for Firefox/geckodriver (where the extension trick doesn't apply) and for exotic environments like Electron app testing. Cost: one more moving part to supervise, and per-context routing requires one forwarder port per session.
| Method | Works in | Headless | Per-context sessions | Production verdict |
|---|---|---|---|---|
| Playwright native | Playwright | Yes | Yes | Default choice |
| selenium-wire | Selenium (Py) | Yes | Per-driver | Fine at moderate scale |
| Auth extension | Selenium (any lang) | --headless=new only | Per-driver | Solid, watch MV3 |
| Local forwarder | Everything | Yes | One port per session | Universal fallback |
Silent fallback to your real IP is the failure mode that gets accounts banned. Assert the exit IP at the start of every run:
ip = driver.execute_script(
"return fetch('https://api.ipify.org').then(r => r.text())"
) if hasattr(driver, 'execute_script') else page.evaluate(
"fetch('https://api.ipify.org').then(r => r.text())"
)
assert ip != MY_REAL_IP, "Proxy not applied - aborting before we leak"
--proxy-server; Chrome dropped them. Use one of the four methods.ERR_PROXY_CONNECTION_FAILED — host/port wrong or protocol mismatch. Test the same line with curl -x first; if curl works, the problem is your browser wiring.--headless=new.Browsers don't read user:pass@ proxy URLs — something has to answer the 407 for them. In Playwright that something is built in; in Selenium it's selenium-wire, a generated auth extension, or a local forwarder. Pick by your stack from the comparison table, then assert the exit IP on every run so a silent auth failure can never leak your real address mid-crawl.
$5 free residential credit — one gateway, sticky or rotating, HTTP and SOCKS5.
Start Free TrialNew users get $5 USDT instantly, plus an extra first-deposit reward — limited-time offer.