Why proxies for Selenium are a separate engineering problem
Selenium was built for functional testing, not for stealth. It launches a real browser, drives it through WebDriver, and by default it does absolutely nothing to hide where the traffic comes from. So the moment you point your script at Google SERP, a marketplace, or a social platform, the IP address becomes the weakest link in the chain — long before fingerprints or behavior come into play.
That is why a working selenium proxy setup is two tasks, not one. First, the technical part: passing the right configuration into the driver so that every request — including DNS and WebSocket traffic — leaves through the tunnel. Second, the strategic part: choosing an IP type that antibot systems are willing to trust. Below we cover both, with code for Chrome and Firefox, the differences between Selenium versions, and the reasons mobile IPs behave differently from everything else.
Selenium HTTP proxy: three ways to wire it up
There are three practical approaches. They are not equivalent, and picking the wrong one is the most common reason people see "it works in curl but not in the browser."
Method 1: browser arguments (the fast path)
The simplest option is a launch flag. No extra import, no extra file, one line in your options object:
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://HOST:PORT")
driver = webdriver.Chrome(options=options)
driver.get("https://ipinfo.io/json")This covers HTTP and HTTPS in one setting and works in Chrome, Edge, and any Chromium build. The limitation: the flag does not accept credentials. If your endpoint needs login and password, the browser will throw a native auth dialog that WebDriver cannot click.
Method 2: the Proxy class from the Selenium API
If you prefer the framework-native route, use the dedicated class instead of raw arguments. It maps onto W3C capabilities and works across drivers:
from selenium import webdriver
from selenium.webdriver.common.proxy import Proxy, ProxyType
prox = Proxy()
prox.proxy_type = ProxyType.MANUAL
prox.http_proxy = "HOST:PORT"
prox.ssl_proxy = "HOST:PORT"
prox.socks_proxy = "HOST:PORT" # for SOCKS5 endpoints
options = webdriver.ChromeOptions()
options.proxy = prox
driver = webdriver.Chrome(options=options)
Firefox deserves its own recipe, because it ignores Chromium flags and is configured through preferences. This is also the cleanest way to get SOCKS5 with remote DNS resolution, so your queries do not leak to a local resolver:
options = webdriver.FirefoxOptions()
options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.socks", "HOST")
options.set_preference("network.proxy.socks_port", 1080)
options.set_preference("network.proxy.socks_version", 5)
options.set_preference("network.proxy.socks_remote_dns", True)
options.set_preference("media.peerconnection.enabled", False)That last line kills WebRTC. Skip it and a STUN request can expose your real address while every HTTP header says otherwise — one of the fastest ways to get a session flagged.
Method 3: authenticated endpoints
Most commercial endpoints use login:password. Three ways to handle it in a real browser:
- IP whitelisting — bind access to the machine running the script and drop credentials entirely. The cleanest option for servers with a static address.
- A generated extension — build a small manifest plus background script that answers the auth challenge, then load it with
options.add_extension(). Works, but adds a moving part to maintain. - CDP interception — call
Fetch.enable with handleAuthRequests through driver.execute_cdp_cmd and answer the challenge programmatically. Native to Selenium 4, no third-party wrapper needed.
Pro-tip: many tutorials still recommend Selenium Wire for authenticated tunnels. It is convenient, but it terminates TLS locally, which means your JA3/JA4 signature becomes the signature of a Python library, not of Chrome. On targets that fingerprint TLS, that single detail can outweigh everything the proxy gave you. Prefer whitelisting or CDP auth when the target is aggressive.
Selenium version differences that break configurations
A lot of copy-pasted code fails simply because it was written for another version. Quick map:
| Version | How you pass settings | Notes |
|---|
| Selenium 3 | desired_capabilities dictionary | Removed in 4.x — raises TypeError |
| Selenium 4.0–4.5 | Options object, capabilities deprecated | Both worked, with warnings |
| Selenium 4.6+ | Options only, Selenium Manager bundled | No manual driver file needed |
Since 4.6 the driver binary is resolved automatically, so you no longer keep a chromedriver file next to your script or juggle Service paths. Pin your Selenium version in requirements and keep one configuration style across the project.
The part no code sample solves: which IP you send
Assume your setup is perfect. The tunnel works, DNS is remote, WebRTC is off. On a protected target you can still collect a challenge on request one, because layer one of every antibot stack is IP intelligence: the ASN type, the fraud score, blacklist presence, and geo consistency.
| IP source | ASN type | Typical fraud score | Fit for automation |
|---|
| Datacenter | hosting | 75–100 | Internal tests only |
| Residential (other category) | isp | Moderate | Medium-strength targets |
| Mobile (carrier) | mobile | 0–15 | Aggressively protected web targets |
The reason mobile addresses score so well is structural, not cosmetic. Carriers run Carrier-Grade NAT: one public IPv4 is shared by roughly 500 to 5000 subscribers at the same time. Banning that address means banning a crowd of paying customers, so platforms answer with soft measures — a captcha, rate limiting — instead of a hard block. Reputation scoring also loses precision, because thousands of unrelated behavior patterns arrive from the same address.
Add historically clean ranges (mobile blocks were never spam or DDoS sources at datacenter scale) and honest geolocation, and you get the highest trust score of all proxy types. On the same targets where other categories drop below 80% success, teams typically report 95–99% with carrier IPs.
Rotation strategy: sticky sessions versus per-request changes
Selenium keeps state — cookies, localStorage, an open session. Rotating the address mid-session is a red flag, unless you are deliberately simulating a network handover. Two working patterns:
- Sticky session for anything with a login, a cart, multi-step navigation, or paginated crawling. One profile, one port, one address for the whole scenario.
- Controlled rotation between scenarios: finish a run, close the driver, request a new address, start a fresh profile. In mobile networks a new IP arrives through a PDP context reset — ordinary network behavior, indistinguishable from a phone reconnecting.
In practice this means your rotation should be driven by your script, not by a timer you cannot see. A rotation link fired with requests between Selenium runs is the simplest orchestration method there is:
import requests, time
requests.get(ROTATION_URL, timeout=30)
time.sleep(7) # let the modem re-register
driver = webdriver.Chrome(options=options)
What a proxy will not do for you
The network layer is one of four. Be honest about the rest:
- Fingerprint (layer 3). Vanilla ChromeDriver exposes automation markers, and Canvas, WebGL, and font sets stay identical across runs. For account work, pair carrier IPs with an antidetect browser — Multilogin, GoLogin, AdsPower, Dolphin Anty, Octo Browser — and drive it through the same WebDriver protocol. For pure scraping, undetected-chromedriver removes the loudest markers.
- Behavior (layer 2). Randomize delays, scroll, move the cursor, vary navigation order. Requests arriving every 200 ms on the dot are recognizable regardless of the address.
- Correlation (layer 4). Timezone,
Accept-Language, locale, and User-Agent must match the geo of the IP. A German address with an en-US browser and a Moscow timezone is an instant mismatch.
Pro-tip: before blaming the target, verify the address itself. Open ipqualityscore.com, iphub.info, or Spur.us through the tunnel and confirm the ASN type reads mobile. If a supplier advertises carrier IPs but the lookup says hosting, that is substitution, and no amount of Selenium tuning will fix it.
How OnlineProxy fits into a Selenium stack
OnlineProxy sells mobile ports — IPs issued by real carriers to real devices with real SIM cards, with a choice of country, city, and operator. Billing is per port for a period: 1 day, 7 days, or 30 days, with 24 hours as the minimum billable window. There is no per-gigabyte metering on any plan; traffic is unmetered, which means no gigabyte accounting rather than unlimited bandwidth. Prices depend on country and carrier and are shown on the tariff page.
| Capability | Lite | Regular |
|---|
| Port access | Shared, up to 5 users | Dedicated device |
| IP rotation | Automatic, every 2–5 min | Sticky, by link, by timer |
| Device reboot | Not available | Available |
| Support | Standard | Priority |
For Selenium the practical difference is control. Lite rotates on its own schedule, which suits short stateless requests. Regular gives you the whole device, so a login flow can hold one address for as long as it needs and change it on your command through a link — the behavior automation actually depends on. Both HTTP(S) and SOCKS5 are available, with login:password or IP whitelisting, plus an API for rotation and monitoring.
One free server proxy is available through the widget on the site, from a choice of countries. It is a different category and it is useful for one thing only: smoke-testing that your driver configuration, DNS, and auth work end to end. There is no free mobile plan and no free trial on mobile ports. Cashback is credited as promo credit to your internal balance after a paid rental ends, and refunds follow the refund and replacement policy: full within the first hour after access is issued, later minus the time used, with a replacement offered first when the issue is technical. Support runs around the clock with a target first response of four hours.
The short version
Wire the tunnel with browser options or the Proxy class, disable WebRTC, resolve DNS remotely, and keep one address per session. Then remember the formula: carrier IP for network legitimacy, unique fingerprint for browser isolation, realistic timing for behavior, and matching geo signals for data correlation. Any Selenium proxy configuration that covers only the first term will pass your local test and fail in production — which is exactly why the IP type deserves as much attention as the code.