The One Thing Most nodriver Proxy Guides Get Wrong
nodriver — the successor to undetected-chromedriver, driving Chromium directly over the DevTools Protocol — gives you a clean browser_args list to set a proxy. What trips people up is the username and password: Chromium does not read credentials from the --proxy-server value, so the http://USER:PASS@HOST:PORT you'd use with requests silently drops the USER:PASS part, and your first navigation fails with 407 Proxy Authentication Required.
There is no page.authenticate in nodriver. The credentials go in over the CDP Fetch domain instead:
- Pass only the host and port in browser args:
--proxy-server=PROXY_HOST:PORT. - Enable the Fetch domain with
handle_auth_requests=True, then answer the auth challenge with your username and password viacontinue_with_auth.
nodriver is a library for making an automated browser present a consistent, realistic fingerprint so you can reach public data reliably — not a tool for defeating access controls. The other half of that reliability is the egress IP: a real German mobile IP with solid IP reputation is what a well-behaved crawler needs on German platforms.
Before You Start
You need four values from your GermanProxy.io account:
- Host and port of your endpoint
- Username and password
The examples use PROXY_HOST, PORT, USER and PASS as placeholders — replace them with your own. Install nodriver (Python 3.9+):
pip install nodriver
Step 1 — Start Chromium Through the Proxy (host and port)
If your endpoint uses IP-allowlist authentication, this is the whole setup — the host and port go in browser_args:
import asyncio
import nodriver as uc
PROXY_HOST = "PROXY_HOST"
PORT = "PORT"
async def main():
browser = await uc.start(
browser_args=[f"--proxy-server={PROXY_HOST}:{PORT}"],
)
page = await browser.get("https://www.example.de")
print("Title:", await page.evaluate("document.title"))
browser.stop()
asyncio.run(main())
For a credential-based endpoint, add the auth handler in the next step before your first navigation.
Step 2 — Handle Username/Password Auth over CDP Fetch
Chromium raises an auth challenge for a credential proxy. Register the Fetch handlers, enable the domain with handle_auth_requests=True, and answer the challenge with continue_with_auth:
import asyncio
import nodriver as uc
from nodriver import cdp
PROXY_HOST = "PROXY_HOST"
PORT = "PORT"
USER = "USER"
PASS = "PASS"
async def main():
browser = await uc.start(
browser_args=[f"--proxy-server={PROXY_HOST}:{PORT}"],
)
tab = browser.main_tab
async def on_auth(event: cdp.fetch.AuthRequired):
# Reply with the credentials for the proxy's auth challenge.
asyncio.create_task(
tab.send(
cdp.fetch.continue_with_auth(
request_id=event.request_id,
auth_challenge_response=cdp.fetch.AuthChallengeResponse(
response="ProvideCredentials",
username=USER,
password=PASS,
),
)
)
)
async def on_paused(event: cdp.fetch.RequestPaused):
# Non-auth pauses: just let the request continue unchanged.
asyncio.create_task(
tab.send(cdp.fetch.continue_request(request_id=event.request_id))
)
# Add handlers first, then enable the Fetch domain with auth handling.
tab.add_handler(cdp.fetch.AuthRequired, on_auth)
tab.add_handler(cdp.fetch.RequestPaused, on_paused)
await tab.send(cdp.fetch.enable(handle_auth_requests=True))
page = await browser.get("https://www.example.de")
print("Title:", await page.evaluate("document.title"))
browser.stop()
asyncio.run(main())
Two details matter: add the handlers before you enable the Fetch domain, and reply from inside the handler with asyncio.create_task(...) (fire-and-forget) so one paused request doesn't block the others. AuthChallengeResponse carries response="ProvideCredentials" plus your username and password.
Step 3 — Verify the IP Before You Scale
Confirm you're actually on a German mobile IP before you build anything on top of it. Point the browser at an IP-echo endpoint and read the result:
page = await browser.get("https://api.ipify.org?format=json")
ip = await page.evaluate("document.body.innerText")
print("Egress IP:", ip)
If it returns your own address, re-check that the host and port are in --proxy-server and that the Fetch handlers were added before the first navigation. For a fuller checklist, see verifying your mobile proxy. A persistent 407 points at the credentials — see how to fix 407 proxy authentication required.
Step 4 — Check for WebRTC and DNS Leaks
A proxy routes Chromium's HTTP traffic, but the browser has two side channels that can expose your real address:
- WebRTC can surface your local IP via ICE candidates even with a proxy set — see what a WebRTC leak is.
- DNS can resolve names outside the tunnel depending on configuration — see what a DNS leak is.
For data-collection work in headless mode WebRTC is usually inert, but verify it for any flow where IP consistency matters. A real German mobile IP only helps if nothing else leaks past it — walk through both with how to check a proxy for leaks.
HTTP vs. SOCKS5 in nodriver
Both protocols come with your endpoint. Which to pass in --proxy-server depends on what you need:
| Aspect | HTTP endpoint | SOCKS5 endpoint |
|---|---|---|
| Browser arg | --proxy-server=HOST:PORT |
--proxy-server=socks5://HOST:PORT |
| Username/password auth in Chromium | Yes, via the CDP Fetch handler | No — Chromium has no SOCKS5 user/pass auth |
| Best auth mode | Credentials | IP allowlist |
| Typical use | Default for most scraping | Lower-level / non-HTTP traffic |
For most nodriver scraping, the HTTP endpoint with the Fetch auth handler is the simplest path. Reach for SOCKS5 when you specifically need it and can use IP-allowlist authentication. Background: SOCKS5 vs HTTP proxy.
Sticky Sessions vs. Rotation
With a dedicated German endpoint you control the IP at the proxy, not in your code:
- Sticky session: keep one browser open across a multi-step flow — login, navigate, paginate — and the German mobile IP stays the same.
- Rotation on demand: when you want a fresh IP, trigger rotation at the proxy and start a new browser. There's no per-request IP pool to manage in nodriver.
This is why a dedicated endpoint keeps the code small: no rotation list, no health-checking, no retry-on-dead-IP loop. See sticky sessions and IP rotation explained and dedicated vs shared mobile proxy.
Why a Mobile IP for nodriver at All
nodriver is a common choice for JavaScript-heavy pages that a plain HTTP client can't render — product pages, SERPs, dashboards. The catch is the egress IP: datacenter ranges are widely distrusted, so even a perfectly behaved crawler can hit friction. A real German mobile IP rides on carrier-grade NAT (CGNAT), the same shared-IP space thousands of ordinary phone users sit behind, which is why it carries higher IP reputation on German platforms — the point that matters more than any browser-side setting, since websites also detect proxies by the IP itself.
For the trade-offs against other IP types, see mobile vs residential vs datacenter proxy and what is a mobile proxy.
Scrape Responsibly
A mobile proxy improves reliability for legitimate data collection — it is not a tool to circumvent access controls or platform rules. Keep your nodriver crawl clean and defensible:
- Respect each site's terms and its
robots.txt; collect only public data you're permitted to use. - Throttle with realistic delays between navigations so you don't overload servers — a full Chromium is heavier than a plain HTTP client.
- Collect only what you need, and handle any personal data in line with the GDPR. See is web scraping legal in the EU.
- Be honest about who you are — don't impersonate a service or misuse a target's security mechanisms.
Whether mobile proxies themselves are lawful to use: are mobile proxies legal.
FAQ
How do I set a proxy in nodriver?
Pass the host and port as a Chromium browser argument when you start: uc.start(browser_args=['--proxy-server=PROXY_HOST:PORT']). For an authenticated proxy you must also handle the credentials over the CDP Fetch domain — Chromium ignores any user:pass embedded in the --proxy-server value.
How does nodriver handle username and password proxy auth?
nodriver has no page.authenticate helper. You enable the CDP Fetch domain with handle_auth_requests=True, add a handler for cdp.fetch.AuthRequired, and reply with cdp.fetch.continue_with_auth carrying an AuthChallengeResponse that holds your username and password. A second handler resumes normal requests with continue_request.
Why does nodriver ignore the credentials in my proxy URL?
Because Chromium — the browser nodriver drives — does not read a username and password from the --proxy-server flag. Passing http://USER:PASS@HOST:PORT silently drops the credentials and your first navigation fails with 407 Proxy Authentication Required. Supply the credentials through the CDP Fetch auth handler instead — see how to fix 407 proxy authentication required.
Can nodriver use a SOCKS5 mobile proxy?
Yes, via browser_args=['--proxy-server=socks5://PROXY_HOST:PORT']. Chromium cannot do SOCKS5 username/password auth, so a SOCKS5 endpoint works best with IP-allowlist authentication. For credential auth, use the HTTP endpoint with the CDP Fetch handler. See the proxy protocol choice.
How do I rotate IPs with nodriver?
With a dedicated German endpoint you rotate on demand at the proxy, not per request in code. For a sticky session, keep one browser open across the flow; to get a fresh IP, trigger rotation at the proxy and start a new browser. See sticky sessions and IP rotation explained.
Is nodriver the successor to undetected-chromedriver?
Yes. nodriver is from the same author and drives Chromium directly over the DevTools Protocol without Selenium or a webdriver binary. Both are libraries for presenting a consistent, realistic browser to reach public data — the proxy work differs because nodriver uses CDP, not selenium-wire. Compare the undetected-chromedriver setup.
Related guides
- undetected-chromedriver Proxy Setup: German Mobile IPs
- Botasaurus Proxy Setup with a Dedicated German Mobile Proxy
- Patchright Proxy Setup: Route via German Mobile IPs
CTA
Drive nodriver on real German mobile IPs. Get a German mobile proxy — HTTP/SOCKS5/OpenVPN, sticky sessions, on-demand rotation, fair-use 20 GB/day, from a transparent EU provider.
Get a German mobile proxy: Real 4G/5G IPs from Germany, fair-use 20 GB/day — compare German mobile proxy plans or get a dedicated proxy on the Blue network.