Proxy Troubleshooting by Error Code and Symptom

Fix ERR_TUNNEL_CONNECTION_FAILED Behind a Proxy

Chrome, Playwright and Puppeteer show ERR_TUNNEL_CONNECTION_FAILED when a proxy refuses CONNECT. Read the hidden status with curl and fix the cause.

By Proxies.sx Team. Reviewed .

Chromium returns ERR_TUNNEL_CONNECTION_FAILED when it reached the proxy but the proxy answered CONNECT with something other than 200 or 407. Chromium discards that response on purpose, so the browser cannot tell you why. Repeat the same request with curl -v to see the status, then read the gateway code.

In this guide

What Chromium is telling you

To load an HTTPS page through an HTTP proxy, the browser sends CONNECT host:443 to the proxy and waits for a 2xx answer. Any other answer means the tunnel was not formed (RFC 9110, section 9.3.6). Chromium's handling of that answer is short, and it explains the error you see.

In http_proxy_client_socket.cc, a 200 opens the tunnel and a 407 starts proxy authentication. Every other status returns ERR_TUNNEL_CONNECTION_FAILED, and the response is ignored so that a proxy cannot impersonate the target site. The Chromium comment admits the cost: useful error bodies from proxies are lost. Our gateway puts its reason in exactly that body.

ErrorWhere it failedCommon causes with our gateway
ERR_PROXY_CONNECTION_FAILEDThe browser never connected to the proxy. Chromium's error list says this code excludes CONNECT failures.Wrong host or port, a firewall blocking outbound port 7000, a typo in gw.proxies.sx
ERR_TUNNEL_CONNECTION_FAILEDThe proxy answered CONNECT with a refusal.No stock for the country (502), an account limit (429), a blocked target (403), an upstream refusal (590 to 599)
ERR_INVALID_AUTH_CREDENTIALS or a credentials promptThe proxy asked for authentication (407) and the browser could not answer it with working credentials.Credentials missing from the automation library, or rejected by the gateway

Repeat the request with curl to see the status

Run the same username, password and target from a terminal. With -v, curl prints the proxy's status line, and the write-out variable %{http_connect} records the code the proxy returned to CONNECT (curl manual, --write-out). Pass the credentials with -U so a special character in the password does not need URL encoding.

We ran this pattern against a local test proxy that refuses CONNECT with a 502. curl 8.7.1 printed CONNECT tunnel failed, response 502, exited with code 56, and wrote connect=502 code=000. The 000 means no response ever came from the target.

curl -sS -v --max-time 30 -o /dev/null \
  -x http://gw.proxies.sx:7000 \
  -U 'psx_ab12cd-peer-us:YOUR_PROXY_PASSWORD' \
  -w 'connect=%{http_connect} code=%{http_code}\n' \
  https://example.com/

# A refusal looks like this:
# * CONNECT tunnel failed, response 502
# curl: (56) CONNECT tunnel failed, response 502
# connect=502 code=000

Read the gateway code and map it to a fix

The status alone narrows the cause. To read the code, send the same request to a plain http:// URL with -i. The gateway applies the same checks and prints the body.

curl -sS -i --max-time 30 -x http://gw.proxies.sx:7000 \
  -U 'psx_ab12cd-peer-us:YOUR_PROXY_PASSWORD' http://example.com/
CONNECT statusLikely codeNext step
407E_AUTH_REQUIRED, E_AUTH_INVALID, E_BALANCE_EXHAUSTEDFix how credentials reach the browser (below), or the credentials themselves (authentication guide)
502E_NO_STOCK_COUNTRY, E_STOCK_DEGRADEDCheck stock for the country and remove narrow filters, as the 502 guide describes
429E_RATE_LIMITED_CONN, E_SESSION_LIMITLower concurrency or reuse session ids
403E_SSRF_BLOCKEDThe target address is private or internal
590 to 599E_UPSTREAM_REFUSED or noneRetry on a new connection. A persistent 593 points at the target hostname

