Guide

net::ERR_PROXY_CONNECTION_FAILED in Puppeteer, Playwright, and Selenium: Causes and Fixes

Headless Chromium reports proxy failures as net:: errors on page.goto. What each means, why 127.0.0.1 fails inside Docker, and how each framework passes credentials.

HProxy Team··6 min read
HProxy.Guide

Free proxies won't hold up here.

Shared datacenter IPs get flagged and dropped fast. When it has to hold, gaming, streaming, accounts, you need mobile and residential IPs that read as a real device, from $0.44/GB, pay as you go.

See plans & pricing

The error arrives as a rejected promise from page.goto: Error: net::ERR_PROXY_CONNECTION_FAILED at https://example.com/. The net:: prefix marks it as one of Chromium's network errors, the same family a desktop Chrome shows on its grey error page, and this one is the same error a person sees when Chrome cannot reach a configured proxy. The difference in automation is that the proxy was configured by your code, the browser runs somewhere your code may not, and the frameworks each have their own rules about how a proxy and its credentials are passed. Those three facts account for almost every instance.

This guide covers the errors Chromium reports through Puppeteer, Playwright, and Selenium when a proxy is involved, why the same proxy that works in curl fails in the browser, and how each framework wants the proxy and the credentials supplied.

The errors and what each one means

Error on page.gotoWhat Chromium is sayingUsual cause in automation
net::ERR_PROXY_CONNECTION_FAILEDCould not connect to the proxy at allWrong address or port; proxy down; unreachable from the container; unsupported scheme
net::ERR_TUNNEL_CONNECTION_FAILEDReached the proxy; it refused the HTTPS tunnelCredentials not supplied; HTTP-only proxy; port or destination refused
net::ERR_NO_SUPPORTED_PROXIESNone of the proxies in the setting is usableA scheme Chromium does not support, or credentials inside the URL
net::ERR_SOCKS_CONNECTION_FAILEDThe SOCKS handshake failedAuthentication required, wrong protocol on that port, or a SOCKS reply
net::ERR_INVALID_AUTH_CREDENTIALSThe proxy rejected the credentialsWrong username or password in authenticate or the proxy option
Timeout ... exceeded (framework timeout)The proxy accepted and then stalledOverloaded or dying proxy, or a target dropping the exit

The first one is the subject of this page, but the others share its causes, and a job that produces one usually produces several.

Cause 1: the proxy is unreachable from where the browser runs

The proxy works in curl on your machine. The browser runs in a Docker container, a CI runner, a cloud function, or a remote grid. 127.0.0.1 in the container is the container. A proxy on the host, or a local forwarder on your laptop, is unreachable at that address from inside, and Chromium reports exactly this error.

Use the host's address as seen from the container (Docker Desktop provides host.docker.internal; on Linux, the host's bridge address or --network host), or run the forwarder inside the same container or network. The same applies to a proxy on a private network the runner cannot reach, and to a proxy whose port the runner's network blocks. Test from inside the environment, not from your desk: docker exec -it <container> curl -v -x http://proxy:port https://example.com/ tells you what the browser will see.

Cause 2: the address, the port, or the proxy itself

The boring causes are still the common ones. A typo in the host, a port for the wrong protocol, a rotating gateway's hostname that resolves only on some networks, or a proxy that has died. Free proxies die within hours and are shared by thousands of processes, and a scraper on a public list spends most of its time in this error. Run the address through our proxy checker; a dead result ends the investigation, and a live result narrows it to the scheme and reachability causes.

Cause 3: the scheme and the credentials

Chromium's --proxy-server accepts http://, https://, socks4://, and socks5:// addresses, and it does not accept credentials inside the URL. socks5h://, which curl and Python understand, is not a Chromium scheme. A URL with user:pass@ in it is ignored or rejected depending on version, and either way the proxy answers with an authentication challenge the browser cannot satisfy, which surfaces as ERR_TUNNEL_CONNECTION_FAILED, ERR_NO_SUPPORTED_PROXIES, or this error. Credentials go through the framework, not the URL:

Puppeteer. Launch with the proxy and authenticate the page before navigating:

const browser = await puppeteer.launch({
  args: ["--proxy-server=http://gateway.example:8080"],
});
const page = await browser.newPage();
await page.authenticate({ username: "user", password: "pass" });
await page.goto("https://example.com/");

Playwright. The proxy option carries credentials and works at launch or per context, which is how one browser can use different exits for different contexts:

const browser = await chromium.launch({
  proxy: { server: "http://gateway.example:8080", username: "user", password: "pass" },
});
const context = await browser.newContext({
  proxy: { server: "http://gateway.example:8080", username: "user-session-abc123", password: "pass" },
});

Selenium. The driver passes --proxy-server through the browser options, and there is no equivalent of authenticate: Chromium raises an authentication dialog that Selenium cannot answer. The working routes are IP authentication with the provider, so no credentials are needed; a small extension packaged with the profile that answers the proxy challenge; or a local forwarding proxy that adds the credentials and exposes an unauthenticated proxy on localhost. Our Selenium proxy guide shows the extension approach.

Cause 4: SOCKS5 with authentication

Chromium does not do SOCKS5 authentication. A socks5:// proxy that requires a username and password is unusable from Chromium directly, in every framework, and produces ERR_SOCKS_CONNECTION_FAILED or this error. Use the provider's HTTP port with credentials, which nearly every provider offers alongside SOCKS, or IP authentication on the SOCKS port, or a local forwarder that authenticates to the SOCKS proxy and exposes it plainly on localhost. SOCKS5 proxy errors covers what the handshake failures mean when the proxy is reached.

