undetected-chromedriver, nodriver and SeleniumBase all answer the same question: how do you drive a real Chrome from Python without the browser announcing that it is being automated? They answer it in different ways. undetected-chromedriver patches ChromeDriver, nodriver drops WebDriver and talks to Chrome over the DevTools Protocol, and SeleniumBase wraps both ideas in a full framework. This comparison explains why stock Selenium is easy to spot, how each tool works, how well each one is maintained in October 2026, and how to run each behind an authenticated ProxyEmpire proxy with one sticky IP per browser.
The short version
For new projects in 2026, pick SeleniumBase or nodriver. Treat undetected-chromedriver as legacy. undetected-chromedriver’s last PyPI release is 3.5.5 from February 2024. nodriver, from the same author, is its official successor: async, no WebDriver, and it can run an authenticated proxy per browser context through a built-in local forwarder. SeleniumBase is the most actively maintained of the three and the only one with a documented proxy="user:pass@host:port" argument. Its UC Mode and CDP Mode give you the same stealth ideas with a synchronous API. Chrome’s --proxy-server flag can’t take a username and password, so how each tool gets around that is often what decides it.
Key takeaways
- A stock ChromeDriver session leaves visible traces:
navigator.webdriver, automation launch switches, injected driver variables and side effects of DevTools Protocol use. - undetected-chromedriver patches the driver. nodriver removes the driver entirely. SeleniumBase offers both styles (UC Mode and CDP Mode) inside a test and automation framework.
- undetected-chromedriver has had no PyPI release since February 2024. nodriver and SeleniumBase both shipped releases in October 2026.
- Chrome’s
--proxy-serverswitch has no field for credentials. SeleniumBase acceptsuser:pass@host:portdirectly, and nodriver accepts credentials increate_context(proxy_server=...). With undetected-chromedriver you have to bring your own workaround. - Use the
http://proxy URL with all three. Chromium does not do username/password authentication on SOCKS5 proxies. - Map one ProxyEmpire sticky session (
sid) to one browser or browser context, so cookies and IP stay consistent. - Stealth tooling doesn’t replace permission. Use it for public data and for systems you are allowed to test.
Why Default Selenium and ChromeDriver Are Easy to Detect
The browser tells on itselfSelenium drives Chrome through ChromeDriver, which speaks the W3C WebDriver protocol to your script and the Chrome DevTools Protocol (CDP) to the browser. That arrangement was built for testing, where nobody wants to hide automation. Several signals follow from it, and anti-bot systems read all of them:
- The WebDriver flag. The WebDriver specification says a browser under automation should report
navigator.webdriverastrue. Any script on the page can read it. - Launch switches. When ChromeDriver launches Chrome it adds automation-related switches and preferences. These change small details of browser behavior and UI that a page can notice.
- Driver artifacts. ChromeDriver has historically injected helper variables into pages. SeleniumBase’s documentation calls them the “Chrome DevTools Console variables” that UC Mode renames, because a plain Selenium session exposes them.
- CDP side effects. Enabling some DevTools domains has side effects that page JavaScript can observe. Detection scripts probe for them. Our explainer on WebDriver BiDi vs CDP covers how the two protocols differ.
- Headless differences. Older headless modes differed from headed Chrome in user agent, rendering and available APIs. The headless browsers primer walks through the differences.
- Network identity. None of the above concerns your IP address. A perfect browser fingerprint still looks wrong coming from a datacenter range that has sent a million requests today.
You can see the first signal in four lines:
from selenium import webdriver
driver = webdriver.Chrome() # Selenium Manager fetches a matching chromedriver
try:
driver.get("https://example.com")
print(driver.execute_script("return navigator.webdriver")) # True
finally:
driver.quit()
Commercial bot-management products combine these browser signals with TLS and HTTP fingerprints, behavior over time and IP reputation. Our DataDome bot detection explainer describes one vendor’s layered model in neutral terms. The tools below work on the browser layer only. None of them changes the network layer. The undetected-chromedriver README says plainly that the package doesn’t hide your IP, and that runs from datacenter or low-reputation addresses will probably still fail.
How Each Tool Approaches the Problem
Patch the driver, drop the driver, or wrap bothStock Selenium
script --WebDriver--> chromedriver --CDP--> Chrome (launched by the driver)
undetected-chromedriver
script --WebDriver--> patched chromedriver --CDP--> Chrome
(driver binary patched, browser started with
a cleaner launch, Selenium API kept)
nodriver
async script ------------------CDP websocket------> Chrome
(no chromedriver, no Selenium)
SeleniumBase
UC Mode: script --WebDriver--> uc_driver --> Chrome
(driver disconnects during sensitive steps)
CDP Mode: script --CDP (MyCDP)--> Chrome
(activate_cdp_mode() hands control to CDP;
sb_cdp / cdp_driver skip WebDriver entirely)
undetected-chromedriver: a patched ChromeDriver
undetected-chromedriver (imported as undetected_chromedriver, usually aliased uc) keeps the Selenium API you already know. When it starts it downloads a ChromeDriver binary, patches it and launches Chrome in a way that avoids the usual automation markers. In the 3.4.0 rewrite, according to its changelog, the package stopped renaming the driver’s variables after the fact and started preventing them from being injected at all. You get a uc.Chrome object that is a Selenium WebDriver, so existing locators, waits and page objects mostly carry over. The cost is that WebDriver is still connected the whole time, and everything depends on a patch that has to keep up with Chrome releases.
nodriver: no WebDriver, CDP directly, async
nodriver is by the same author and is described in its README as the official successor of undetected-chromedriver. It has no ChromeDriver binary and no Selenium dependency. It starts Chrome (or Chromium, Edge or Brave) and talks CDP over a websocket. The API is fully asyncio: await uc.start(), await browser.get(url), await tab.select("css"), await tab.find("text"). Element lookup retries until a timeout, so you write fewer explicit waits. Each run gets a fresh profile that is cleaned up on exit unless you pass user_data_dir. With no WebDriver layer there is nothing WebDriver-shaped to detect. Detection scripts can still look for CDP side effects, though, so “no WebDriver” does not mean invisible.
SeleniumBase: a framework with UC Mode and CDP Mode
SeleniumBase is a complete Python framework for testing and automation: a pytest plugin, context managers, a recorder, reporting, and hundreds of helper methods. Its stealth features come in layers:
- UC Mode (
SB(uc=True)orDriver(uc=True)) started out from undetected-chromedriver. The UC Mode docs name three techniques. It renames the driver’s console variables, launches Chrome before attaching the driver, and disconnects the driver during sensitive actions. - CDP Mode (
sb.activate_cdp_mode(url)) disconnects WebDriver and drives the browser over CDP using SeleniumBase’s MyCDP library. The docs present it as the successor to plain UC Mode. In CDP Mode, manysb.*methods redirect to theirsb.cdp.*versions. - Pure CDP Mode (
from seleniumbase import sb_cdp, thensb_cdp.Chrome(url)) skips WebDriver entirely. There is also an async form,cdp_driver.start_async(), whose method names closely follow nodriver’s.
In practice, SeleniumBase gives you the ideas behind undetected-chromedriver and nodriver with a synchronous API and much more around them, including the most complete proxy handling of the three.
undetected-chromedriver vs nodriver vs SeleniumBase: Comparison Table
Verified against repositories and PyPI on 20 October 2026| Criterion | undetected-chromedriver | nodriver | SeleniumBase |
|---|---|---|---|
| Approach | Patched ChromeDriver, Selenium API | No WebDriver; CDP over websocket | Framework: UC Mode (patched driver) + CDP Mode |
| API style | Selenium WebDriver | Own API: start, get, select, find | SB() context manager, Driver(), sb_cdp, pytest |
| Async | No | Yes, async only | Sync by default; async via cdp_driver |
| Latest PyPI release | 3.5.5 (17 Feb 2024) | 0.50.6 (8 Oct 2026) | 4.56.2 (16 Oct 2026) |
| Proxy auth | Not built in | Per context: create_context(proxy_server="http://user:pass@...") | Built in: proxy="user:pass@host:port" |
| Learning curve | Low if you know Selenium | Medium: asyncio and a new API | Low to start, large surface area |
| License | GPL-3.0 | AGPL-3.0 | MIT |
| Best for | Keeping existing uc scripts alive | Async pipelines, many contexts per browser | New projects, teams, built-in proxy auth |
Licensing matters more than people expect. nodriver is AGPL-3.0 and undetected-chromedriver is GPL-3.0. If you ship software that bundles either one, or run nodriver inside a network service you expose to others, get your legal team’s view on the AGPL’s obligations. SeleniumBase is MIT.
Maintenance Status in October 2026
Release dates matter more than star countsStealth tools go stale quickly. Chrome ships a new major version roughly every four weeks, and detection vendors update their scripts continuously. A tool that was excellent eighteen months ago may now fail on the first page load. Here is what the GitHub API and PyPI showed on 20 October 2026:
- undetected-chromedriver. Last push to the repository: 5 July 2025, a merged pull request that replaces
distutilsfor Python 3.12 and 3.13 compatibility. Last PyPI release: 3.5.5, published 17 February 2024. The repository is not archived, but the issue tracker has more than 1,000 open issues and pull requests. The published 3.5.5 package importsdistutils, which Python 3.12 removed. On current Python versions you will typically needsetuptoolsinstalled or an install from the GitHub master branch. - nodriver. Last push: 8 October 2026, after protocol and bug-fix commits that same week. Latest PyPI release: 0.50.6, published 8 October 2026. PyPI declares
requires_python >=3.9, but 0.50.6 fails to import on Python 3.9 because it usesstr | Pathtype syntax at runtime. Plan on Python 3.10 or newer. - SeleniumBase. Last push: 19 October 2026. Latest release: v4.56.2, published 16 October 2026. It requires Python 3.10 or newer. Commits land several times a week, and the source contains explicit workarounds for recent Chrome changes, including a comment about a Chrome 142 extension fix.
Check before you commit. These dates will be out of date soon. Run curl -s https://api.github.com/repos/OWNER/REPO and look at pushed_at, then check the release history on PyPI. A stealth library with no release in a year is a maintenance liability, however good its README looks.
The Proxy-Authentication Problem
Chrome’s proxy flag has no password fieldEvery one of these tools ends up launching Chrome, and Chrome takes its proxy from the --proxy-server switch. That switch accepts a scheme, host and port, such as http://host:port. It does not accept a username and password. When an authenticated proxy answers with 407 Proxy Authentication Required, Chrome expects someone to answer the challenge: a person at a login dialog, an extension, or an automation client listening for the auth event.
ProxyEmpire authenticates by username and password, and does not use IP whitelisting. The username also carries your targeting and session settings. So “just whitelist the server IP and skip credentials” is not an option here, and it would be a poor fit for rotating cloud workers anyway. There are four common ways to supply credentials:
Fetch.authRequired with the credentials. SeleniumBase’s CDP driver does this.Proxy-Authorization header upstream, and point Chrome at it. nodriver does this inside create_context().Before you debug any browser code, check the credentials on their own. Copy the username and password from Proxy Manager → Connection Builder in the ProxyEmpire dashboard and run:
curl -x http://USERNAME:[email protected]:5000 https://api.ipify.org
If that prints an IP, the proxy is fine and any remaining problem is in the browser setup. If it returns a 407, fix the credentials first. Our guide to proxy error codes lists the usual causes. Use the http:// form in the browser as well. Chromium does not do username/password authentication on SOCKS5 proxies. The SOCKS vs HTTP comparison explains why you lose little by using HTTP here.
undetected-chromedriver with a Proxy
Workable, but you bring the auth layerundetected-chromedriver has no proxy parameter. You pass Chrome switches through uc.ChromeOptions(), the same way you would with plain Selenium. The project’s own README shows only an unauthenticated proxy (a local Tor SOCKS port). Because --proxy-server cannot carry credentials, the clean pattern is to point Chrome at a local forwarder on 127.0.0.1 that holds the ProxyEmpire credentials:
import undetected_chromedriver as uc
# Chrome takes host:port only. 127.0.0.1:8899 is a local forwarder you run,
# which adds your ProxyEmpire username and password to the upstream
# connection to v2.proxyempire.io:5000.
LOCAL_FORWARDER = "http://127.0.0.1:8899"
def main():
options = uc.ChromeOptions()
options.add_argument(f"--proxy-server={LOCAL_FORWARDER}")
driver = uc.Chrome(options=options) # version_main=NNN pins a Chrome major
try:
driver.get("https://api.ipify.org?format=json")
print(driver.find_element("tag name", "body").text)
finally:
driver.quit()
if __name__ == "__main__":
main() # uc starts Chrome in a subprocess; keep the main guard
Where does the forwarder come from? There are three honest answers:
- Your own infrastructure. Many teams already run a forward proxy such as Squid next to their workers, configured with an authenticated upstream (parent) proxy. Chrome connects without credentials and the forwarder adds them. Bind it to localhost only, so you aren’t running an open proxy.
- A runtime extension. You can generate an extension that calls
chrome.proxy.settingsand answersonAuthRequired, then load it with--load-extension. This is the approach SeleniumBase automates. Recent branded Chrome releases restrict command-line extension loading, so test against the exact Chrome version you deploy, or use Chromium or Chrome for Testing. - Move to SeleniumBase UC Mode. It descends from undetected-chromedriver, keeps a Selenium-style API, and takes
proxy="user:pass@host:port"directly. For most uc users this is the least effort.
For the plain-Selenium side of this problem, including extension-based auth in detail, see our Selenium proxy with ChromeDriver and Python guide. The same techniques apply to uc.Chrome, because it is a Selenium driver underneath.
nodriver with a Proxy
Authenticated proxies per browser contextnodriver’s start() has no proxy argument. You could pass --proxy-server in browser_args, but that is limited to unauthenticated proxies. The useful feature is Browser.create_context(). It creates a new, isolated browser context (separate cookies and storage) with its own proxy, and opens a tab in it. In nodriver 0.50.6, if the proxy_server string contains credentials, nodriver starts a small local forwarder on a free port. The forwarder adds the Proxy-Authorization header upstream and points the new context at itself. The method’s docstring covers both http://USERNAME:PASSWORD@SERVER:PORT and socks://... forms. The docs mark the proxy parameters as experimental.
import os
import nodriver as uc
PROXY_URL = "http://{user}:{password}@v2.proxyempire.io:5000".format(
user=os.environ["PE_USER"], # copied from the Connection Builder
password=os.environ["PE_PASS"],
)
async def main():
browser = await uc.start()
# The default context is NOT proxied. Do all work in the proxied one.
tab = await browser.create_context(
"https://api.ipify.org?format=json",
proxy_server=PROXY_URL,
)
await tab.sleep(2)
print(await tab.evaluate("document.body.innerText", return_by_value=True))
browser.stop()
if __name__ == "__main__":
# The README uses nodriver's own loop helper rather than asyncio.run()
uc.loop().run_until_complete(main())
Three details from reading nodriver’s source, worth knowing before you go to production:
- The first window goes direct. Only the context you create with
proxy_serveris proxied. The default context thatstart()opens is not, so don’t navigate in it. - HTTPS only, through the HTTP forwarder. The built-in HTTP forwarder handles
CONNECTtunnels and rejects plain-HTTP proxy requests. That covershttps://pages, which is nearly everything, but anhttp://URL in that context will fail. - Credentials are parsed with
urlparseand are not URL-decoded. Avoid@,:and/in the password rather than relying on percent-encoding.
Because each context has its own proxy, one nodriver browser can host several isolated sessions, each with its own exit IP. That is covered under sticky sessions below.
SeleniumBase with a Proxy
One argument, three entry pointsSeleniumBase is the only one of the three with a first-class, documented proxy argument. SB(), Driver() and the CDP launchers all accept proxy="SERVER:PORT" or proxy="USER:PASS@SERVER:PORT". Under the hood it generates a Manifest V3 extension that answers the auth challenge. In CDP Mode it also answers Fetch.authRequired over the DevTools Protocol. You never touch either.
SB() with UC Mode and CDP Mode
import os
from seleniumbase import SB
PROXY = "{}:{}@v2.proxyempire.io:5000".format(
os.environ["PE_USER"], os.environ["PE_PASS"]
)
with SB(uc=True, proxy=PROXY, locale="en") as sb:
# Disconnects WebDriver and continues over CDP
sb.activate_cdp_mode("https://api.ipify.org?format=json")
print(sb.cdp.get_text("body"))
activate_cdp_mode() can be called with or without a URL. Recent versions also switch to CDP Mode when you call sb.goto(url) from UC Mode. Once WebDriver is disconnected, sb.reconnect() brings it back for WebDriver-only methods. SeleniumBase’s docs warn that reconnecting makes you detectable again, so do it only when needed.
Driver() for Selenium-style code
import os
from seleniumbase import Driver
PROXY = "{}:{}@v2.proxyempire.io:5000".format(
os.environ["PE_USER"], os.environ["PE_PASS"]
)
driver = Driver(uc=True, proxy=PROXY)
try:
# Opens the URL with WebDriver disconnected, then reconnects after 4 s
driver.uc_open_with_reconnect("https://api.ipify.org?format=json", 4)
print(driver.get_text("body"))
finally:
driver.quit()
Driver() returns a Selenium driver with SeleniumBase’s extra methods attached, which suits teams moving off raw undetected-chromedriver. On headless Linux servers the docs recommend the SB manager instead, because it brings a virtual display (Xvfb) by default.
Pure CDP Mode
import os
from seleniumbase import sb_cdp
PROXY = "{}:{}@v2.proxyempire.io:5000".format(
os.environ["PE_USER"], os.environ["PE_PASS"]
)
sb = sb_cdp.Chrome("https://api.ipify.org?format=json", proxy=PROXY)
print(sb.get_text("body"))
sb.quit()
SeleniumBase parses the proxy string by splitting on @ and :, so the same rule applies as with nodriver: keep those characters out of the password.
One Sticky Session per Browser
Keep IP, cookies and fingerprint alignedA browser session builds up state: cookies, local storage, a consent choice, maybe a logged-in account you are authorized to use. If the exit IP changes mid-session, the site sees one browser jump between cities or networks. That is unusual for a real user and a common reason flows break. The rule is simple. One sticky session per browser, or per browser context, for the life of that session.
ProxyEmpire Connection Builder / proxy list (host:port:user:pass)
| | |
sid A sid B sid C (each username = one sticky IP)
| | |
browser 1 browser 2 browser 3 SeleniumBase / uc: one process each
or
context 1 context 2 context 3 nodriver: one Chrome, many contexts
In ProxyEmpire, a sticky session is a session ID (sid) inside the proxy username. You can generate sessions in the Connection Builder, create your own 8-digit session IDs, or export many sessions at once as a proxy list in host:port:user:pass format. Rotation can be Same IP (sticky), a new IP on every request, or a custom rotation time of up to 2 hours. For browser work, use sticky, with a rotation time longer than your longest session. The sticky sessions documentation has the dashboard steps, and sticky vs rotating proxies covers when each mode fits.
With nodriver, a single browser can host one context per exported session:
import nodriver as uc
def load_proxies(path):
"""Read an exported host:port:user:pass list into http:// proxy URLs."""
urls = []
with open(path) as fh:
for line in fh:
line = line.strip()
if not line:
continue
host, port, user, password = line.split(":", 3)
urls.append(f"http://{user}:{password}@{host}:{port}")
return urls
async def main():
proxies = load_proxies("proxyempire_sessions.txt")[:3]
browser = await uc.start()
tabs = []
for proxy_url in proxies:
tab = await browser.create_context(
"https://api.ipify.org?format=json",
proxy_server=proxy_url,
)
tabs.append(tab)
await tabs[-1].sleep(3)
for i, tab in enumerate(tabs):
ip = await tab.evaluate("document.body.innerText", return_by_value=True)
print(f"context {i}: {ip}")
browser.stop()
if __name__ == "__main__":
uc.loop().run_until_complete(main())
With SeleniumBase, run one SB or Driver per session. When several threads use different authenticated proxies at once, pass multi_proxy=True so each browser gets its own auth extension. Also add sys.argv.append("-n") as the UC Mode docs describe for concurrent.futures, so SeleniumBase applies its thread locking. With pytest, pytest --uc -n 4 handles that for you.
Some sizing advice that applies to all three tools. Each real Chrome instance costs real memory and CPU, so size concurrency from measurements on your own hardware, not from guesses. When you outgrow a single machine, our write-up on designing a browser cluster covers scheduling and session lifecycles. For the network side, residential IPs suit most consumer-facing sites. Mobile 4G proxies help where carrier IPs are the norm, such as mobile-first apps.
Responsible Use
Stealth is not permissionAll three projects advertise CAPTCHA and challenge handling. nodriver has a cf_verify() helper and SeleniumBase has solve_captcha(). We don’t cover those here, on purpose. A challenge is the site telling you it has doubts about your traffic. The sustainable response is to slow down, reduce concurrency, use an official API or data feed if one exists, or get permission, not to escalate.
Legitimate uses of these tools include end-to-end testing of your own sites behind bot protection, QA of localized pages and prices, ad and affiliate verification, accessibility and uptime monitoring, and collecting public, non-personal data at modest rates. In every case:
- Read the target’s terms of service and robots.txt, and respect rate limits.
- Prefer official APIs where they exist.
- Don’t automate logins to accounts you don’t own or aren’t authorized to operate. Don’t collect personal data without a lawful basis.
- Run security and bot-detection testing only against systems you are allowed to test.
- Store proxy credentials in a secret manager or environment variables, never in code or logs.
These tools make an automated browser behave more like a real one. They don’t make an unauthorized workload acceptable.
Which One Should You Use in 2026?
Recommendation by use case| Your situation | Pick | Why |
|---|---|---|
| New project, sync Python, authenticated proxies | SeleniumBase | Built-in proxy="user:pass@host:port", UC and CDP Modes, active releases, MIT |
| Async pipeline, many isolated sessions | nodriver | Native asyncio and a per-context authenticated proxy in one Chrome process |
| Existing undetected-chromedriver codebase | SeleniumBase Driver(uc=True) | Closest API to uc, adds proxy auth, actively maintained |
| QA team already using pytest | SeleniumBase | pytest plugin, --uc, --proxy and -n on the command line |
| Low-level CDP control | nodriver | The whole CDP surface through nodriver.cdp, minimal abstraction |
| Licensing-sensitive product | SeleniumBase | MIT, compared with GPL-3.0 and AGPL-3.0 |
| Must stay on Python 3.9 | Plain Selenium or Playwright | SeleniumBase needs 3.10+, nodriver 0.50.6 fails to import on 3.9, uc is unmaintained |
When should you not use any of them? If your workload doesn’t need Chrome at all, an HTTP client is cheaper and faster. Our Python scraping with residential proxies guide covers requests, httpx and Scrapy. If you work in Node.js, the equivalent decisions are in the Puppeteer proxy authentication guide. And if you just need managed rendering with proxies attached, the ProxyEmpire Scraping API removes the browser fleet from your side entirely.
Frequently Asked Questions
undetected-chromedriver, nodriver and SeleniumBase, in shortIs undetected-chromedriver still maintained in 2026?
Barely. Its last PyPI release is 3.5.5 from 17 February 2024, and the last commit to the repository was in July 2025. The repository is not archived, but its author describes nodriver as the official successor. For new work, use nodriver or SeleniumBase’s UC Mode, which descends from undetected-chromedriver.
What is the difference between undetected-chromedriver and nodriver?
undetected-chromedriver patches ChromeDriver and keeps the synchronous Selenium API. nodriver removes ChromeDriver and Selenium entirely, talks to Chrome over the DevTools Protocol, and is fully asynchronous. They have the same author, and nodriver is the official successor.
What is SeleniumBase UC Mode vs CDP Mode?
UC Mode (SB(uc=True) or Driver(uc=True)) uses a modified driver that launches Chrome first and disconnects WebDriver during sensitive steps. CDP Mode (sb.activate_cdp_mode(url)) disconnects WebDriver and drives the browser over the DevTools Protocol. SeleniumBase’s documentation presents it as the successor to plain UC Mode. Pure CDP Mode (sb_cdp.Chrome()) skips WebDriver from the start.
How do I use an authenticated proxy with SeleniumBase?
Pass it as proxy="USER:PASS@HOST:PORT" to SB(), Driver() or sb_cdp.Chrome(). For ProxyEmpire that is your Connection Builder username and password with v2.proxyempire.io:5000. SeleniumBase handles the authentication challenge itself. Add multi_proxy=True when several threads use different authenticated proxies.
Does nodriver support proxies with a username and password?
Yes, per browser context. await browser.create_context(url, proxy_server="http://USER:PASS@HOST:PORT") starts a local forwarder that adds the credentials upstream. The default context created by start() is not proxied, and the HTTP forwarder handles HTTPS (CONNECT) traffic only.
Why can’t I put my proxy password in –proxy-server?
Chrome’s --proxy-server switch accepts only a scheme, host and port. Credentials have to be supplied when the proxy sends its authentication challenge. That means an extension, a DevTools auth handler, or a local forwarder that adds them for you.
Do these tools hide my IP address?
No. They change how the browser presents itself, not where its traffic comes from. undetected-chromedriver’s own README warns that datacenter or low-reputation IPs will likely still be blocked. Pair any of them with an appropriate proxy and use one sticky session per browser.
Give every browser its own sticky IP
ProxyEmpire’s gateway at v2.proxyempire.io:5000 serves rotating and sticky residential, mobile and datacenter IPs over HTTP, HTTPS and SOCKS5, with country, region, city, ZIP, ISP and ASN targeting. Plug it into SeleniumBase’s proxy= argument or nodriver’s create_context() in one line.



