Every HTTP request your browser makes carries a User-Agent header. It looks like a specification and reads like an archaeology site:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
That is Chrome on Windows. It claims to be Mozilla, mentions Apple's rendering engine, claims to be like Gecko, and ends by claiming to be Safari. Only one token in it is true in the obvious sense.
Why it reads like that
In the 1990s, servers checked the user agent to decide whether to send frames-capable HTML. Netscape ("Mozilla") supported frames, so servers looked for that token. Internet Explorer supported frames too, and to avoid being served the stripped-down page it claimed to be "Mozilla (compatible; MSIE …)".
Every browser since has inherited the problem. Safari added AppleWebKit and also claimed like Gecko so that Gecko-targeting code would work. Chrome added Chrome/ and kept Safari/ so WebKit-targeting code would work. Edge added Edg/ and kept everything else.
The string is therefore a stack of compatibility claims in rough historical order, and the only reliable way to read it is to look for the most specific token present: Edg/ before Chrome/, Chrome/ before Safari/, which is exactly what a user agent parser does.
What it is legitimately used for
Analytics. Aggregate browser and platform share, to decide what to test and what to stop supporting.
Serving the right asset. Offering an APK to Android and an IPA to iOS from the same download page.
Bug reproduction. A bug report is much more actionable with the exact browser and version attached.
Crawler identification. Bots identify themselves in the user agent, which is how a server distinguishes a search crawler from a person.
What it should not be used for
Feature detection. "Is this Safari 14" is a proxy for "does this support the API I want", and a bad one. Ask about the API directly; the answer stays correct across versions, forks and embedded webviews you have never heard of.
Security decisions. The header is set by the client. Anyone can change it in a devtools panel or a one-line curl flag. Nothing that matters should depend on its value.
Blocking. Blocking by user agent blocks polite bots and courteous users, and does nothing to anything hostile, which will send whatever string gets through.
The string is shrinking on purpose
The user agent is a fingerprinting surface: precise OS version, exact browser build and device model combine with other signals to identify a specific person across sites without any cookie.
So browsers have been reducing it. Chrome freezes parts of the string and reports minor versions as zeros. Safari has reported a near-static string for years. Platform detail on mobile has been generalised: many Android devices now report a single generic model rather than their own.
The replacement is Client Hints: the browser sends a low-entropy set by default (rough platform, rough browser) and a site must explicitly request more detail, which makes the request visible rather than automatic.
The practical consequence is that user-agent-derived analytics get vaguer every year, and code that compares version numbers gets more fragile. Both arguments point the same way: detect capabilities.
Reading one quickly
- Ignore
Mozilla/5.0. Every browser says it. - Find the most specific browser token:
Edg/,OPR/,SamsungBrowser/, thenChrome/, thenFirefox/, thenSafari/. - Read the platform from the parenthesised section.
- Treat the numbers as approximate, because increasingly they are.