# Proxy works for HTTP but not HTTPS: the CONNECT tunnel

> A proxy that works for HTTP but not HTTPS is refusing CONNECT, the tunnel every HTTPS page needs. A check that fetches a plain-HTTP page cannot see it.

Source: https://mosaicbrowser.com/blog/proxy-http-works-https-fails

## A proxy that answers HTTP and refuses CONNECT

A proxy that works for HTTP but not HTTPS is refusing CONNECT, the tunnel every HTTPS page needs. A check that fetches a plain-HTTP page cannot see it.

![CONNECT: Answers HTTP, refuses the tunnel](https://mosaicbrowser.com/covers/proxy-http-works-https-fails.webp)

A proxy that works for HTTP but not HTTPS is accepting one request and refusing another. Ask it for `http://example.com/` and the client sends the whole URL, which the proxy fetches and hands back. Ask for `https://example.com/` and the client sends `CONNECT example.com:443` instead, wants a tunnel, and only then starts TLS with the site. Those are two code paths in the proxy and often two permissions in the seller's product, and since nearly every page is HTTPS, a gateway that refuses the second one leaves a profile with almost nothing to load.

Most proxy checks only send the first request. Ours sends both, for that reason.

## Why a proxy works for HTTP but not HTTPS

RFC 9110 defines both. A plain request through a proxy uses the absolute form, so the request line names the full address. Ours looks like this:

```
GET http://ip-api.com/json HTTP/1.1
Host: ip-api.com

```

The proxy opens its own connection to ip-api.com, reads the JSON and relays it. It sees the page and could rewrite it if it wanted to.

For HTTPS that would defeat the point of the padlock, so the client asks for a pipe:

```
CONNECT www.cloudflare.com:443 HTTP/1.1
Host: www.cloudflare.com:443

```

A `200` back means the pipe is open. After that the proxy copies bytes both ways without reading them: the handshake, Cloudflare's certificate and the page all pass through encrypted. RFC 9110 also tells proxies to limit CONNECT to a known set of ports, which is why so many allow 443 and refuse everything else.

So the failure has a few common shapes. A seller's HTTP-only plan or port answers GET and refuses CONNECT. A port meant for another product answers CONNECT with `403` or `405`. And an address with the wrong port can still return a page of its own to a plain GET, which looks like success.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Provider minting](https://mosaicbrowser.com/features/proxy-providers)

## Why the usual proxy check passes

A typical checker sends a GET through the proxy to an IP lookup page, and many lookup pages are plain HTTP. The proxy fetches it, the page prints an address in the right country, and the checker shows green. CONNECT was never tried.

Chrome tries it on the first page it opens. A refused CONNECT becomes [ERR_TUNNEL_CONNECTION_FAILED](https://mosaicbrowser.com/blog/err-tunnel-connection-failed), Chrome throws away the proxy's own reply along with whatever reason it gave, and the person at the keyboard gets a grey error page from a proxy whose dashboard says it is fine.

## How to test both paths with curl

Send the same proxy one `http://` URL and one `https://` URL, with `-v` so curl prints what it sends and receives:

```
curl -v -x http://user:pass@host:port http://example.com/
curl -v -x http://user:pass@host:port https://example.com/

```

In the second run, find the reply under `CONNECT example.com:443`. `HTTP/1.1 200` is an open tunnel, and anything after it is between you and the site. A `403`, `405`, `407` or `502` at that line came from the proxy, and no TLS was sent at all.

The opposite mistake looks the same from the browser. The scheme on a proxy address describes the hop to the proxy, so `HTTPS_PROXY=http://host:port` is normally right for an ordinary HTTP proxy, and `https://host:port` makes curl start a TLS handshake with a proxy that never speaks TLS. A tool with a separate HTTPS proxy setting left blank does something else again: it sends HTTPS traffic straight out, so the proxy seems ignored rather than broken.

## How Mosaic checks a proxy, in two separate requests

Our check runs through the same local relay a profile launches with, with the same credentials and the same session token:

1. A plain HTTP GET to `ip-api.com`, which returns the exit's address, country, ISP and timezone.
2. A CONNECT to `www.cloudflare.com:443`, a TLS handshake through the tunnel, and a GET for `/cdn-cgi/trace`. Cloudflare answers in a few lines of plain text, among them `ip=` and `loc=`: the address and country it saw.

We write the second request by hand on a raw socket, because a library would pick its own route and we want CONNECT, then TLS, the way Chrome does it. The first version of that probe reported every proxy as a failed tunnel. One set of socket handlers did both steps, and its `close` listener fired when TLS took the socket over, which raced the real answer and won. Now the CONNECT happens on the raw socket, the socket goes to TLS, and the probe stops listening to it.

A refused tunnel gets its own sentence, with the gateway's status in the brackets: "The proxy answered plain HTTP but refused CONNECT (403). Profiles behind it will not load HTTPS sites." The row in the proxy list reads No HTTPS. The check runs up to three attempts when a residential pool hands out a dead exit, but a refused CONNECT is on its short list of answers that are final, so it is not retried. Start then refuses to open a profile behind that proxy: the message says it "answered but refused to tunnel, so this profile was not started", and names the wrong port or a plan without HTTPS as the usual cause.

![Mosaic's proxy check result showing the exit, its location and whether HTTPS tunnels](https://mosaicbrowser.com/shots/proxy-check.png)
*The check reports HTTPS on its own line*

Two requests also mean two readings of the exit. On one endpoint we tested, three requests in a row came back from Peru, Brazil and Bahrain, so different addresses from the same family mark the proxy as rotating. A residential exit once showed `172.115.91.44` to ip-api.com and an IPv6 address to Cloudflare on three checks in a row, and the first version called that rotation too. Now an IPv4 reading next to an IPv6 one counts as no evidence either way ([why one exit can show two addresses](https://mosaicbrowser.com/blog/two-ip-addresses-one-exit)). The country, the reputation and the other things to read before trusting a proxy with an account are in [how to check a proxy before you use it](https://mosaicbrowser.com/guides/how-to-check-a-proxy-before-you-use-it).

## Questions

### Why does my proxy work for HTTP but not HTTPS?

An http:// page is fetched by the proxy on your behalf. An https:// page needs CONNECT, a request for a raw tunnel to port 443, and plenty of gateways answer the first and refuse the second on some ports or plans.

### How do I test whether a proxy supports HTTPS?

Run curl -v -x http://user:pass@host:port https://example.com and find the reply printed under the CONNECT line. HTTP/1.1 200 is an open tunnel. A 403, 405 or 502 there came from the proxy, before a single byte of TLS left your machine.

### Should the proxy address start with https:// for HTTPS sites?

Almost never. That scheme is about the hop between you and the proxy. Write https:// for a proxy that only speaks plain HTTP and curl opens a TLS handshake with it, which fails before any site is involved.

### What does Mosaic do with a proxy that refuses CONNECT?

The proxy's row in the list reads No HTTPS, and Start refuses to open a profile behind it. The refusal says the proxy answered but refused to tunnel, and points at the two usual causes: the wrong port, or a plan without HTTPS.

## Related

[What ERR_TUNNEL_CONNECTION_FAILED is telling you](https://mosaicbrowser.com/blog/err-tunnel-connection-failed): Chrome's tunnel connection failed error means the proxy refused the CONNECT tunnel. In our runs the usual cause was a sticky session the gateway had retired.

[How to check a proxy before you use it](https://mosaicbrowser.com/guides/how-to-check-a-proxy-before-you-use-it): Check that a proxy answers and tunnels, where its exit really is, whether the address is residential or blocklisted, and that the browser stays behind it.

[IPv4 from one probe, IPv6 from the other: one exit, not two](https://mosaicbrowser.com/blog/two-ip-addresses-one-exit): A proxy that shows an IPv4 address on one checker and an IPv6 address on another is usually one dual-stack exit. Rotation only shows within one family.

[The port number told us whether a proxy was SOCKS5](https://mosaicbrowser.com/blog/port-number-names-protocol): Nothing in host:port:user:pass says HTTP or SOCKS5, and curl assumes HTTP. At CatProxies we asked the port: 11000 answered SOCKS5, 10000 HTTP.

[A CONNECT to a raw IP means your machine resolved the name](https://mosaicbrowser.com/blog/a-numeric-connect-means-dns-leaked): To check if DNS goes through your proxy, read what the proxy is asked for. CONNECT example.com:443 means it resolved the name; a raw IP means you did.

[DNS prefetching, the lookup that skips your proxy](https://mosaicbrowser.com/blog/dns-prefetch-and-proxies): Chrome's DNS prefetcher resolves names through your own resolver even with a proxy set. Mapping every name to NOTFOUND makes the leak fail closed.

[socks5 and socks5h: where Chrome resolves the name](https://mosaicbrowser.com/blog/socks5-and-socks5h-in-chrome): In curl, socks5h sends the hostname to the proxy and socks5 resolves it locally. Chrome's socks5 already sends the name; the leak is elsewhere.

[Our proxy relay told the gateway it was a script](https://mosaicbrowser.com/blog/the-relay-that-looked-like-a-script): Chrome sends Proxy-Connection: keep-alive, lower case, to every proxy. Our relay sent Keep-Alive, no User-Agent and Connection: close, until we copied Chrome.

## Mosaic, on your own computer

The browser this blog is about: its own Chromium, fingerprints from real machines and a proxy exit per profile. Free holds 3 profiles. Pro is $49 a month for any number.

Mosaic is a desktop app

Email yourself the link and open it on your computer.