The error code reference lists every code with its cause.

Put Playwright credentials in the proxy option

Playwright takes the proxy server, username and password as separate fields, either when launching the browser or per context (Playwright network docs). Keep the credentials out of the server value. When the tunnel is refused, page.goto throws an error whose message contains net::ERR_TUNNEL_CONNECTION_FAILED and the URL. Log it together with the username you used.

Each browser context can carry a different username, so contexts can use different countries or session ids. Firefox and WebKit report a refused tunnel with their own messages, and the curl check above works the same way for all of them.

import { chromium } from 'playwright';

const browser = await chromium.launch({
  proxy: {
    server: 'http://gw.proxies.sx:7000',
    username: process.env.PROXY_USERNAME, // e.g. psx_ab12cd-peer-us-rot-sticky-sid-run1
    password: process.env.PROXY_PASSWORD,
  },
});

try {
  const page = await browser.newPage();
  const response = await page.goto('https://example.com/', { timeout: 30_000 });
  console.log('status', response?.status());
} catch (error) {
  // net::ERR_TUNNEL_CONNECTION_FAILED means the proxy refused CONNECT.
  console.error(error.message);
} finally {
  await browser.close();
}

Answer the 407 in Puppeteer with page.authenticate

Puppeteer passes the proxy to Chromium with the --proxy-server switch and answers the proxy's 407 through page.authenticate. Call it before the first navigation. Puppeteer's API reference notes that it turns on request interception behind the scenes, which can affect performance.

If you skip page.authenticate, the gateway's 407 reaches a browser with no way to respond and the page fails. A 407 is the one refusal Chromium does not hide, so this case shows up as an authentication error rather than a tunnel failure.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({
  args: ['--proxy-server=http://gw.proxies.sx:7000'],
});

try {
  const page = await browser.newPage();
  await page.authenticate({
    username: process.env.PROXY_USERNAME,
    password: process.env.PROXY_PASSWORD,
  });
  const response = await page.goto('https://example.com/', { timeout: 30_000 });
  console.log('status', response?.status());
} finally {
  await browser.close();
}

When the error comes and goes

An error that appears on some page loads and not others often means the route changes between attempts. Three gateway conditions behave this way: E_STOCK_DEGRADED when a selected endpoint drops during setup, 590 to 599 refusals from a residential peer, and E_NO_STOCK_COUNTRY for a country with only a handful of endpoints online.

Retry a refused navigation a small, fixed number of times with a pause, and record the time of each failure. For countries with little stock, check the counts in the stock endpoint before a long run, because a country with two or three listed endpoints can drop to zero. If the failures follow one session id, start a new one. The sticky session guide explains how sessions move between endpoints.

Common questions

Why does the proxy work in curl but fail in Chrome?

Compare the exact username. Automation code often builds a different session id or country than the one you tested. Then check that credentials reach the browser through the library's proxy authentication, because a browser started by automation cannot answer a 407 without them.

Is ERR_TUNNEL_CONNECTION_FAILED a problem with the website?

Often it is not. Gateway refusals such as E_NO_STOCK_COUNTRY happen before the website is contacted. Chromium also files some failures the proxy hit while connecting onward, such as a failed DNS lookup for the target, under the same code. Read the status with curl to know which.

Sources and test scope

Standards and vendor documentation checked on 19 October 2026. Gateway responses on these pages were observed with test requests or read from the gateway build running that day, and the client examples were run against local test servers. They do not establish current stock in a country or how a website will respond.

Connect your application to Proxies.sx

Use the username builder for gateway routing fields and the format converter for existing connection lists. Review Pool Gateway documentation, then check current mobile stock before buying traffic.

For agent-driven workflows, the agent service index lists the supported APIs. A configured HTTP client still needs to validate its task's response.

Proxy error and troubleshooting guides

Start from the symptom table, or open the guide for the error you saw.