# Is Chrome bypassing the proxy for HTTPS? Check the window

> We counted 1, 0 and 0 CONNECTs in three launches and thought Chrome was bypassing the proxy for HTTPS. Our counter was reset after Chrome had started.

Source: https://mosaicbrowser.com/blog/chrome-preconnects-before-you-measure

## Chrome opens connections before your test starts

We counted 1, 0 and 0 CONNECTs in three launches and thought Chrome was bypassing the proxy for HTTPS. Our counter was reset after Chrome had started.

![After launch versus Before launch, each name in its own tile: Counted after the start, and before it](https://mosaicbrowser.com/covers/chrome-preconnects-before-you-measure.webp)

When Chrome seems to be bypassing the proxy for HTTPS, check when your counting started. We spent four hours on what looked like exactly that bypass: three launches of one profile through a traffic inspector showed 1, 0 and 0 CONNECT requests for the site under test. There was no bypass. Chrome had opened the tunnel during startup, before our counter was reset, and the page rode a connection that already existed.

## The report, and the numbers that agreed with it

The question was about traffic inspection, the setting that sends every launch through Proxyman or Charles so you can watch it. A relaunched profile seemed to send its HTTPS somewhere other than the inspector. So we measured it, counting CONNECT requests at the proxy for each launch:

```
launch 1 · through the inspector: 1 connect (httpbin.org)
launch 2 · through the inspector: 0 connect (nothing)
launch 3 · through the inspector: 0 connect (nothing)

```

An HTTPS page through an HTTP proxy starts with a CONNECT naming the host. Zero CONNECTs for a page that loaded meant the page had come from somewhere else. That was the reasoning, and it was sound as far as it went.

## Every check made the bypass look more real

We tried to kill the other explanations one at a time, and each one died in the bypass's favour.

The page body carried a token unique to that launch, so it was a fresh fetch and not the cache. The browser's process id changed between launches, so it was a real restart. `--proxy-server` was on the command line both times, character for character. Wiping the profile's `Network Persistent State`, its `TransportSecurity` file and the whole `Cache` folder changed nothing. We counted raw TCP accepts at the proxy instead of parsed requests, so a malformed CONNECT could not slip past the count.

By then we had worked out four mechanisms in detail: QUIC through a cached `alt-svc` entry, an extension calling `chrome.proxy`, Chromium's bad-proxy fallback, and a persisted preference. We came close to patching one of them. (QUIC is the usual suspect, and it is not this: with a proxy set Chrome stays on TCP, as [QUIC through a proxy](https://mosaicbrowser.com/blog/chrome-quic-through-a-proxy) measures.)

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)

## The counter was reset after the browser started

Chrome does work before the first line of any test runs. A restored tab preconnects to its own origin during startup, so the tunnel to the host we were testing opened a few seconds before our measurement window began. The navigation then reused that tunnel, and inside the window nothing new appeared.

We moved the reset to before the browser process existed and counted the whole launch:

```
launch 1 · 9 tcp, 6 connect · ip-api.com clients2.google.com … (list capped at 8)
launch 2 · 9 tcp, 8 connect · ip-api.com httpbin.org accounts.google.com …
launch 3 · 9 tcp, 8 connect · ip-api.com httpbin.org accounts.google.com …

```

The host was there on every launch, and so was everything else Chrome talks to at startup. There had been no routing bug at all.

The rule we already kept was to bracket a run with a control at both ends: if the control does not agree with itself, the run measured nothing. This added a second half to it. A measurement window that opens after the subject has started is not a control. It is a different experiment, one that asks "what did the browser do after second three", and it answers that question correctly.

## Two smaller traps on the way

The token in the page was weaker evidence than it looked. A Chromium error page quotes the URL it failed to load, so "the page contains this launch's token" proves the browser tried to navigate there. It does not prove anything was fetched. Read a value the origin computed, such as the exit address an echo service reports, instead.

The proxy we counted with was home-made, and at first it could fail without saying so. The first version parsed CONNECT by hand with no error handling, so its zero meant nothing. Counting raw TCP accepts, logging every event with a timestamp, and installing an `uncaughtException` handler are what turned it into something a zero could be believed from.

## If Chrome really is bypassing your proxy for HTTPS

Once the window starts before the browser, a real bypass still has a short list of causes. A PAC file whose answer ends in `DIRECT` lets Chrome go straight to the site when the proxy fails. An extension with the `chrome.proxy` permission can change the route for some hosts. WebRTC can send UDP outside an HTTP proxy unless it is told not to. Each of those shows up in a whole-launch count as traffic that never reached the proxy.

In Mosaic the route is fixed at launch: the proxy goes on the command line, and with one attached the engine is started, by default, so WebRTC cannot send UDP around it. A launch does not open at all until the proxy has answered and tunnelled. One consequence of the command line is that a setting changed while a browser is open reaches the next launch, not the open window, which is the other half of the report that started this. A saved setting can miss a launch for a duller reason too: [a mapper that dropped one field](https://mosaicbrowser.com/blog/setting-saved-launch-ignored) did it to three launches in a row. When a tunnel fails outright you get an error page instead of a quiet zero, and [ERR_TUNNEL_CONNECTION_FAILED](https://mosaicbrowser.com/blog/err-tunnel-connection-failed) covers what each cause looks like.

## Questions

### Why does Chrome look like it is bypassing the proxy for HTTPS?

Often it is not. Chrome preconnects to the origins of restored tabs while it starts, so the CONNECT tunnel for a site can open before your test begins and the page then reuses it. Count proxy traffic from before the browser starts and the host usually shows up.

### How does HTTPS go through an HTTP proxy?

The browser sends the proxy a CONNECT request naming the host and port. The proxy opens a TCP connection there and passes the encrypted bytes through without reading them, so a proxy log shows one CONNECT per tunnel, not one line per request.

### Can Chrome really send traffic around a proxy?

Some things do take another route: a PAC file that ends in DIRECT, an extension using the chrome.proxy API, or WebRTC sending UDP outside the proxy unless a policy stops it. Rule out the measurement first, because a counter started late shows the same zero.

### Does a Chromium error page prove the request was made?

No. A Chromium error page quotes the URL it failed to load, so finding your test token in the page proves the browser tried to navigate there, not that anything was fetched. Read a value the server computed instead.

## Related

[What happens to QUIC when Chrome has a proxy](https://mosaicbrowser.com/blog/chrome-quic-through-a-proxy): With an HTTP, HTTPS or SOCKS5 proxy set, Chrome reaches sites over TCP tunnels, so a page that gets HTTP/3 direct gets HTTP/2 through the proxy.

[The setting was saved, and three launches ignored it](https://mosaicbrowser.com/blog/setting-saved-launch-ignored): Our rotation setting was stored, reported back by the API, and ignored by three launches. A mapper had copied every field of the proxy except the new one.

[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.

[Provider accounts: one gateway line, one exit per profile](https://mosaicbrowser.com/features/proxy-providers): Paste your proxy seller's gateway line once and Mosaic mints a separate session for every profile, so each one browses from its own exit address.

[Local first: your profiles stay on your computer](https://mosaicbrowser.com/features/local-first): Mosaic has no cloud copy of your profiles. Profiles, cookies and proxies stay on your computer, encrypted on disk, with app lock, daily backups and a trash.

[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.

[A proxy that answers HTTP and refuses CONNECT](https://mosaicbrowser.com/blog/proxy-http-works-https-fails): 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.

## 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.
