Chrome — extension approach
Chrome ignores credentials embedded in --proxy-server. The reliable workaround is a tiny packed extension that calls chrome.proxy and handles the auth challenge. Build it in memory at runtime so there is nothing to ship:
import base64, io, zipfile
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
PROXY_HOST = "gw.justproxies.online"
PROXY_PORT = 8080
PROXY_USER = "USER"
PROXY_PASS = "PASS"
_manifest = """{
"version": "1.0.0",
"manifest_version": 2,
"name": "Proxy Auth",
"permissions": ["proxy", "webRequest", "webRequestBlocking", "<all_urls>"],
"background": {"scripts": ["bg.js"]}
}"""
_background = f"""
chrome.proxy.settings.set({{
value: {{
mode: "fixed_servers",
rules: {{
singleProxy: {{ scheme: "http", host: "{PROXY_HOST}", port: {PROXY_PORT} }},
bypassList: []
}}
}},
scope: "regular"
}}, () => {{}});
chrome.webRequest.onAuthRequired.addListener(
(details, callback) => callback({{
authCredentials: {{ username: "{PROXY_USER}", password: "{PROXY_PASS}" }}
}}),
{{ urls: ["<all_urls>"] }},
["blocking"]
);
"""
def build_proxy_extension(host, port, user, pw):
manifest = _manifest
background = f"""
chrome.proxy.settings.set({{
value: {{
mode: "fixed_servers",
rules: {{
singleProxy: {{ scheme: "http", host: "{host}", port: {port} }},
bypassList: []
}}
}},
scope: "regular"
}}, () => {{}});
chrome.webRequest.onAuthRequired.addListener(
(details, callback) => callback({{
authCredentials: {{ username: "{user}", password: "{pw}" }}
}}),
{{ urls: ["<all_urls>"] }},
["blocking"]
);
"""
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr("manifest.json", manifest)
zf.writestr("bg.js", background)
return base64.b64encode(buf.getvalue()).decode()
options = Options()
options.add_encoded_extension(
build_proxy_extension(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
)
driver = webdriver.Chrome(options=options)
driver.get("https://api.ipify.org")
print(driver.find_element("tag name", "body").text)
driver.quit()
webRequest blocking API (required for auth) is not available in MV3. Pack it fresh per session rather than loading from disk so credentials never persist.Firefox — Python
Firefox accepts proxy settings and port numbers natively via preferences. For auth, Firefox pops a credential dialog once per session — suppress it by pre-seeding the password manager or use the extension technique above. The simpler path for automation is to whitelist your server IP (contact support) so no auth challenge fires at all.
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
options = Options()
options.set_preference("network.proxy.type", 1)
options.set_preference("network.proxy.http", "gw.justproxies.online")
options.set_preference("network.proxy.http_port", 8080)
options.set_preference("network.proxy.ssl", "gw.justproxies.online")
options.set_preference("network.proxy.ssl_port", 8080)
options.set_preference("network.proxy.no_proxies_on", "")
# Avoid the credential dialog by whitelisting your egress IP with support,
# or provide pre-seeded credentials via the signon database.
driver = webdriver.Firefox(options=options)
driver.get("https://api.ipify.org")
print(driver.find_element("tag name", "body").text)
driver.quit()
Java + ChromeDriver
Same extension trick, adapted for Java. Construct the zip in memory and pass it as a Base64 string via ChromeOptions.addExtensions(File) — or use the encoded form.
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.util.Base64;
import java.util.zip.*;
import java.io.*;
public class SeleniumProxy {
static final String HOST = "gw.justproxies.online";
static final int PORT = 8080;
static final String USER = "USER";
static final String PASS = "PASS";
static String buildExtension() throws Exception {
String manifest = """
{"version":"1.0.0","manifest_version":2,"name":"PA",
"permissions":["proxy","webRequest","webRequestBlocking","<all_urls>"],
"background":{"scripts":["bg.js"]}}""";
String bg = String.format("""
chrome.proxy.settings.set({value:{mode:"fixed_servers",
rules:{singleProxy:{scheme:"http",host:"%s",port:%d}}},
scope:"regular"},()=>{});
chrome.webRequest.onAuthRequired.addListener(
(d,cb)=>cb({authCredentials:{username:"%s",password:"%s"}}),
{urls:["<all_urls>"]},["blocking"]);
""", HOST, PORT, USER, PASS);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
try (ZipOutputStream zip = new ZipOutputStream(buf)) {
zip.putNextEntry(new ZipEntry("manifest.json"));
zip.write(manifest.getBytes()); zip.closeEntry();
zip.putNextEntry(new ZipEntry("bg.js"));
zip.write(bg.getBytes()); zip.closeEntry();
}
return Base64.getEncoder().encodeToString(buf.toByteArray());
}
public static void main(String[] args) throws Exception {
ChromeOptions opts = new ChromeOptions();
opts.addEncodedExtensions(buildExtension());
ChromeDriver driver = new ChromeDriver(opts);
driver.get("https://api.ipify.org");
System.out.println(driver.findElement(
org.openqa.selenium.By.tagName("body")).getText());
driver.quit();
}
}
Sticky sessions
Bake the session token into the username before building the extension. Every request the browser makes during that session exits through the same IP.
import secrets
session = secrets.token_hex(8)
user_with_session = f"USER-session-{session}"
options = Options()
options.add_encoded_extension(
build_proxy_extension(PROXY_HOST, PROXY_PORT, user_with_session, PROXY_PASS)
)
driver = webdriver.Chrome(options=options)
# All page loads, XHR, image fetches — same exit IP.
SSL certificate errors
Our proxy tunnels HTTPS via CONNECT — we never see the TLS handshake. SSL warnings in Selenium are target-side issues, not proxy issues. The standard flag to suppress them for self-signed certs on test targets:
# Chrome
options.add_argument("--ignore-certificate-errors")
# Firefox
options.set_preference("webdriver.accept.untrusted.certs", True)