Seatext library / BotRefund evidence

How to Make a Headless Browser Undetectable: Step‑by‑Step Guide

Use stealth plugins, adjust browser fingerprints, and emulate real‑world behavior to hide a headless browser. Follow the ordered steps below and verify the result with a detection audit.

Built for advertisers who need clear, refund-ready traffic evidence.

You can make a headless browser harder to detect by masking the signals that BotRefund and similar services check, such as the CDP debugger leak, user‑agent mismatches, and engine inconsistencies.

How headless detection works: the 106‑signal model

BotRefund looks at 106 browser, network, hardware, and behavior signals. No single signal decides; the AI weighs the full pattern before labeling traffic as human or bot.

When several signals appear together, the confidence of automation rises. This section explains the most relevant signals for headless browsers and how stealth fixes address each one.

WebRTC Network Leak

Why it exists: WebRTC can reveal local IP addresses even when a VPN is used.

Real browser: Returns the device’s local network interfaces via RTCPeerConnection.

Stealth fix: Block RTCPeerConnection or replace its IP with a fake one using puppeteer-extra-plugin-stealth or a custom page.evaluate.

DNS Tunnel Leak

Why it exists: DNS queries may go to a different resolver than HTTP traffic, exposing a mismatch.

Real browser: Uses the system DNS resolver for both DNS and HTTP requests.

Stealth fix: Route all traffic through the same proxy; ensure DNS settings match the HTTP proxy.

DNS Routing Mismatch

Why it exists: Some resolvers return different IPs for the same domain based on query type.

Real browser: Gets consistent A/AAAA records for a domain.

Stealth fix: Use a trusted resolver (e.g., Google 8.8.8.8) and disable custom DNS settings.

Latency Mismatch

Why it exists: Network round‑trip time reported by JavaScript may differ from actual TCP handshake.

Real browser: Measures latency consistently with network layer.

Stealth fix: Avoid aggressive throttling; keep network conditions natural.

Languages Mismatch

Why it exists: The navigator.languages list may not match the Accept‑Language header or IP location.

Real browser: Sends language preferences that align with geo‑IP.

Stealth fix: Set --lang and override navigator.languages to match the proxy’s locale.

HTTP User-Agent Mismatch

Why it exists: The User‑Agent header and navigator.userAgent can diverge.

Real browser: Header and object reflect the same Chrome version.

Stealth fix: Pass the user‑agent via launch args and overwrite navigator.userAgent in page.

CDP Debugger Leak

Why it exists: Automation leaves traces in the Chrome DevTools Protocol.

Real browser: No debugger agent attached unless devtools are open.

Stealth fix: Use puppeteer-extra-plugin-stealth to hide the debugger endpoint.

Engine Mismatch

Why it exists: The reported JavaScript engine version may differ from the actual Chrome build.

Real browser: engine property matches the binary.

Stealth fix: Patch navigator.userAgent, navigator.appVersion, and window.chrome to reflect a real build.

Automation Properties

Why it exists: Flags like navigator.webdriver are set by automation tools.

Real browser: These properties are undefined or false.

Stealth fix: Set navigator.webdriver = false and overwrite other automation flags.

What headless detection looks for

Bot detection platforms examine over a hundred signals across network, hardware, and browser layers. When several of these signals appear together, they flag the session as automated.

SignalWhat it checksStealth fix
CDP Debugger LeakTraces left by browser automation or masking toolsUse puppeteer-extra-plugin-stealth to hide the debugger protocol
HTTP User-Agent MismatchInconsistent user‑agent string between request headers and navigator objectSet a genuine user‑agent via args and overwrite navigator.userAgent
Engine MismatchDifferences between reported JavaScript engine and real Chrome versionPatch navigator.webdriver, define window.chrome, and spoof navigator.appVersion
Timezone BiasLocation and language settings that don’t alignMatch Intl.DateTimeFormat().resolvedOptions().timeZone to the IP location; set --lang=en-US
WebRTC Network LeakExposes local IP addresses through RTCPeerConnectionBlock RTCPeerConnection or replace its IP with a fake value
DNS Tunnel LeakDNS and web traffic follow different routesRoute all traffic through the same proxy; ensure DNS settings match the HTTP proxy
DNS Routing MismatchInconsistent DNS responses for the same domainUse a stable public resolver (e.g., 8.8.8.8) and disable custom DNS
Latency MismatchJS‑measured latency differs from actual network delayAvoid extreme network throttling; keep connection characteristics natural
Languages Mismatchnavigator.languages does not match Accept‑Language or IP localeSet --lang and override navigator.languages to match proxy locale

