Tunnel connection failed
Whoever the status points at, so read it first: 407 is your login, 403 is what you asked to reach, 502 is the path to the site.
- Usually from
- The proxy’s answer to CONNECT
- Try first
- Send the same CONNECT with curl -v and read the status
What a failed tunnel means through a proxy
For an HTTPS site the browser sends CONNECT example.com:443 and waits for 200. Chromium turns every other answer, bar a 407 it can log in to, into net error -111, ERR_TUNNEL_CONNECTION_FAILED. It ignores the proxy’s response body on purpose, so a proxy cannot pass off its own page as the site’s.
That one code covers several different answers. A 407 nobody answered: when Chromium has no credentials to send, or the login prompt is cancelled, it ends with this error rather than show the proxy’s page. A 403 for a port or host the proxy will not tunnel to. A 502 or 503 when the proxy could not reach the site.
Other tools print the status. curl says CONNECT tunnel failed, response 407. Node’s undici says Proxy response (407) !== 200 when HTTP Tunneling. Python puts Tunnel connection failed: 407 Proxy Authentication Required inside a ProxyError.
Your credentials, the proxy, or the target?
Whoever the status points at, so read it first: 407 is your login, 403 is what you asked to reach, 502 is the path to the site.
Your credentials
The usual cause in browser automation: credentials written into
--proxy-server, where Chrome ignores them, or a login handler set up after the first navigation.The proxy
A 502 or 503 on the CONNECT means the gateway could not reach the target through the exit it picked. A retry often leaves from another exit.
The target site
Indirectly. A target that is down or refuses the exit produces a 502 on the CONNECT, which Chrome reports as this error.
How to tell
- With
curl -v, the line straight after> CONNECTis the proxy’s status:< HTTP/1.1 407,403or502. That is the line Chrome hid. - Load an
http://page through the same proxy. It needs no tunnel, so the proxy’s answer comes back as an ordinary response, reason and all. - It fails on every site: the login or the proxy. It fails on one site: that host, or the port in its URL.
- The same credentials work in curl while Puppeteer or Selenium fails: the browser is not sending them with the CONNECT.
Fixes, in the order to try them
- Costs nothing
Read the real status with curl
Send the same CONNECT with
curl -vthrough the same proxy. The status afterCONNECTis the answer Chrome discarded, and every other fix depends on it. - Costs nothing
On a 407, pass credentials the way the tool wants
Chrome ignores
USER:PASSwritten into--proxy-server. Use theusernameandpasswordfields in Playwright,page.authenticatein Puppeteer before the firstgoto, or an extension in Selenium. - Costs nothing
On a 403, check the port and the host
Many proxies only tunnel to standard HTTPS ports and answer anything else with 403. Check the port in the URL you are opening, and that the host is one the acceptable use policy allows.
- Costs nothing
On a 502 or 503, retry once and test the site
A retry on the rotating gateway leaves through a different exit. Open the site without the proxy as well; if it is down for everyone, nothing in your setup will help.
- Costs some time
Update the allowlist if you use IP auth
With IP allowlisting, a machine whose public IP has changed is a stranger to the proxy and gets a 407. Add the new address in the dashboard, or switch that machine to username and password.
See the real status and headers
Each sample prints the status the proxy sent back on CONNECT, which is the part Chromium leaves out.
curl
# the proxy's answer to CONNECT, which Chrome does not show
curl -sv -o /dev/null -x http://USER:[email protected]:8000 https://example.com/ 2>&1 \
| grep -E '^> CONNECT|^< HTTP|CONNECT tunnel failed'
# > CONNECT example.com:443 HTTP/1.1
# < HTTP/1.1 407 Proxy Authentication Required
# * CONNECT tunnel failed, response 407The exit code for a refused CONNECT differs between curl releases. The status line does not.
Python Requests
import requests
PROXY = "http://USER:[email protected]:8000"
try:
r = requests.get("https://example.com/", proxies={"http": PROXY, "https": PROXY}, timeout=30)
print("tunnel open, the site answered", r.status_code)
except requests.exceptions.ProxyError as err:
inner = err.args[0].reason.original_error
print(inner)
# Tunnel connection failed: 407 Proxy Authentication RequiredScrapy
import scrapy
from scrapy.core.downloader.handlers.http11 import TunnelError
class TunnelSpider(scrapy.Spider):
name = "tunnel"
start_urls = ["https://example.com/"]
async def start(self):
for url in self.start_urls:
yield scrapy.Request(url, meta={"proxy": "http://USER:[email protected]:8000"}, errback=self.on_error)
def on_error(self, failure):
if failure.check(TunnelError):
self.logger.error("%s", failure.value)
# Could not open CONNECT tunnel with proxy resi.proxymonkey.io:8000 [{'status': 407, 'reason': b'Proxy Authentication Required'}]A TunnelError is on Scrapy’s default retry list, so with RETRY_TIMES = 2 a refused CONNECT is tried twice more before the errback sees it.
Playwright
from playwright.sync_api import Error, sync_playwright
PROXY = {"server": "http://resi.proxymonkey.io:8000", "username": "USER", "password": "PASS"}
with sync_playwright() as p:
browser = p.chromium.launch(proxy=PROXY)
page = browser.new_page()
try:
page.goto("https://example.com/")
except Error as err:
print("https:", err.message.splitlines()[0])
response = page.goto("http://example.com/")
print("http, no tunnel:", response.status, response.status_text)
browser.close()The http:// request needs no tunnel, so whatever status the proxy sends comes back as a response you can read.
Node.js
import { fetch, ProxyAgent } from "undici";
const dispatcher = new ProxyAgent("http://USER:[email protected]:8000");
try {
const res = await fetch("https://example.com/", { dispatcher });
console.log("tunnel open, the site answered", res.status);
} catch (err) {
let e = err;
while (e && !/Proxy response/.test(e.message ?? "")) e = e.cause;
console.error(e ? e.message : err);
}
// Proxy response (403) !== 200 when HTTP TunnelingThe samples use the residential gateway, resi.proxymonkey.io:8000. For an ISP or datacenter IP, use USER:PASS@IP:PORT for the address you rented. Your dashboard lists the host and port for every order, and where it differs from this page, the dashboard is right.
Will a different proxy line fix it?
When switching helps
Only for scattered 502s on one target from rotating residential exits. A static ISP or datacenter IP has a steadier path to it, if the target accepts that kind of address.
When it will not
A 407 or 403 on the CONNECT follows you to every line. It is about the login or what you asked to reach, not the exit.
Is a failed request billed?
We bill for request bytes and response bytes, including headers and protocol overhead on the tunnelled connection. Connections that fail before transferring data are not billed. Retries that you initiate are billed and appear as separate entries in your usage log.
The terms bill request and response bytes and say connections that fail before transferring data are not billed. They do not say which side of that line a refused CONNECT falls on. Look for the rows in your usage log, and ask in Discord if you think they should not be there.
Residential is billed per GB of that traffic. ISP and datacenter addresses are charged per IP for their term, and where a plan includes a traffic allowance, traffic past it is billed per GB under the same rule. The usage log in your dashboard has one row per request with bytes in, bytes out and cost, so you can look up the failed request yourself.
Questions people ask about a failed tunnel
Why does Chrome not show the proxy’s error message?
On purpose. A proxy answering CONNECT could otherwise show you a page that pretends to be the site. Chromium discards anything but a 200, or a 407 it can answer, so you need curl or your tool’s exception text to see the real status.
Is ERR_TUNNEL_CONNECTION_FAILED always a password problem?
No, though in Puppeteer and Selenium it often is. It covers every refused CONNECT: a login the browser never sent, a blocked port or host, and a proxy that could not reach the site. The status in curl tells you which.
Why do I get it in Puppeteer but not in curl?
Chrome ignores a username and password written into --proxy-server, so it sends the CONNECT without them. Call page.authenticate before the first navigation, or use a tool that takes proxy credentials as separate fields.
Does it mean the site blocked me?
Not directly. A site that blocks you usually answers inside the tunnel, with a 403 or a challenge page you can read. This error means no tunnel opened at all, so the answer came from the proxy.
Errors that travel with this one
- 407
407 Proxy Authentication Required
The proxy did not accept your login. Nearly always the credentials, or the way your tool sends them.
Work it out → - PROXY_CONNECTION_FAILED
Proxy connection failed
Chrome could not reach the proxy it was told to use. A wrong address, a proxy that is down, or a proxy setting left behind.
Work it out → - 502
502 Bad Gateway
A go-between got a bad answer from the next hop. Could be the proxy, could be the site’s own load balancer.
Work it out →
Still stuck on a failed tunnel?
Paste your error in the Discord: the full message plus the command or the few lines that set up the proxy, with the password taken out. Someone there has seen it before.
Join the Discord4,200+monkeys in the Discord
Help from humans
Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.
A status bot that tells on us
Pool health, incidents and maintenance posted automatically. Including the bad days.
Deals and free traffic
Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.