TCP/IP Fingerprinting: How Your OS Leaks Through Every Connection

Key takeaways

  • The first packet of every TCP connection names your operating system. Initial TTL, window size, MSS, window scale and TCP option order are kernel defaults chosen before any application runs.
  • Option order is the strongest field. The specification does not mandate one, so Linux, Windows and macOS each picked a different arrangement and kept it.
  • Initial TTL splits the world coarsely: 64 for Linux, Android, macOS and iOS; 128 for Windows. Servers see the decremented value and round up.
  • Passive fingerprinting sends nothing back. Tools in the p0f lineage have matched SYN packets against a signature database for two decades; the same logic now runs inside edge networks.
  • JA4T writes the signature as a readable string — window size, option kinds in order, MSS, window scale — so you can see which field differs instead of comparing hashes.
  • You cannot fix this from the browser. By the time JavaScript exists the handshake is finished, and no anti-detect setting or proxy configuration reaches below the application layer.
  • We ship an OS-level TCP/IP fingerprint rewrite on opted-in devices — the outbound SYN is rebuilt at the network layer to present a macOS signature. It changes the TCP/IP layer only.

Most fingerprinting discussion stops at the browser. Canvas hashes, WebGL renderer strings, font lists, the TLS handshake — all real, all measurable, all fixable with enough effort.

Underneath all of it is a layer nobody configures, because there is nothing to configure. Before your browser exists, before TLS negotiates, before a single byte of application data moves, your operating system's kernel sends a packet. It is built from defaults chosen by whoever wrote the network stack, and those defaults differ on Linux, Windows, macOS and Android.

So an anti-bot system can form an opinion about your operating system from the first packet, and compare it against everything you claim afterwards. This page covers what is in that packet, how it is read, and the one place we do something about it.

What the SYN packet reveals

Every TCP connection opens with a SYN packet, and that packet is full of defaults you never chose. Initial TTL, advertised window size, maximum segment size, window scale factor, and which TCP options are present and in what order are all decided by the operating system's networking stack before any application is involved.

An outbound SYN carries roughly this much identifying material:

IP header
  ttl              64            ← OS family
  df (don't fragment) set        ← OS behaviour
TCP header
  window           65535         ← OS default receive window
  options
    mss 1460                     ← path MTU, so: the network
    nop
    wscale 6                     ← OS default scale exponent
    nop, nop
    timestamps                   ← present or absent per OS
    sackOK                       ← present or absent per OS
    eol                          ← padding style per OS
                                 ↑ and the ORDER of all of it

None of these fields is secret or unusual; they are ordinary protocol negotiation. What makes them useful for detection is that they are stable per operating system and unset by the user. Nobody types a window scale exponent.

Initial TTL: which OS family sent this

The IP header's time-to-live field starts at a value the OS picks and drops by one at every router. Linux, Android, macOS and iOS start at 64; Windows starts at 128; some network equipment starts at 255. A server sees the remaining value and rounds up to the nearest of those three.

Initial TTLTypical origin
64Linux, Android, macOS, iOS, the BSD family
128Windows
255Some Unix variants, and a great deal of network equipment

A server receiving TTL 52 concludes the packet started at 64 and crossed twelve hops; one receiving 118 concludes it started at 128. It is a coarse split, but it is free, on every packet, and it immediately contradicts a client claiming Windows while starting at 64.

Window size, MSS and window scale

Three numeric fields in the SYN narrow the guess considerably. The advertised receive window is an OS default. The maximum segment size is derived from the link's MTU, so it describes the network path. The window scale exponent is another OS-specific constant. Together they separate families that share a TTL.

Individually any of these could belong to several systems. Combined with TTL and the option layout below, the set usually names an OS family and often a rough version range: the same logic behind every other detection layer, where no single field is conclusive and the combination almost always is.

TCP option order: the strongest field of the group

TCP options are a list, and the specification does not mandate an order, so every stack picked one and kept it. Linux, Windows and macOS each pad and arrange the same options differently, and that arrangement is the single most distinguishing part of the packet. It survives NAT, and it survives most tunnels.

The characteristic layouts, in packet order:

OS familyTypical TCP option arrangement in the SYN
Linux and AndroidMSS, SACK permitted, Timestamps, NOP, Window scale
WindowsMSS, NOP, Window scale, NOP, NOP, SACK permitted
macOS and iOSMSS, NOP, Window scale, NOP, NOP, Timestamps, SACK permitted, End of list

The NOP entries are padding that keeps multi-byte options aligned, and even the padding style differs: macOS closes the list with an explicit end-of-list marker; the others do not.

Because option order is a sequence rather than a value, it is far harder to hit by accident than any single number. Two machines can share a TTL, a window size and an MSS and still be told apart instantly by the order they listed their options in. That is why it carries the most weight in the packet, and why a rewrite has to get it exactly right.

Passive fingerprinting, and why it costs nothing

Passive fingerprinting reads packets that were going to arrive anyway. Nothing is sent back, nothing is probed, and the client cannot tell it happened. Tools in the p0f lineage have matched SYN packets against a signature database since the early 2000s, and the same logic runs inside modern edge networks at line rate.

The distinction from active fingerprinting matters. Active fingerprinting, what a port scanner does with an OS-detection flag, sends deliberately unusual packets and studies how the stack replies: accurate, noisy, and detectable by the target. Passive fingerprinting just watches, with no probe to notice, no added latency, and no way for a client to know it is being profiled.

Web detection is passive, always. The packets arrive regardless, so classifying them is close to free. And because it happens at the transport layer, the classification is complete before the connection is routed to an application: a request is scored on its operating system before the server knows what URL was wanted.

JA4T: a readable notation for the TCP layer

JA4T is the TCP member of the JA4 fingerprint family, and it writes the SYN's identifying fields as one readable string: window size, the TCP option kinds in order, MSS, and window scale, joined by underscores. Unlike a hash, you can read it and see immediately which field differs.

The option kinds are the protocol's own numbers — 0 for end of list, 1 for NOP, 2 for MSS, 3 for window scale, 4 for SACK permitted, 8 for timestamps — listed in the order they appeared:

65535_2-1-3-1-1-8-4-0_1460_6
  │            │        │  │
  │            │        │  └── window scale exponent
  │            │        └───── MSS
  │            └────────────── option kinds, in packet order:
  │                            MSS, NOP, WScale, NOP, NOP, TS, SACKperm, EOL
  └─────────────────────────── advertised window size

That string is the macOS layout, and the one our own rewrite produces on the wire; the next section is about how. The notation's value is that it is diffable: when a client does not match, you can see whether the mismatch is one padding byte or an entirely different stack, without decoding a hash. JA4T sits alongside JA4 for TLS and JA4H for HTTP.

Why you cannot fix this from the browser

Because the browser does not build the packet. By the time JavaScript exists, the TCP handshake completed several milliseconds ago, using values the kernel chose. There is no API, no flag, no anti-detect setting and no proxy configuration that reaches this layer — it is below the application entirely.

This is the structural reason anti-detect browsers cannot close the gap. They are extremely good at the layer they operate on (canvas, WebGL, fonts, screen metrics, navigator properties, automation flags) because all of that lives in the browser process.

The packet does not. A profile presenting as macOS on a Linux server produces a macOS browser environment on a Linux TCP stack, and the two disagree in a way the profile cannot reach. Automation frameworks share the ceiling: the Playwright leaks you can close all sit above the transport.

Changing the packet requires privileged access on the machine that emits it, operating at the network layer, on every outbound connection. That is a systems problem rather than a browser problem, which is why almost nobody does it.

What we ship: an OS-level TCP/IP fingerprint rewrite

We rewrite the TCP/IP signature in flight. On an opted-in device, outbound SYN packets are intercepted at the network layer and their TCP options are rebuilt into a macOS layout before they leave the handset, so the connection presents a macOS TCP/IP fingerprint rather than the Android device's.

The mechanism, without the operational detail:

Two things made this hard, and they explain why the capability is rare. First, it is below the application layer, unreachable from the browser, the automation framework or the proxy protocol, so it has to happen on the device that emits the packet with privileges most deployments do not have. Second, the mobile data path performs its own address-family translation before the packet reaches the carrier, and the rewrite has to survive that with the option order intact. Confirming it meant capturing packets on the device's cellular interface and reading the option line directly rather than trusting a fingerprinting website's summary.

What the rewrite does not do

It changes the TCP/IP layer and nothing else. Your TLS fingerprint, your HTTP header order, your canvas hash, your timezone and your behaviour are all produced by your machine and pass through untouched. A macOS TCP signature under a Chrome-on-Windows browser profile is a new contradiction, not a fix.

What remains unchanged, stated plainly:

There is a further caveat, and it is why the feature is opt-in rather than default: a macOS desktop TCP signature arriving from a mobile carrier address is itself an unusual combination. Real macOS laptops do tether to cellular, so it is not impossible, but it is not a common shape either, and a thorough system can notice. Whether it helps depends on what the rest of your stack claims to be: consistency beats any individual value.

MSS also leaks tunnels and VPNs

MSS is derived from the path MTU, so it describes the network as much as the OS. A standard Ethernet path gives 1460. VPN and tunnel encapsulation eats into it, producing lower values that mark the connection as tunnelled regardless of what the rest of the packet claims.

The arithmetic is simple: every layer of encapsulation adds header bytes that come out of the payload budget, so the values cluster in recognisable places per protocol.

Mobile networks have their own characteristic MTUs, so a cellular path does not produce a plain Ethernet MSS either. That is expected and self-consistent — a carrier address with a carrier-shaped MSS agrees with itself. The problem case is the mismatch: a claimed desktop-on-broadband identity carrying an MSS only a tunnel produces.

How to see your own signature

Capture your own SYN and read the fields. Any packet capture on the sending machine shows the options line of an outbound SYN directly, and online fingerprint services report what they observed from the server side. Comparing the two tells you whether anything on the path rewrote your packet.

The comparison is the interesting part. Run it three ways:

That second row surprises most people, and it is worth stating plainly: with an ordinary proxy, the operating system a site fingerprints is the exit's, not yours. It is one of the few places the proxy architecture genuinely changes a signal below the application layer, and one more reason to understand it in detail: how mobile proxies work.

Should you care about this layer?

Only if the rest of your stack is already consistent. TCP/IP fingerprinting is a low-weight signal on its own and a high-weight one in combination, so it matters most to people who have already matched their TLS handshake, controlled their browser fingerprint, and are being caught by something they cannot see.

If your requests are still identifiable as a Python script at the handshake, fix that first; it is cheaper and carries more weight. If twenty accounts share one canvas fingerprint, fix that first. If your traffic moves at a rate no human moves at, no packet helps.

But if you have done all of that and are still collecting firewall blocks with no visible cause, this is the layer people forget, precisely because there is no setting for it. A few option bytes in a packet nobody looks at is not where most people expect their problem to be, which is exactly why it is worth checking.

Frequently asked questions

What is TCP/IP fingerprinting?

It is identifying an operating system from the low-level fields in its packets rather than from anything the application sent. The SYN packet that opens every TCP connection carries an initial TTL, an advertised window size, a maximum segment size, a window scale factor and a specific arrangement of TCP options, and those values are OS defaults.

Can a website see my real operating system even through a proxy?

It sees the operating system of whatever machine terminates the TCP connection to it. With an ordinary forwarding proxy, that is the proxy's exit node, so the site reads the exit machine's stack, not yours. With a VPN, the packets are your own kernel's and your OS is visible directly.

What is the difference between passive and active OS fingerprinting?

Passive fingerprinting only reads packets that were arriving anyway — no probes are sent and the client cannot detect it. Active fingerprinting, as done by port scanners, sends deliberately unusual packets and studies the replies. Web detection is almost always passive, because it is free and invisible.

What does a JA4T string mean?

JA4T is the TCP member of the JA4 fingerprint family. It concatenates four things from the SYN — the advertised window size, the list of TCP option kinds in the order they appeared, the maximum segment size, and the window scale value — separated by underscores, so each field can be read directly.

Can I change my TCP fingerprint from the browser or an anti-detect tool?

No. Browsers do not build packets; the kernel does, before any page script exists. Changing it requires privileged access on the machine that emits the packet, at the network layer. That is why a browser profile claiming macOS on a Linux host is detectable at the packet level regardless of how good the profile is.

Does MSS reveal that I am using a VPN?

It can. Maximum segment size is derived from the path MTU, so a plain Ethernet path yields 1460 while tunnelling protocols consume header space and produce lower values. An unusual MSS does not prove a VPN by itself, but it is a strong hint that encapsulation sits somewhere on the path.

Dedicated mobile proxies, one dashboard

Real 4G/5G devices on US carrier SIMs. Sticky IP per customer, rotation on demand.

See plans