How to use a proxy in nodriver
Proxy Compare attached a dead proxy to nodriver 0.50.3 by all three documented routes on 27 Aug 2026 and confirmed each applies, by reading the page body rather than the navigation result. It also fixes the encoding bug that stops the package importing on Python 3.14.
3 Sept 2026 · 4 min read
Point in time. The figures below were read on 27 Aug 2026 and are not updated after publication. For current numbers see the comparison table.

What is nodriver?
nodriver is the successor to undetected-chromedriver, written by the same author. The change that matters is what it removed: it does not use Selenium or WebDriver at all, and talks to Chrome directly over the DevTools Protocol.
That matters because WebDriver is itself a detectable signal. A browser being driven by ChromeDriver differs from one a person is using, in ways that no amount of patching the driver fully hides. Dropping the driver removes the problem rather than concealing it, and it makes startup considerably faster.
The cost is a different API. nodriver is async throughout and its own thing,
so a Selenium or Playwright script does not port across; you rewrite.
Attaching a proxy
nodriver documents three ways:
import nodriver as uc
# 1. a browser argument at start
browser = await uc.start(browser_args=["--proxy-server=http://host:port"])
# 2. the same argument through Config
cfg = uc.Config()
cfg.add_argument("--proxy-server=http://host:port")
# 3. a per-context parameter
tab = await browser.create_context(url, proxy_server="http://host:port")
All three work. The next section is how to prove that for yourself, because the obvious check gives the wrong answer.
Verifying it applied
Point it at a port with nothing listening and load a page. If the proxy is in the path, you get Chrome's proxy error; if it is being ignored, you get the real site.
import asyncio, nodriver as uc
async def main():
b = await uc.start(headless=True,
browser_args=["--proxy-server=http://127.0.0.1:9999"])
p = await b.get("https://example.com")
await asyncio.sleep(3)
print(await p.evaluate("document.body.innerText.slice(0, 60)"))
b.stop()
uc.loop().run_until_complete(main())
All three routes above give the same answer:
No internet
There is something wrong with the proxy server, or the address is incorrect
Without the proxy argument the same script prints Example Domain. So the
argument takes effect, whichever way you pass it.
Read the page body, not the navigation result. This is the part worth keeping, and we got it wrong ourselves before checking properly: the navigation above succeeds. Chrome renders its own error page, the promise resolves, no exception is raised, and a script that only asks "did it load" concludes the proxy was skipped. The distinction is one line of JavaScript and it is the difference between a working check and a confident wrong answer.
The same trap appears with credentials rather than a dead port, and there it is
worse: Chrome cannot parse a username and password in --proxy-server, drops the
proxy entirely, and shows a generic "site can't be reached" that names neither.
How to use an authenticated proxy in Selenium
has the measurements.
Getting it to import on Python 3.14
nodriver 0.50.3 will not import on Python 3.14. It fails before your code runs:
SyntaxError: Non-UTF-8 code starting with '\xb1' on line 1345
One character in nodriver/cdp/network.py is stored as latin-1, and Python 3
reads source as UTF-8. Re-encode that one file:
from pathlib import Path
import nodriver, sys
f = Path(nodriver.__file__).parent / "cdp" / "network.py"
f.write_bytes(f.read_bytes().decode("latin-1").encode("utf-8"))
Run once, and it imports. It is a packaging bug rather than anything about how the library works, and it will presumably go away in a release.
What it reports when it runs
On Python 3.12, once running, it is respectable:
| Signal | nodriver |
|---|---|
navigator.webdriver | false |
| user agent | HeadlessChrome/151 |
window.chrome | object |
navigator.plugins.length | 5 |
| launch time | 3.1 s |
Three of four right, and it leaks HeadlessChrome in the user agent, which is
the same gap
Patchright has by default. It launches
in 3.1 seconds against
SeleniumBase UC mode's 10.4, which is the
real trade between them: nodriver is fast and announces itself, SeleniumBase is
clean and slow.
The full set is in how to choose an anti-detect browser.
Which field the credentials go in
Two provider-side things decide whether a working proxy argument actually
connects.
The session token sits in the username at some providers and the password at
others, and the wrong field produces a 407 naming nothing. And some
providers accept no username and password at all, wanting your IP on an
allowlist. We publish what each provider states on its
provider page, with the date we read it.
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
No anti-bot system and no evasion claim. Version 0.50.3, and the encoding fix above is a workaround for a packaging bug rather than a change to how the library works. The dead-port check is the durable part: it takes a minute and it answers the question for any tool, not just this one.
Sources
- nodriver PyPI, read 27 Aug 2026
nodriver 0.50.3
Questions
Does nodriver support proxies?
The parameters exist and are accepted without error. In our test on 27 August 2026 at version 0.50.3 they did not take effect: a dead proxy on 127.0.0.1:9999 should stop a page load and did not, across browser_args, uc.Config and create_context. We do not know why and did not diagnose it.
Why does nodriver fail to import?
On Python 3.14.7 it raises a SyntaxError before your code runs, on a non-UTF-8 byte in its own vendored CDP source at network.py line 1345. The same version 0.50.3 imports and runs on Python 3.12.12, so the fix is an older interpreter rather than an older library.
How do I check whether my proxy is actually being used?
Point the tool at a port with nothing listening on it and load a page. If the page loads, the proxy is not being applied and your traffic is going out on your own address.