Prerequisites before you start

  • Node.js (or Python) environment with puppeteer or selenium installed.
  • Access to puppeteer-extra-plugin-stealth (or equivalent for Selenium).
  • A real‑world user‑agent string from a recent Chrome version.
  • Optionally, a VPN or residential proxy that matches the chosen timezone.

Step‑by‑step process to hide a headless browser

  1. Install the stealth plugin. For Puppeteer run npm i puppeteer-extra puppeteer-extra-plugin-stealth and add it to your launch script.
  2. Launch Chrome with realistic flags. Use --no-sandbox, --disable-blink-features=AutomationControlled, and avoid --headless if possible; instead use headless: 'new' (Chrome 109+). The new headless mode reduces many fingerprint gaps compared to the legacy headless.
  3. Override navigator properties. Inject JavaScript that sets navigator.webdriver = false, defines window.chrome with typical properties, and aligns languages and plugins with a real browser.
  4. Synchronize time‑zone and locale. Set --lang=en-US and adjust Intl.DateTimeFormat().resolvedOptions().timeZone to match the proxy’s IP.
  5. Patch the User‑Agent. Pass the chosen user‑agent via args (e.g., --user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36) and also rewrite navigator.userAgent inside the page.
  6. Disable WebRTC leaks. Add --disable-features=WebRtcHideLocalIpsWithMdns or use a page.evaluate to replace RTCPeerConnection with a mock that returns empty ICE candidates.
  7. Run a short test page. Load a page that prints the above properties; compare the output with a real Chrome session. Expected output shows navigator.webdriver: false, matching user‑agent, and no WebRTC IP leaks.
  8. Common pitfalls. Forgetting to overwrite navigator.userAgent after setting the args leaves a header/object mismatch. Using the old --headless flag can expose the HeadlessChrome string in the user‑agent. Over‑blocking WebRTC (e.g., returning no IP at all) can itself become a signal because real browsers always expose some interface.

Common mistake to avoid

Changing only the user‑agent while leaving navigator.webdriver true is a red flag. Detection tools like BotRefund still see the automation flag and will label the session as a bot.

How to verify your browser is stealthy

After the steps, run BotRefund’s free audit (or any similar detection service). If the audit reports no “CDP Debugger Leak”, “Engine Mismatch”, or “User‑Agent Mismatch”, your setup is passing the most common checks.

Limitations and when stealth may still fail

Even with perfect fingerprint masking, advanced behavioral analysis—such as mouse‑movement jitter, click timing, and network latency patterns—can still reveal automation. If you need to hide those, consider adding human‑like interaction scripts or using a real device farm.

Practical trade‑offs and failure cases beyond fingerprinting

Stealth plugins hide static fingerprints but do not mimic human behavior. Bots that move the pointer in perfectly straight lines, click at exact millisecond intervals, or have uniform session lengths stand out.

Why it matters: Behavior signals like mouse‑jitter, pointer paths, click timing, session duration, and page engagement are part of the 106‑signal model. When these deviate from human norms, the AI raises the bot probability.

When stealth alone is insufficient: If your script performs repetitive actions without variance, detection systems flag the session despite a clean fingerprint.

Additional measures: Introduce random delays between actions, simulate realistic mouse trajectories with slight jitter, vary scroll depth, and mix page visits with idle time. Libraries such as puppeteer‑extra‑plugin‑human‑delay or selenium‑based action chains can help.

Trade‑offs: Adding behavioral noise slows down the script and may reduce throughput. Using a residential proxy improves IP reputation but adds cost and latency. Blocking WebRTC fully can leak the fact that you are hiding it; a better approach is to spoof the local IP to match the proxy’s address.

Bottom line: Fingerprint stealth is necessary but not sufficient. Combine it with realistic behavior, appropriate proxy selection, and continuous audit feedback to lower detection risk.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

Learn more

Visit the website for more information.

Learn more