Fixing ERR_ TUNNEL_ CONNECTION_ FAILED in Puppeteer
Every proxy mistake we could reproduce, the error each one causes, and how to fix yours.
In short
The proxy accepted your connection but refused to open the tunnel. With authenticated residential proxies that almost always means the username: a value the gateway can’t read, or filters no exit can match right now.
When Puppeteer loads an HTTPS page through a proxy, Chromium doesn’t hand the proxy your request. It can’t: the request is encrypted for the website, not for the proxy. Instead it asks the proxy to open a tunnel, a plain TCP connection to the website that the proxy relays without looking inside. That request is a single line, CONNECT ipinfo.io:443, sent together with your proxy username and password.
The proxy answers before any website is involved. 200 Connection established means the tunnel is open and Chromium starts the TLS handshake with the site. 407 means “send credentials first”, which Chromium does with whatever you passed to page.authenticate(). Any other answer means the proxy refuses, and that is what ERR_TUNNEL_CONNECTION_FAILED reports.
With a residential proxy the refusal almost never comes from the network. A residential gateway does more than forward traffic: it reads your username to decide which home connection to send you through. Country, city, sticky session, all of it is written into the username. If the gateway can’t make sense of it, or can’t find a connection that fits, it refuses the tunnel.
- Your script
- 403Gateway
- Home exit
- Website
“usa” is not a two-letter country code. The gateway can’t read the username and stops before choosing an exit.
Here is the frustrating part: Chromium shows you none of this. Whether the gateway answered 403 or 465, Puppeteer reports the same ERR_TUNNEL_CONNECTION_FAILED. The real reason is in the status line, and to see it you have to take the browser out of the picture. That’s the first fix step below.
The username has a value the gateway can’t read
Every provider writes targeting into the username, each in its own way. Ours checks every value and refuses the whole login when one doesn’t fit: a three-letter country code, a city with a space, a lifetime over 24 hours, or a session label sent to the rotating port. It answers 403; it doesn’t guess what you meant.
A whole key it doesn’t know is a different story: it is ignored. john-cc-DE, the way some other gateways write a country, loaded the page in our test with an exit in Russia. No error at all, so look at where your exits are when the data looks odd.
The filters match no connection right now
Every filter narrows the pool, and a connection has to satisfy all of them at once. Country alone almost always works. Country, city and network together can match nobody at a given moment. The gateway then answers 465 instead of quietly giving you a different location, so you never get data from the wrong city without knowing it.
City names go in lower case, without spaces or accents. New York with a space is refused outright (that’s the 403 above), and São Paulo has to arrive as saopaulo.
Other errors you might see
Not every proxy mistake ends in a tunnel error. We changed one thing per run, same gateway and same target, and noted what page.goto() reported, so you can match the message you see to its cause.
- A value the gateway can’t readERR_TUNNEL_CONNECTION_FAILEDGateway sent 403. Two-letter country, city written together, sessions on the sticky port.
- Filters nobody matchesERR_TUNNEL_CONNECTION_FAILEDGateway sent 465. Drop the narrowest filter.
- Credentials in the proxy flagERR_NO_SUPPORTED_PROXIESFlag gets host and port only.
- No page.authenticate()ERR_INVALID_AUTH_CREDENTIALSAuthenticate each page before goto().
- Wrong passwordPage loads with 407Copy both values again.
- https:// as proxy schemeERR_PROXY_CONNECTION_FAILEDUse http://, also for HTTPS sites.
- socks5:// with a passwordERR_SOCKS_CONNECTION_FAILEDChromium can’t; use the HTTP port.
- Wrong portERR_PROXY_CONNECTION_FAILEDCopy host and port from the dashboard.
- http:// to the SOCKS5 portERR_RESPONSE_HEADERS_TRUNCATEDUse the HTTP port, 9000 or 10000.
- Another gateway’s key (-cc-DE)Page loads, wrong countryUnknown keys are ignored. Write -country-de.
- No scheme at allWorksChromium assumes http://.
Puppeteer 25.12.0, Chrome 154, via.proxies.vision, October 14, 2026. The blue cards are the two causes above.
1.Test the same login with curl
curl prints the proxy’s status line, which Chromium hides.
bash curl -sv -x http://via.proxies.vision:9000 \ --proxy-user "USERNAME-country-de:PASSWORD" \ https://ipinfo.io/json 2>&1 | grep "< HTTP"200 → proxy is fine403 / 465 → username407 → credentials2.Strip the username to the bare login
Remove every targeting parameter and try again. If that works, the parameters are the problem. Copied
-cc-USor-s-abcfrom another provider? Here it’s-country-usand-session-abc, and a key the gateway doesn’t know is ignored rather than refused, so check the exit’s country too.3.Add filters back one at a time
An exit has to match every filter at once. Country alone almost always works; country, city and network together may match nothing right now. City names go in lower case without spaces or accents:
newyork,saopaulo.4.Check the Puppeteer side
Credentials only through
page.authenticate(), called on every page beforegoto(). Schemehttp://. Host and port exactly as in your dashboard (sessions need the sticky port), and bandwidth left on your plan.
A setup that works
A new IP per identity in a fixed country, and a sticky session when several pages need the same IP. Ran unchanged on October 14, 2026.
const puppeteer = require("puppeteer");
const USER = process.env.PROXY_USER; // from your dashboard
const PASS = process.env.PROXY_PASS;
// Chromium remembers a proxy login per browser context, so one identity
// (one exit IP) per context. The sticky port pins the exit to the label.
async function newIdentity(browser, targeting = "", session = Math.random().toString(36).slice(2, 10)) {
const context = await browser.createBrowserContext({ proxyServer: "http://via.proxies.vision:10000" });
const page = await context.newPage();
// Authenticate every page before its first navigation; no credentials in the proxy URL.
await page.authenticate({ username: `${USER}${targeting}-session-${session}-lifetime-10`, password: PASS });
return page;
}
(async () => {
const browser = await puppeteer.launch();
// Germany, a new IP for every identity
const page = await newIdentity(browser, "-country-de");
await page.goto("https://ipinfo.io/json");
console.log(await page.evaluate(() => document.body.innerText));
// The same IP for 10 minutes: the same session label, even in a new context
const login = await newIdentity(browser, "-country-de", "checkout1");
await login.goto("https://example.com/login");
await browser.close();
})();Building the targeting part by hand is where most of these errors start. The proxy generator in the dashboard writes valid usernames for you.
Still stuck? Check your setup
Paste what you pass to Puppeteer and the checker points at the mistake, using the rules from our test runs. It runs in your browser; nothing is sent.
Check your Puppeteer setup
runs in your browser · nothing is sent
Username and password are inside --proxy-server→ ERR_NO_SUPPORTED_PROXIES
Chromium refuses credentials in the flag. Keep only scheme, host and port there and call page.authenticate({ username, password }) before page.goto().
Username is valid (1 filter)
If you still see ERR_TUNNEL_CONNECTION_FAILED, test the same login with curl (below) and check that your plan has bandwidth left.
Can I put the proxy username and password into ‑‑proxy-server?
Does Puppeteer support SOCKS5 proxies with a password?
What is the difference between ERR_TUNNEL_CONNECTION_FAILED and ERR_INVALID_AUTH_CREDENTIALS?
Why does my proxy work in curl but not in Puppeteer?
Keep reading
Tunnel connection failed: 407 in Python requests (and 403, 465)
The status is the proxy's answer, not the website's. Five causes of 407 we reproduced, and what 403 and 465 mean instead.
7 min read · #python#errors
Java Proxy Authentication: 407 With the Right Password (disabledSchemes)
Why Java's HttpClient and HttpURLConnection get 407 from a proxy even with the correct login, and the one property that fixes it.
5 min read · #errors#authentication
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
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.