Headless Browser Detection
Headless browser detection is the practice of identifying automated browsers by probing JavaScript-visible gaps that a real, human-driven browser doesn't have, a navigator.webdriver flag set to true, an empty plugins array, a WebGL renderer string that doesn't match any real GPU, or permission-query results that contradict each other. Each of these checks is cheap to run in-page and hard to fake convincingly, which is why they show up in almost every anti-bot script.
This is the direct counterpart to the headless browser itself: same object, opposite side. Every signal listed below is precisely what a stealth mode patch exists to close.
How Sites Detect Headless Browsers
navigator.webdriver
The single most-checked signal. The WebDriver specification requires browsers under automation control to expose navigator.webdriver as true. A human's browser returns false or undefined. Checking it is a one-line test:
if (navigator.webdriver) {
// automation detected
}
Because it's standardized rather than a quirk, it's reliable, which is exactly why it's the first thing every stealth library patches.
Empty or missing plugin and MIME arrays
A real Chrome install reports entries in navigator.plugins and navigator.mimeTypes. A bare headless instance frequently reports zero. An empty array isn't proof of automation on its own, but combined with other signals it weighs heavily.
WebGL vendor and renderer strings
Querying the WEBGL_debug_renderer_info extension returns the GPU vendor and renderer. Headless browsers running without GPU access typically report a software rasterizer, values like Google SwiftShader or llvmpipe, rather than a plausible consumer GPU. A "Chrome on Windows" User-Agent paired with a software renderer is an obvious mismatch.
User-Agent tells
Older headless Chrome builds shipped HeadlessChrome directly in the User-Agent string. That's trivially overridden now, but the header is still checked for internal consistency against Client Hints (Sec-CH-UA), and the platform values the browser reports elsewhere.
Permissions API inconsistency
A well-known probe compares Notification.permission against the result of navigator.permissions.query({name: 'notifications'}). In a real browser, these agree. In some automated environments, they don't, a contradiction a human session can't produce.
Rendering and timing quirks
Canvas and font rendering differ subtly on a machine with no display, no GPU, and a minimal font set. These differences feed into a browser fingerprint rather than acting as a standalone flag.
Missing behavioral input
A headless session produces no mouse movement, no scroll variance, and no natural dwell time. Behavioral checks run over a session rather than a single page load, which makes them slower to trigger but harder to spoof than any single property.
Detection Signal Summary
| Signal | What it checks | How reliable |
|---|---|---|
navigator.webdriver |
Standardized automation flag | High, spec-mandated, but the first thing patched |
| Plugins / MIME types | Empty arrays where a real browser has entries | Medium, weak alone, strong in combination |
| WebGL renderer | Software rasterizer vs. real GPU | High, hard to fake coherently |
| User-Agent consistency | UA vs. Client Hints vs. platform values | Medium, easy to spoof, easy to spoof wrong |
| Permissions API | Contradictory permission states | Medium, a clear logical impossibility when it fires |
| Behavioral input | Absence of human-like interaction | High over a full session, useless on one page load |
Why Coherence Matters More Than Any Single Flag
Modern detection scripts don't just tally individual flags, they check whether the whole profile hangs together. A browser claiming to be Chrome on macOS while reporting a Linux platform, a software GPU, and zero installed fonts is more suspicious than one that honestly reports being headless, because the inconsistency itself is evidence of tampering. Public tools like CreepJS exist specifically to surface these contradictions, and anti-bot vendors use the same approach.
This is the practical consequence for anyone building a scraper: patching one flag in isolation can make things worse. See MrScraper's guides to headless browser scraping with Playwright and Puppeteer, and to undetected ChromeDriver for the Selenium approach.
Related terms
Anti-Fingerprinting
Learn the definition of anti-fingerprinting, spoofing, or randomizing canvas, WebGL, fonts, and screen attributes so a browser doesn't produce the same identifiable hash.
Read more →Anti-Bot System
An anti-bot system is a layered defense mechanism analyzing IP reputation, fingerprints, and behavior to block automated scrapers.
Read more →Honeypot Trap
Learn the definition of a honeypot trap, a link or form field hidden from human users that flags automated scrapers and bots the moment they interact with it.
Read more →Web Unblocker
Extract data automatically, browse undetected, and beat anti-bot systems — all in one powerful tool.
Get started freeCommunity
Head over to our community where you can engage with us and our community directly.
Questions? Ask our team via live chat, join us on our official Slack community. We're always happy to help.
Join our Slack Community