Selenium proxy with username and password, in every language
Chrome’s --proxy-server flag has no place for a username and password, which is why most Selenium proxy setups stall on a login prompt. The fix is built into Selenium: answer the proxy’s login through Chrome’s DevTools. Here it is in Python, Java, JavaScript, C# and Ruby, tested against the Ludax gateway, with no extension and no third-party proxy wrapper.
No extension, no wrapperSelenium answers the login itself
Five languages, testedPython, Java, JavaScript, C#, Ruby
Selenium sets the proxy with Chrome’s --proxy-server=http://gw.ludaxproxy.com:7777; that flag takes no credentials, so Chrome asks for the login and Selenium must answer it.
Selenium answers through Chrome’s DevTools (Fetch.authRequired): HasAuthentication in Java, driver.register() in JavaScript and Ruby, NetworkAuthenticationHandler in C#, and the DevTools connection in Python.
Tested on 2026-10-23 with Selenium 4.51 and Chrome 155. Selenium’s WebDriver BiDi auth handler did not answer a proxy’s login in our tests, and Chrome 137 and later ignore --load-extension, so the usual extension trick no longer works in Chrome.
Username resi-lx_k3m9q2p7xa, password YOUR_PASSWORD plus options: -session-<token>-time-<minutes> holds one IP, -country-de picks the country.
Use the standard gateways (gw.ludaxproxy.com:7777, Premium gw.ludaxproxy.com:8080): Chrome already sends a real browser TLS handshake.
A gateway 502 raises net::ERR_TUNNEL_CONNECTION_FAILED (retry it); a missing login leaves a blank page; a refused one shows “This site can’t be reached”.
Using an AI assistant? Give it this guide
One Markdown file with everything ChatGPT, Claude, Cursor or Copilot needs to write working Ludax code for Selenium: the connection format, the gateways in force, sessions, errors and tested examples. Copy it into the chat, or paste this prompt:
Read https://ludaxproxy.com/docs/selenium.md and use Ludax proxies in my Selenium project.
Why Selenium needs extra code for proxy authentication
Two things are true at once: Chrome can use an authenticated proxy, and Chrome will not take the password on its command line.
Selenium passes proxy settings to Chrome as the --proxy-server flag, which holds a host and a port only. When the gateway answers 407 Proxy Authentication Required, Chrome asks for the login the way it would ask a person. In a script nobody answers, so the page stays blank.
Selenium can answer instead, through Chrome’s DevTools protocol: it pauses the request at the login, sends the username and password, and lets it go on. The table shows the call in each language. Each one was run against an authenticated proxy before it went on this page.
PROXY LOGIN IN SELENIUM, BY LANGUAGE (TESTED)
Language
How Selenium answers the login
Extra package
Python
The DevTools connection (driver.bidi_connection()) and Fetch.authRequired, wrapped in a 30-line helper below
driver.Manage().Network.AddAuthenticationHandler(...), then StartMonitoring()
None
Ruby
driver.register(username:, password:)
selenium-devtools
What we tested and found not to work for a proxy login (2026-10, Selenium 4.51, Chrome 155): the WebDriver BiDi handler (driver.network.add_auth_handler in Python), a Chrome extension loaded with --load-extension or as a .crx (both failed; Chrome 137 and later ignore --load-extension), and Firefox, where the login dialog stays open. Selenium Wire, the old workaround, is no longer maintained. For Firefox with a proxy login, Playwright is the simpler road.
[ 02 / QUICKSTART ]
Set up a Selenium proxy with authentication in three steps
If Chrome prints a JSON answer with a residential IP, it works. Pick your language once: every code block on this page follows it.
Get your username and password
In the dashboard, open Generate. Your username looks like lx_k3m9q2p7xa and gets the product prefix: resi- for Residential, premi- for Residential Premium. Each plan has its own 16-character password. The full format is in proxy authentication.
No account yet? Get 1 GB of Residential for $0.99: an email address is all it takes, you pay in crypto, and you are ready in a minute.
# selenium-devtools brings the DevTools support driver.register needs
gem install selenium-webdriver selenium-devtools
Selenium downloads the matching ChromeDriver by itself. Its DevTools support covers the newest Chrome versions, so update Selenium when Chrome updates.
Choose the product, the country and whether the IP should stay the same, and copy a complete program for your language. Type your own username and password to get code you can run as is: they stay in this browser tab and are never sent anywhere.
Selenium proxy recipes: sticky IPs, geo, bandwidth and errors
Each one runs on its own (Python with the helper from the quickstart next to it). They follow the language you picked above.
01
One IP per browser: rotating proxies in Selenium
Several accounts or jobs, each in its own browser and from its own IP. A new session token gives a new IP; the same token keeps it for every request that browser makes.
sticky.py
import json
import secrets
from selenium import webdriver
from ludax_login import with_proxy_login
for job in ["account-1", "account-2", "account-3"]:
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://gw.ludaxproxy.com:7777")
driver = webdriver.Chrome(options=options)
# a new session token = a new IP, held by this browser for 30 minutes
password = f"YOUR_PASSWORD-session-{secrets.token_hex(4)}-time-30-country-us"defwork(driver):
driver.get("https://api.ludaxproxy.com/whoami")
print(job, json.loads(driver.find_element("tag name", "body").text)["ip"])
try:
with_proxy_login(driver, "resi-lx_k3m9q2p7xa", password, work)
finally:
driver.quit()
Sticky.java
import java.security.SecureRandom;
import java.util.HexFormat;
import org.openqa.selenium.By;
import org.openqa.selenium.HasAuthentication;
import org.openqa.selenium.UsernameAndPassword;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
publicclass Sticky {
publicstaticvoidmain(String[] args) {
SecureRandom random = newSecureRandom();
for (String job : new String[] {"account-1", "account-2", "account-3"}) {
ChromeOptions options = newChromeOptions();
options.addArguments("--proxy-server=http://gw.ludaxproxy.com:7777");
WebDriver driver = newChromeDriver(options);
try {
// a new session token = a new IP, held by this browser for 30 minutes
byte[] bytes = new byte[4];
random.nextBytes(bytes);
String password = "YOUR_PASSWORD-session-" + HexFormat.of().formatHex(bytes) + "-time-30-country-us";
((HasAuthentication) driver).register(UsernameAndPassword.of("resi-lx_k3m9q2p7xa", password));
driver.get("https://api.ludaxproxy.com/whoami");
System.out.println(job + " " + driver.findElement(By.tagName("body")).getText());
} finally {
driver.quit();
}
}
}
}
sticky.mjs
import { randomBytes } from"node:crypto";
import { Builder, By } from"selenium-webdriver";
import chrome from"selenium-webdriver/chrome.js";
for (const job of ["account-1", "account-2", "account-3"]) {
const options = new chrome.Options().addArguments("--proxy-server=http://gw.ludaxproxy.com:7777");
const driver = awaitnewBuilder().forBrowser("chrome").setChromeOptions(options).build();
try {
// a new session token = a new IP, held by this browser for 30 minutesconst password = `YOUR_PASSWORD-session-${randomBytes(4).toString("hex")}-time-30-country-us`;
await driver.register("resi-lx_k3m9q2p7xa", password, await driver.createCDPConnection("page"));
await driver.get("https://api.ludaxproxy.com/whoami");
console.log(job, JSON.parse(await driver.findElement(By.css("body")).getText()).ip);
} finally {
await driver.quit();
}
}
Sticky.cs
using System.Security.Cryptography;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
foreach (var job innew[] { "account-1", "account-2", "account-3" })
{
var options = newChromeOptions();
options.AddArgument("--proxy-server=http://gw.ludaxproxy.com:7777");
usingvar driver = newChromeDriver(options);
// a new session token = a new IP, held by this browser for 30 minutesvar token = Convert.ToHexString(RandomNumberGenerator.GetBytes(4)).ToLowerInvariant();
var network = driver.Manage().Network;
network.AddAuthenticationHandler(new NetworkAuthenticationHandler
{
UriMatcher = _ => true,
Credentials = newPasswordCredentials("resi-lx_k3m9q2p7xa", $"YOUR_PASSWORD-session-{token}-time-30-country-us"),
});
await network.StartMonitoring();
driver.Navigate().GoToUrl("https://api.ludaxproxy.com/whoami");
Console.WriteLine($"{job} {driver.FindElement(By.TagName("body")).Text}");
driver.Quit();
}
sticky.rb
require "securerandom"
require "selenium-webdriver"
%w[account-1 account-2 account-3].eachdo |job|
options = Selenium::WebDriver::Options.chrome(args: ["--proxy-server=http://gw.ludaxproxy.com:7777"])
driver = Selenium::WebDriver.for(:chrome, options: options)
begin# a new session token = a new IP, held by this browser for 30 minutes
password = "YOUR_PASSWORD-session-#{SecureRandom.hex(4)}-time-30-country-us"
driver.register(username: "resi-lx_k3m9q2p7xa", password: password)
driver.get("https://api.ludaxproxy.com/whoami")
puts "#{job} #{driver.find_element(tag_name: "body").text}"
ensure
driver.quitendend
Why a token per browser, and not simply no session? A page is dozens of connections. Without a token each new connection can leave from a different IP, which no real visitor does. With one token everything the browser loads shares one IP. Sessions hold up to 1000 minutes on Residential and Premium; always send -time-.
02
Geo-targeting with a matching language and time zone
Local prices, search results and ads. Pair the exit country with the browser’s language (--lang) and time zone (DevTools Emulation.setTimezoneOverride).
geo.py
from selenium import webdriver
from ludax_login import with_proxy_login
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://gw.ludaxproxy.com:7777")
options.add_argument("--lang=de-DE") # the browser's language
driver = webdriver.Chrome(options=options)
driver.execute_cdp_cmd("Emulation.setTimezoneOverride", {"timezoneId": "Europe/Berlin"})
defwork(driver):
driver.get("https://api.ludaxproxy.com/whoami")
print(driver.find_element("tag name", "body").text)
print(driver.execute_script("return [navigator.language, Intl.DateTimeFormat().resolvedOptions().timeZone]"))
try:
# an IP in Munich for 30 minuteswith_proxy_login(driver, "resi-lx_k3m9q2p7xa",
"YOUR_PASSWORD-session-muc01-time-30-country-de-state-by-city-munich", work)
finally:
driver.quit()
Order matters in the password: session first, then -country-, -state-, -city-. Codes for every country are in the locations directory.
03
Save bandwidth: do not load images
Plans are priced per GB, and images are most of a page’s weight. One Chrome preference stops them before they cross the proxy.
save_data.py
from selenium import webdriver
from ludax_login import with_proxy_login
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://gw.ludaxproxy.com:7777")
# images are never downloaded, so they never count against your GB
options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
driver = webdriver.Chrome(options=options)
defwork(driver):
driver.get("https://en.wikipedia.org/wiki/Proxy_server")
print(driver.title)
try:
with_proxy_login(driver, "resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-session-wiki01-time-10-country-us", work)
finally:
driver.quit()
SaveData.java
import java.util.Map;
import org.openqa.selenium.HasAuthentication;
import org.openqa.selenium.UsernameAndPassword;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
publicclass SaveData {
publicstaticvoidmain(String[] args) {
ChromeOptions options = newChromeOptions();
options.addArguments("--proxy-server=http://gw.ludaxproxy.com:7777");
// images are never downloaded, so they never count against your GB
options.setExperimentalOption("prefs", Map.of("profile.managed_default_content_settings.images", 2));
WebDriver driver = newChromeDriver(options);
try {
((HasAuthentication) driver).register(UsernameAndPassword.of(
"resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-session-wiki01-time-10-country-us"));
driver.get("https://en.wikipedia.org/wiki/Proxy_server");
System.out.println(driver.getTitle());
} finally {
driver.quit();
}
}
}
save_data.mjs
import { Builder } from"selenium-webdriver";
import chrome from"selenium-webdriver/chrome.js";
const options = new chrome.Options()
.addArguments("--proxy-server=http://gw.ludaxproxy.com:7777")
// images are never downloaded, so they never count against your GB
.setUserPreferences({ "profile.managed_default_content_settings.images": 2 });
const driver = awaitnewBuilder().forBrowser("chrome").setChromeOptions(options).build();
try {
await driver.register("resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-session-wiki01-time-10-country-us",
await driver.createCDPConnection("page"));
await driver.get("https://en.wikipedia.org/wiki/Proxy_server");
console.log(await driver.getTitle());
} finally {
await driver.quit();
}
SaveData.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
var options = newChromeOptions();
options.AddArgument("--proxy-server=http://gw.ludaxproxy.com:7777");
// images are never downloaded, so they never count against your GB
options.AddUserProfilePreference("profile.managed_default_content_settings.images", 2);
usingvar driver = newChromeDriver(options);
var network = driver.Manage().Network;
network.AddAuthenticationHandler(new NetworkAuthenticationHandler
{
UriMatcher = _ => true,
Credentials = newPasswordCredentials("resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-session-wiki01-time-10-country-us"),
});
await network.StartMonitoring();
driver.Navigate().GoToUrl("https://en.wikipedia.org/wiki/Proxy_server");
Console.WriteLine(driver.Title);
driver.Quit();
save_data.rb
require "selenium-webdriver"
options = Selenium::WebDriver::Options.chrome(args: ["--proxy-server=http://gw.ludaxproxy.com:7777"])
# images are never downloaded, so they never count against your GB
options.add_preference("profile.managed_default_content_settings.images", 2)
driver = Selenium::WebDriver.for(:chrome, options: options)
begin
driver.register(username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-session-wiki01-time-10-country-us")
driver.get("https://en.wikipedia.org/wiki/Proxy_server")
puts driver.titleensure
driver.quitend
output
Proxy server - Wikipedia
04
Handle proxy errors: retry a 502, stop on everything else
Production scrapers. A dropped exit is worth another try; a refused login or a wrong address is not.
retry.py
import time
from selenium import webdriver
from selenium.common.exceptionsimport WebDriverException
from ludax_login import with_proxy_login
defopen_page(driver, url, tries=3):
"""Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else."""for attempt inrange(1, tries + 1):
try:
return driver.get(url)
except WebDriverException as error:
dropped = "ERR_TUNNEL_CONNECTION_FAILED"instr(error)
ifnot dropped or attempt == tries:
raise
time.sleep(2 ** (attempt - 1))
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=http://gw.ludaxproxy.com:7777")
driver = webdriver.Chrome(options=options)
defwork(driver):
open_page(driver, "https://api.ludaxproxy.com/whoami")
print(driver.find_element("tag name", "body").text)
try:
with_proxy_login(driver, "resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-country-us", work)
finally:
driver.quit()
import { Builder, By } from"selenium-webdriver";
import chrome from"selenium-webdriver/chrome.js";
// Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else.asyncfunctionopen(driver, url, tries = 3) {
for (let attempt = 1; ; attempt++) {
try {
returnawait driver.get(url);
} catch (error) {
const dropped = String(error.message).includes("ERR_TUNNEL_CONNECTION_FAILED");
if (!dropped || attempt === tries) throw error;
awaitnewPromise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
}
}
}
const options = new chrome.Options().addArguments("--proxy-server=http://gw.ludaxproxy.com:7777");
const driver = awaitnewBuilder().forBrowser("chrome").setChromeOptions(options).build();
try {
await driver.register("resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-country-us", await driver.createCDPConnection("page"));
awaitopen(driver, "https://api.ludaxproxy.com/whoami");
console.log(await driver.findElement(By.css("body")).getText());
} finally {
await driver.quit();
}
Retry.cs
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
var options = newChromeOptions();
options.AddArgument("--proxy-server=http://gw.ludaxproxy.com:7777");
usingvar driver = newChromeDriver(options);
var network = driver.Manage().Network;
network.AddAuthenticationHandler(new NetworkAuthenticationHandler
{
UriMatcher = _ => true,
Credentials = newPasswordCredentials("resi-lx_k3m9q2p7xa", "YOUR_PASSWORD-country-us"),
});
await network.StartMonitoring();
awaitOpen(driver, "https://api.ludaxproxy.com/whoami");
Console.WriteLine(driver.FindElement(By.TagName("body")).Text);
driver.Quit();
// Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else.staticasync Task Open(IWebDriver driver, string url, int tries = 3)
{
for (var attempt = 1; ; attempt++)
{
try
{
driver.Navigate().GoToUrl(url);
return;
}
catch (WebDriverException error) when (
error.Message.Contains("ERR_TUNNEL_CONNECTION_FAILED") && attempt < tries)
{
await Task.Delay(1000 * (1 << (attempt - 1)));
}
}
}
retry.rb
require "selenium-webdriver"
# Open a URL through Ludax: retry a dropped exit (502), stop at once on anything else.defopen_page(driver, url, tries: 3)
attempt = 0begin
attempt += 1
driver.get(url)
rescue Selenium::WebDriver::Error::WebDriverError => e
raise unless e.message.include?("ERR_TUNNEL_CONNECTION_FAILED") && attempt < tries
sleep(2**(attempt - 1))
retry
endend
options = Selenium::WebDriver::Options.chrome(args: ["--proxy-server=http://gw.ludaxproxy.com:7777"])
driver = Selenium::WebDriver.for(:chrome, options: options)
begin
driver.register(username: "resi-lx_k3m9q2p7xa", password: "YOUR_PASSWORD-country-us")
open_page(driver, "https://api.ludaxproxy.com/whoami")
puts driver.find_element(tag_name: "body").textensure
driver.quitend
Headless runs work the same: add --headless=new to the Chrome arguments. Microsoft Edge is Chromium too, so its options and driver take the same proxy flag.
[ 05 / TLS PORTS ]
Do I need the TLS ports with Selenium?
No. The TLS ports exist for HTTP libraries, which give themselves away in the TLS handshake. Chrome does not.
Residential TLS connects to the site with a real browser’s handshake for clients such as Python’s requests, which would otherwise be recognised. Selenium drives a real Chrome, so the site already sees a real Chrome handshake: the whoami answer shows it as tls_profile.
Use the standard gateways, gw.ludaxproxy.com:7777 or Premium gw.ludaxproxy.com:8080: they pass HTTPS through untouched, with no certificate to install in Chrome. If a site still blocks the browser, look at its behaviour or the IP’s reputation: slower pacing, sticky sessions or Residential Premium.
[ 06 / TROUBLESHOOTING ]
Selenium proxy errors and fixes
What Selenium shows, what it means and the one thing to do. All gateway answers: status codes.
SYMPTOM, CAUSE, FIX
You see
Why
Fix
A blank page, no error
Chrome got the 407 and asked for a login nobody answered.
Register the login before the first get(), as in the quickstart.
“This site can’t be reached”
The login was answered but refused: a typo, a prefix that does not match the port, or a plan with no data left.
Copy the credentials again from Generate; check the plan. Every 407 cause.
net::ERR_TUNNEL_CONNECTION_FAILED
The gateway answered 502: the exit dropped or could not reach the site.
Retry (recipe 04); with a sticky session, switch to a new token after two failures.
net::ERR_PROXY_CONNECTION_FAILED
The gateway could not be reached: a wrong host or port, or a firewall.
01How do I use a proxy with a username and password in Selenium?
Start Chrome with --proxy-server=http://gw.ludaxproxy.com:7777, then let Selenium answer the proxy’s login through Chrome’s DevTools: HasAuthentication in Java, driver.register() in JavaScript and Ruby, NetworkAuthenticationHandler in C#, and the DevTools connection in Python. The code for each is on this page.
02Why does Chrome ignore the username and password in --proxy-server?
The flag takes a host and a port only. When the proxy asks for a login, Chrome asks the user. In an automated browser nobody answers, so the page stays blank until Selenium answers through DevTools.
03Does Selenium Wire still work for proxy authentication?
Selenium Wire is no longer maintained. Selenium itself now answers proxy logins through Chrome’s DevTools, with no extra proxy in between, as shown on this page.
04Can I use a Chrome extension for the proxy login?
Not in Chrome 137 or later: it ignores extensions loaded with --load-extension, and in our tests a packed .crx did not answer the login either. Selenium’s DevTools login works without an extension.
05How do I rotate proxies in Selenium?
Start each browser with its own session token in the password (-session-<token>-time-<minutes>). A new token gives a new IP; the same token keeps it. Without a token every new connection can get a new IP, even within one page.
06Does it work with Firefox?
Not for a proxy login: in our tests Selenium could not answer Firefox’s proxy login dialog. Use Chrome or Edge, or Playwright, which logs in to proxies on its own.
[ 08 / KEEP GOING ]
Where to go from here
The products behind the code, and guides for the jobs Selenium runs.