Font fingerprinting explained, mechanically
Most browsers do not hand a page a list of installed fonts on request. So a page asks a narrower question, over and over: is this specific font installed, yes or no, and it answers that question by measuring text rather than reading a list.
The measurement technique
Render a fixed test string in a candidate font with a generic fallback behind it, "Candidate Font", monospace, and separately in just the fallback, monospace. If the candidate font is installed, the browser substitutes it and the rendered text measures differently, width and often height, from the fallback-only baseline. If it is not installed, the browser falls through to the fallback both times and the two measurements match.
function isFontAvailable(font) {
const testString = 'mmmmmmmmmmlli'
const testSize = '72px'
const fallbacks = ['monospace', 'sans-serif', 'serif']
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const baselineWidths = fallbacks.map(fallback => {
ctx.font = `${testSize} ${fallback}`
return ctx.measureText(testString).width
})
return fallbacks.some((fallback, i) => {
ctx.font = `${testSize} "${font}", ${fallback}`
return ctx.measureText(testString).width !== baselineWidths[i]
})
}
console.log('Arial', isFontAvailable('Arial'))
console.log('Comic Sans MS', isFontAvailable('Comic Sans MS'))
console.log('SF Pro Display', isFontAvailable('SF Pro Display'))
Run this once per candidate font, against a short list of a few hundred common names, and the yes and no answers together form a set: the fonts this specific browser has access to. Older implementations of the same idea used a hidden, off-screen span and compared offsetWidth/offsetHeight against the same three generic baselines. The canvas version above does the same comparison without touching the DOM.
document.fonts.check()
The CSS Font Loading API exposes a more direct version of the same question: document.fonts.check('12px "Font Name"') returns a boolean for whether that font is available to render, without the page having to build its own fallback comparison. It is cheaper than the measurement technique and produces the same yes or no per font, tested one candidate at a time, not a bulk listing.
console.log(document.fonts.check('12px "Arial"'))
console.log(document.fonts.check('12px "SF Pro Display"'))
The Local Font Access API
A newer, permission-gated API lets a page skip probing and ask for the real thing: window.queryLocalFonts() returns actual font metadata, family name, full name, postscript name, straight from the system font list. Unlike the techniques above, it requires an explicit permission grant before it returns anything, so a script cannot get this list silently on page load, which is why most fingerprinting collection still relies on probing rather than this API.
Why the installed font set is so identifying
The fonts available to a browser are not a browser property, they are a record of the machine underneath it. Windows systems ship with fonts like Calibri and Segoe UI. macOS ships San Francisco and Helvetica Neue. Linux distributions typically ship DejaVu and Noto family fonts by default, and the exact set depends on the distribution. Locale and language pack fonts only appear if that language support was installed, CJK and Indic scripts being the clearest example. Installed software adds more: design and publishing tools bundle their own font libraries, office suites add families beyond the OS defaults. None of that has anything to do with cookies or IP addresses, and all of it is readable through a few hundred width comparisons.
Cross-OS mismatches are the loudest possible tell
A user agent string claiming Windows, paired with fonts that only ship on macOS, is not an unlikely combination. It is an impossible one. Those font files live in the operating system's own font directories, not somewhere a page or a script installs them, so a genuine Windows machine has no legitimate path to having them present. A detector does not need to reason about probability here the way it does for an uncommon screen resolution or an unusual timezone. Either the fonts named in the claimed OS are consistent with the OS actually reporting itself, or they are not, and there is no middle ground to hedge with.
A suspiciously short font list is its own signal
Real desktop machines carry dozens of fonts from OS defaults alone, before any third-party software adds more. A profile that answers "not installed" to nearly everything, matching only the three generic families and nothing else, does not look private, it looks like fonts are being blocked or the environment is a stripped-down image with nothing installed on it. Blocking font enumeration removes the specific signal at the cost of creating a different one: almost no real user has an empty font list, so an empty one is itself rare and identifying, the same trade a blocked canvas read makes.
What holds up
A font set consistent with the OS, locale and language packs the rest of the profile claims, generated once per profile and applied in the browser engine itself rather than assembled by a script that has to guess which fonts a real machine of that description would actually have.
Where a simpler tool is the right answer
Firefox's resist-fingerprinting mode restricts font enumeration to a fixed allow-list baked into the browser, the same list regardless of what is actually installed underneath, which removes this entropy source for free. The tradeoff is that a page requesting a font outside that list will not get it, so some text falls back instead of rendering in the font the page intended, a fine trade for one person browsing privately. It does nothing for someone who needs several profiles that each need to look like their own distinct machine, since a shared allow-list makes every profile match on fonts by design.
Check your own font set
Beyond the snippets above, CreepJS runs a broader font probe alongside its other checks and will flag both an empty list and OS-inconsistent fonts as their own findings. For how font consistency fits into the wider set of properties expected to agree with each other, see the fingerprint consistency checklist, and for the same deterministic-per-machine logic applied to a different API, the canvas fingerprinting post.
Try it on the free tier.
Unlimited local profiles, no credit card. Check it against the detectors yourself.