# Akamai HTTP/2 fingerprint explained, field by field

> The Akamai HTTP/2 fingerprint is four fields: the SETTINGS values, the window update, any priority frames and pseudo-header order, all read before content.

Source: https://mosaicbrowser.com/blog/akamai-http2-fingerprint-read-field-by-field

## The Akamai HTTP/2 fingerprint, read field by field

The Akamai HTTP/2 fingerprint is four fields: the SETTINGS values, the window update, any priority frames and pseudo-header order, all read before content.

![Four fields, read before the page exists, beside a screenshot: routing Mosaic launches through Proxyman, Charles or mitmproxy to read their traffic](https://mosaicbrowser.com/covers/akamai-http2-fingerprint-read-field-by-field.webp)

The Akamai HTTP/2 fingerprint is a short string made from four things a client sends when an HTTP/2 connection opens: its SETTINGS values in order, its WINDOW_UPDATE increment, its PRIORITY frames and the order of its pseudo-headers. The server computes it before it serves any content, so no script can touch it. We compared our engine's against Google Chrome's and found nothing to separate them.

## Where the string comes from

Akamai's threat research team presented the method at Black Hat Europe 2017, in a white paper titled "Passive Fingerprinting of HTTP/2 Clients". HTTP/2 leaves a client a lot of small decisions. Which settings to announce, and with what values. How much extra window to grant. Whether to build a priority tree. What order the pseudo-headers go in. A browser's network code makes each of those decisions once, when somebody writes it, and then repeats them on every connection it ever opens.

The paper's title says "passive", and that word carries the method. Nothing is probed or requested. The server just keeps the frames the client was sending anyway.

## The Akamai HTTP/2 fingerprint explained, field by field

Written out, the fingerprint is four parts joined by a pipe:

`SETTINGS|WINDOW_UPDATE|PRIORITY|PSEUDO_HEADER_ORDER`

To show the shape, here is one with made-up values: `1:4096;3:100;4:65535|0|0|m,p,s,a`. Each part reads like this.

### SETTINGS

The first frame a client sends after the connection preface. Each entry is a setting's numeric id and the value announced, in the order sent, separated by semicolons. The ids are defined by the HTTP/2 specification: 1 is the header table size, 2 is server push, 3 is the maximum concurrent streams, 4 is the initial window size, 5 is the maximum frame size and 6 is the maximum header list size, and later RFCs added 8, for the extended CONNECT protocol, and 9, for turning off the old priority scheme. Which ids appear, their order and their values all count. A client that leaves a setting out is saying something too.

### WINDOW_UPDATE

HTTP/2 starts every connection with a small flow-control window, and most clients immediately send a WINDOW_UPDATE on stream 0 to make it larger. The fingerprint records the increment, or 0 if none was sent. It is one number, and different stacks pick quite different ones.

### PRIORITY

Some clients send PRIORITY frames at the start to build a dependency tree of streams. Each is written as stream id, exclusive bit, the stream it depends on and its weight, with frames separated by commas, and 0 if there were none.

### Pseudo-header order

Every HTTP/2 request opens with four pseudo-headers, `:method`, `:authority`, `:scheme` and `:path`. The protocol does not fix their order, so each implementation has its own, written as the first letters: `m,a,s,p` or some other arrangement.

Mosaic is a desktop app

Email yourself the link and open it on your computer.

[How it works: Machines that exist](https://mosaicbrowser.com/features/real-fingerprints)

## Why it is hard to fake from outside the browser

None of the four fields comes from the page. They come from the HTTP/2 stack, and that cuts both ways. Chrome can't send the wrong ones, since they're simply what its network code does. Now take a script that puts a Chrome User-Agent on an ordinary HTTP library: out go the library's SETTINGS, the library's window increment, the library's pseudo-header order. The server is looking at a "Chrome" whose opening frames were written by somebody else, and it knows that before it has sent one byte of HTML. To fake it you would have to rewrite the frames themselves, every value in the right order, for the exact Chrome version the User-Agent names.

## What we measured on our own engine

Tesseract Chrome is stock Chromium source with our patch series on top. We went through the series file by file: most of the patches live in Blink, the rest in the browser process, V8 and our own fingerprint component, and not one of them opens a file under `net/`. On paper, then, our HTTP/2 fingerprint is Chrome's. We measured it anyway. "On paper" is how a suspect gets waved through.

It came up while we were chasing an IPHey score, where our engine got a worse verdict than branded Chrome on the same Mac. Ruling HTTP/2 in or out cost one probe. We wrote a small node `http2` server with a self-signed certificate, launched both browsers at it with `--ignore-certificate-errors`, and printed what arrived from each. Same machine, same minute, nothing rebuilt.

| What we compared | Our engine vs Google Chrome |
| --- | --- |
| SETTINGS frame (header table size, push, max concurrent streams, initial window, max frame, max header list, enable connect protocol) | same |
| Header order | same |
| ALPN | same |

To be exact about scope: the probe printed the settings, the header order and ALPN. It did not print the WINDOW_UPDATE increment or the PRIORITY frames as rows of their own. Those come out of the same untouched code, so we had no reason to expect a difference there, but we didn't print them and we won't claim them. The IPHey score had its causes somewhere else. HTTP/2 went on the list next to the TLS handshake: identical, and not the tell. (The handshake probe, which took about a minute, is in [reading a browser's ClientHello with a one-minute listener](https://mosaicbrowser.com/blog/reading-a-client-hello-in-a-minute).)

## Why we don't vary it per profile

Some anti-detect browsers sell control over network fingerprints as a feature. We kept Chrome's TLS and HTTP/2 fingerprints on purpose. Every copy of one Chrome version sends identical frames, which is what makes the crowd. Give one profile its own string and it is the only browser anywhere sending it. Let the string drift away from the version in the User-Agent and you have built the precise contradiction Akamai's method was written to catch. The same argument for the handshake is in [why our engine has no TLS fingerprint setting](https://mosaicbrowser.com/blog/no-tls-fingerprint-setting), and why Chrome's JA3 changes between connections anyway is in [why Chrome's JA3 hash changes on every connection](https://mosaicbrowser.com/blog/why-chromes-ja3-changes).

## Questions

### What is the Akamai HTTP/2 fingerprint?

A string built from the first frames an HTTP/2 client sends: its SETTINGS values in order, its connection WINDOW_UPDATE increment, any PRIORITY frames, and the order of its pseudo-headers. Akamai's researchers described it in a white paper at Black Hat Europe 2017.

### Can JavaScript change the HTTP/2 fingerprint?

No. The frames are written by the browser's network stack when the connection opens, before the page or any script exists. Only the HTTP/2 implementation itself decides them.

### Why do scripts and libraries get caught by the HTTP/2 fingerprint?

Each HTTP/2 library has its own defaults for the SETTINGS values, the window increment and the pseudo-header order. A client that claims to be Chrome in its User-Agent but sends a library's frames contradicts itself before the first byte of HTML.

### Should an anti-detect browser randomise its HTTP/2 fingerprint?

We think not. Every Chrome of one version sends the same frames, so a per-profile variant separates nobody from anybody. It makes one profile the only client on the internet with that string.

## Related

[Reading a browser's ClientHello with a one-minute listener](https://mosaicbrowser.com/blog/reading-a-client-hello-in-a-minute): A raw TCP listener that reads the first TLS record and hangs up captures a browser's ClientHello. Ours and branded Chrome's matched in every field.

[Why our engine has no TLS fingerprint setting](https://mosaicbrowser.com/blog/no-tls-fingerprint-setting): You can't change Chrome's TLS fingerprint from a setting, and we chose not to patch it: our engine's JA4 matched Google's own build byte for byte.

[Why Chrome's JA3 hash changes on every connection](https://mosaicbrowser.com/blog/why-chromes-ja3-changes): Chrome's JA3 changes because Chrome shuffles its TLS extension order on every connection, and JA3 hashes that order. JA4 sorts first and holds still.

[Machines that exist: fingerprints drawn from real hardware](https://mosaicbrowser.com/features/real-fingerprints): Mosaic draws each profile's GPU, screen, CPU, memory and fonts from one real device, so the parts of a fingerprint agree with each other and with the host.

[Why every Mac's Chrome says Mac OS X 10_15_7](https://mosaicbrowser.com/blog/mac-os-x-10-15-7): Chrome froze the macOS version in its User-Agent, so every Mac claims 10.15.7. The real one is in Sec-CH-UA-Platform-Version, a hint sites must ask for.

[Why the User-Agent says Windows 10 on Windows 11](https://mosaicbrowser.com/blog/windows-11-user-agent-windows-10): Windows 11 and 10 both send Windows NT 10.0 in the User-Agent. Only the platformVersion client hint, 13 or higher, says Windows 11.

[Why Chrome's User-Agent says 0.0.0](https://mosaicbrowser.com/blog/chrome-user-agent-0-0-0): Chrome zeroes everything after the major, so the User-Agent says 0.0.0 and the build moves to Sec-CH-UA-Full-Version-List. A real build in the UA is a tell.

[Privacy Sandbox is retired, and its APIs are a version tell](https://mosaicbrowser.com/blog/privacy-sandbox-apis-removed): Chrome is removing Topics and Protected Audience release by release, so whether runAdAuction exists tells a page which Chrome you really are.

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