Playwright proxy errors: ERR_TUNNEL_CONNECTION_FAILED and the rest
The proxy answers with a number; Chromium and Firefox turn it into different names, or into a 30-second timeout. Here is which is which.
In short
Pass the proxy login as username and password, not inside server. If a proxied page only times out, the login is wrong. ERR_TUNNEL_CONNECTION_FAILED means the login was fine and the username’s targeting wasn’t. SOCKS5 with a password doesn’t work in Playwright at all; use the HTTP port.
Every error, reproduced
One mistake at a time, same gateway, same page, both browsers. The proxy column is what the gateway answered.
| What we broke | Proxy | Chromium | Firefox |
|---|---|---|---|
| Wrong password | 407 | Timeout 30000ms exceeded | NS_ERROR_PROXY_CONNECTION_REFUSED |
| Login inside server (http://USER:PASS@…) | 407 | Timeout 30000ms exceeded | NS_ERROR_PROXY_CONNECTION_REFUSED |
| A username value it can’t read (-country-usa) | 403 | net::ERR_TUNNEL_CONNECTION_FAILED | NS_ERROR_PROXY_FORBIDDEN |
| Filters no exit matches (Chicago in Germany) | 465 | net::ERR_TUNNEL_CONNECTION_FAILED | NS_ERROR_PROXY_CONNECTION_REFUSED |
| socks5:// with a password | — | Browser does not support socks5 proxy authentication | the same |
| https:// as the proxy scheme | — | net::ERR_PROXY_CONNECTION_FAILED | SSL_ERROR_UNKNOWN |
| A port where nothing listens | — | net::ERR_PROXY_CONNECTION_FAILED | NS_ERROR_PROXY_CONNECTION_REFUSED |
| Another gateway’s key (-cc-DE) | 200 | loads, exit in the US | loads, exit in Russia |
| No scheme in server | 200 | loads | loads |
Two things stand out. Chromium never names a login problem with the default setup; it waits. And Firefox uses one name, NS_ERROR_PROXY_CONNECTION_REFUSED, for a wrong password, a filter that matches nothing and a closed port alike. The browser error tells you roughly where to look; the proxy’s status tells you what to fix.
Playwright launches Chromium as a slimmed-down “headless shell” by default. When the proxy answered 407 to a wrong password, that shell didn’t fail the navigation; page.goto() waited until Playwright’s 30-second timeout. Launched with channel="chromium", the full browser in new headless mode, the same mistake failed at once:
So a proxied scraper whose pages “just time out” most likely has a login problem. Copy username and password again, and launch with channel="chromium" while you debug so the error comes back right away.
The login: 407
Playwright takes the login only from the username and password fields of the proxy settings. A login written into server was not sent in our test, exactly as if there was none. Also check that you didn’t paste a password with a trailing space.
A value in the username: 403
Country, city, network and session go into the proxy username, and our gateway refuses values it can’t read: a three-letter country code, a city with a space, a session label on the rotating port. Firefox calls that NS_ERROR_PROXY_FORBIDDEN, Chromium ERR_TUNNEL_CONNECTION_FAILED. A key it doesn’t know is a different case: -cc-DE was ignored and the page loaded from the US and from Russia. Use -country-de.
Filters no exit matches: 465
The login and the username are fine, but no residential exit fits every filter at once. Drop the narrowest filter first, usually the city. Both browsers hide the number; to see it, run the same login through curl:
curl -sv -x http://via.proxies.vision:9000 --proxy-user "USER-country-de:PASS" https://ipinfo.io/json 2>&1 | grep "< HTTP"SOCKS5 with a password
Neither browser can authenticate to a SOCKS5 proxy, and Playwright refuses up front: Browser does not support socks5 proxy authentication, raised by new_context() before any page opens. Use the HTTP port of the same proxy with the same login. On our gateway HTTP and SOCKS5 have their own ports, so change scheme and port: http://via.proxies.vision:9000.
Our rotating port gives a new exit per connection, and a browser context keeps its connections open. So pages in one context share an IP, and a new context gets a new one. We loaded an IP checker in each:
One context per identity, then: a fresh context when you want a fresh IP, and the same session label when an identity has to survive a context being closed. The same rule holds in Puppeteer.
A setup that works
Login in its own fields, one context per identity. Ran as shown; the output is below.
import os
from playwright.sync_api import sync_playwright
USER = os.environ["PROXY_USER"]
PASS = os.environ["PROXY_PASS"]
def proxy(country: str = "", session: str = "") -> dict:
"""Playwright proxy settings: login in its own fields, never in server."""
user = USER + (f"-country-{country}" if country else "")
if session:
# Sticky port: the same label keeps the same IP for 10 minutes.
return {"server": "http://via.proxies.vision:10000", "username": f"{user}-session-{session}-lifetime-10", "password": PASS}
return {"server": "http://via.proxies.vision:9000", "username": user, "password": PASS}
with sync_playwright() as p:
# channel="chromium" reports proxy errors at once; the default headless
# shell waited 30 seconds on a wrong password in our test.
browser = p.chromium.launch(channel="chromium")
# A new context = a new connection to the proxy = a new IP.
for _ in range(2):
context = browser.new_context(proxy=proxy("de"))
page = context.new_page()
page.goto("https://api.ipify.org")
print("new context:", page.inner_text("body"))
context.close()
# One identity across contexts: the same session label.
for _ in range(2):
context = browser.new_context(proxy=proxy("de", session="account1"))
page = context.new_page()
page.goto("https://api.ipify.org")
print("account1:", page.inner_text("body"))
context.close()
browser.close()new context: 46.114.177.235
new context: 91.22.159.104
account1: 2.245.177.252
account1: 2.245.177.252Node.js
The same settings in JavaScript: browser.newContext({ proxy: { server, username, password } }). The field names are identical.
Retry dropped pages
An exit is someone’s home connection and can go away mid-page. Catch the navigation error, open a new context and try again once; a 403 or 407 won’t change on a retry, so don’t loop on those.
Why does Playwright time out instead of showing a proxy error?
What does 'Browser does not support socks5 proxy authentication' mean?
Can I put the username and password into the proxy server URL?
What does NS_ERROR_PROXY_CONNECTION_REFUSED mean in Playwright Firefox?
How do I get a new IP per page in Playwright?
Keep reading
aiohttp Proxy Errors: ClientHttpProxyError 407, SOCKS5 and trust_env
aiohttp's ClientHttpProxyError explained, why its message leaks your proxy password, socks5:// without aiohttp-socks, and rotation.
6 min read · #python#errors
curl: (56) CONNECT tunnel failed, response 407 — and git's Proxy CONNECT aborted
What curl's 'CONNECT tunnel failed, response 407' means, every curl proxy exit code we hit, and why git fails through a proxy that works in curl.
6 min read · #errors#authentication
Missing dependencies for SOCKS support: the Fix, and Why You See It Without SOCKS
Install requests[socks], or find the environment variable that asked for SOCKS. Plus socks5 vs socks5h and the errors that come next.
6 min read · #python#socks5
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.