Cause 5: an HTTP-only proxy and an HTTPS target

Free lists are full of proxies that fetch plain http:// pages and refuse the CONNECT request every https:// page needs. In the browser that is ERR_TUNNEL_CONNECTION_FAILED on the first HTTPS navigation, and since almost every target is HTTPS, such a proxy is useless for automation. The checker reports HTTPS support separately from HTTP for exactly this reason, and our guide to ERR_TUNNEL_CONNECTION_FAILED shows how to read the proxy's CONNECT answer with curl.

Cause 6: the environment variable that does nothing

Scripts often set HTTP_PROXY in the environment and expect the browser to follow it, because curl, pip, and Node's fetch helpers do. The bundled Chromium that Puppeteer and Playwright download does not reliably pick up shell proxy variables, and its behaviour differs by platform and flags. Pass the proxy explicitly, every time, and pass the bypass list explicitly too: --proxy-bypass-list="localhost;127.0.0.1;*.internal" in Puppeteer's args, or the bypass field of Playwright's proxy option, so that health checks and local services do not go through the exit.

Cause 7: the proxy accepted, then stalled

Not this error by name, but the same job: the framework's navigation timeout fires because the proxy accepted the connection and never delivered. Overloaded free proxies do this, and so does a target that drops the exit's address at the network level. Raise the timeout only after confirming the proxy answers the checker in milliseconds; a proxy that takes seconds there will time out under a browser's page load. A target that drops one exit and not another is the address problem covered in how websites detect proxies.

A launch that avoids most of it

  • Test the proxy from inside the environment the browser runs in, with curl, before the first launch.
  • Pass the proxy explicitly with a supported scheme and no credentials in the URL.
  • Supply credentials through page.authenticate, Playwright's proxy option, or IP authentication for Selenium.
  • Use the HTTP port for authenticated access; keep SOCKS for IP-authenticated setups.
  • Set the bypass list so local traffic stays local.
  • Prefer a per-context proxy in Playwright when different contexts need different exits, and keep one exit per context for the life of a session.

The frameworks' own guides cover the rest of the setup: Puppeteer, Playwright, and Selenium. For the exits themselves, a residential gateway with session control in the username gives each context a stable address the target treats as a household, on an HTTP port with credentials the frameworks can send, which removes causes 2 through 5 in one step.

Where curl and the browser differ

net::ERR_PROXY_CONNECTION_FAILED means the browser never reached the proxy. Check the address from inside the environment the browser runs in, especially inside Docker. Use a scheme Chromium supports, keep credentials out of the URL and pass them through the framework, avoid authenticated SOCKS5, and use a proxy that carries HTTPS. If curl from the same place succeeds and the browser still fails, the scheme or the credentials are the difference.

Frequently asked questions

What does net::ERR_PROXY_CONNECTION_FAILED mean in Puppeteer or Playwright?
The Chromium instance the framework launched could not open a connection to the proxy it was given. It is the same error a desktop Chrome shows for an unreachable proxy, surfaced through page.goto. The proxy address is wrong, the proxy is down, the scheme is unsupported, or the proxy is unreachable from where the browser runs, which inside a container is a different place from the host.
Why does the proxy work in curl but fail in headless Chromium with this error?
Usually because the browser runs somewhere else. Inside a Docker container, 127.0.0.1 is the container, so a proxy on the host is unreachable at that address; use the host's address or host networking. The other common difference is the scheme: curl accepts socks5h:// and credentials in the URL, while Chromium's --proxy-server takes http://, https://, socks4://, or socks5:// with no credentials, and rejects anything else.
How do I pass proxy credentials in Puppeteer, Playwright, and Selenium?
Puppeteer: launch with --proxy-server=http://host:port and call page.authenticate({username, password}) before navigating. Playwright: pass proxy: {server, username, password} to launch or to newContext, and Playwright handles the challenge. Selenium: Chromium has no way to receive credentials from the driver, so use IP authentication with the provider, or a small extension that answers the proxy challenge, or a local forwarding proxy that adds the credentials. Credentials in the --proxy-server URL are ignored or rejected by Chromium.
What is the difference between ERR_PROXY_CONNECTION_FAILED and ERR_TUNNEL_CONNECTION_FAILED here?
The first means the browser never reached the proxy. The second means it reached the proxy and the proxy refused to open the HTTPS tunnel: it wanted credentials the framework did not supply, it does not support CONNECT at all (HTTP-only free proxies), or it refuses that port or destination. The fix for the first is the address and reachability; for the second, credentials and a proxy that carries HTTPS.
Why does SOCKS5 with a username and password fail in headless Chromium?
Chromium does not support SOCKS5 authentication. A socks5:// proxy that requires a username and password cannot be used from Chromium at all, and one with credentials in the URL produces an error at launch or on the first navigation. Use the provider's HTTP port with credentials, use IP authentication on the SOCKS port, or run a local forwarder that authenticates to the SOCKS proxy and exposes it unauthenticated on localhost.
Does headless Chromium use the HTTP_PROXY environment variable?
Do not rely on it. Chromium's proxy resolution depends on the platform and the flags, and the bundled browsers that Puppeteer and Playwright download do not reliably pick up shell variables the way curl or pip do. Pass the proxy explicitly with --proxy-server or the proxy launch option, and pass --proxy-bypass-list or the bypass option for hosts that should go direct.

Proxies that don't die mid-job

Residential, ISP, datacenter and mobile, verified by the same engine that runs tens of millions of checks. They read as a real device and hold up under load. Pay as you go, and your balance never expires. $0.44/GB is the 2,000 GB+ rate; a single gigabyte is $0.50/GB, with no minimum order.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup