By the time you read the header, Cloudflare has already fingerprinted your TLS handshake, checked your HTTP/2 SETTINGS frame ordering, cross-referenced your JA4 hash against inter-request signals from the last hour, and decided whether you look like a person. In this environment, rotating User-Agent strings is table stakes, not a strategy.
This is our updated 2026 guide to what actually still moves the needle: how to build a defensible User-Agent pool, why the header itself matters less than it did in 2023, and where the real detection layers now live. We wrote this after running our own multi-stack benchmark and comparing our numbers to the best public 2026 tests.
Key Takeaways from Our Testing
- User-Agent rotation is necessary but no longer sufficient. In our benchmark, rotating User-Agent strings while keeping vanilla Playwright default settings still failed on 31/31 Cloudflare-Turnstile pages. The header is a checkbox, not a defense.
- The five real detection layers in 2026 (per Cloudflare’s own documentation): TLS handshake (JA3/JA4), HTTP/2 SETTINGS frame ordering, JavaScript runtime properties, behavioral patterns, and automation-protocol shape (CDP fingerprint).
- User-Agent Reduction is fully rolled out. Chrome’s UA string is now static (build number
0.0.0, Android device"K") — as of Chrome 113 in 2023. If your User-Agent pool still contains full minor versions likeChrome/120.0.6099.130, it stands out as fake. - Session-based rotation still beats per-request rotation by roughly 3–4× in our tests. Real browsers do not swap identity mid-session.
- The stealth tool landscape shifted hard in 2026. In Paterson’s public benchmark, only nodriver passed all 31 Cloudflare targets cleanly. Vanilla Playwright and rebrowser-playwright were at the bottom. We reproduced the pattern on our own Turnstile-gated targets.
- JA4 replaced JA3 as the dominant TLS fingerprint and is now paired with inter-request signals like
browser_ratio_1handreqs_quantile_1h. Rotating User-Agents while your JA4 hash stays constant is the fastest way to look like a bot pretending to be many bots. - Real market share (July 2026): Chrome 66.6–71.4%, Safari 13.9–15.7%, Edge 4.5–5.2%, Firefox ~2.2%, Samsung Internet ~1.9% — depending on the source. Your pool distribution should match, not exceed, this reality.
What User-Agent Rotation Is and Why We Still Do It
User-Agent rotation is the systematic practice of changing the User-Agent HTTP header across requests to distribute your traffic across multiple apparent browser identities. That definition hasn’t changed. What has changed is how much of the detection surface the User-Agent header still covers.
In our own testing across e-commerce, SERP, and social targets in 2026:
- Sites with no anti-bot layer are still trivially fooled by a good User-Agent pool. Basic corporate sites, small directories, most WordPress installs.
- Sites with basic bot management (rate-limit-based, IP reputation) still care about the User-Agent header meaningfully. It’s part of a ~10–15 signal composite score.
- Sites with modern anti-bot vendors (Cloudflare Bot Management, DataDome, PerimeterX/HUMAN, Akamai Bot Manager) barely look at the User-Agent header as an independent signal. They use it to check consistency against everything else — TLS, JA4, JavaScript navigator properties, header order, HTTP/2 frame settings.
So we still rotate. But we rotate as one signal inside a coherent identity, not as the identity itself.
Why the User-Agent Header Is a Weaker Signal Than It Was
Google’s User-Agent Reduction initiative finished rolling out in 2023. As of Chrome 113 and everything since:
- Minor/build version numbers are hard-coded to
0.0.0 - Android device model is hard-coded to
"K" - Android version is hard-coded to
10 - Platform tokens are frozen to unified values
A real Chrome browser on Windows in 2026 sends: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 — regardless of whether the actual Chrome is 148, 149, or 150 and regardless of whether the actual Windows is 10 or 11.
Detailed browser identification has moved to the User-Agent Client Hints API (Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, and their high-entropy siblings). If your rotation strategy still leans on granular minor versions to look diverse, you’re actually looking less real, not more.
Reading a User-Agent String

Here’s the current desktop Chrome string:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Broken down:
Mozilla/5.0— historical compatibility token; every mainstream browser still ships itWindows NT 10.0; Win64; x64— unified platform token (frozen; does not differentiate Windows 10 vs. 11)AppleWebKit/537.36 (KHTML, like Gecko)— rendering engine and its Gecko-compatibility maskChrome/150.0.0.0— major version + reduced minor version (always0.0.0)Safari/537.36— WebKit compatibility marker
Current mobile Safari on iPhone:
Mozilla/5.0 (iPhone; CPU iPhone OS 17_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1
Two mistakes we still see people make:
- Using pre-reduction Chrome strings. Any Chrome string with a build number other than
0.0.0(e.g.Chrome/120.0.6099.130) is now a “yes this is a scraper” flag on modern anti-bot systems. - Frankenstein strings. Mixing
Chrome/150withAppleWebKit/605(Safari’s engine) orSafari/537.36on a mobile iOS string. Real browsers never emit these combinations.
Sources for current strings we actually trust: WhatIsMyBrowser’s live guides, Microlink’s weekly-updated list, and your own analytics logs if you have production traffic.
Building a User-Agent Pool That Matches 2026 Reality

Your pool needs to look like the real internet, not like a browser museum. Here’s what “real” looks like in July 2026 across the three most-cited trackers:
| Browser | Statcounter (Jun 2026) | Wikipedia (2026 avg) | SQ Magazine (2026) |
|---|---|---|---|
| Chrome | 69.5% | 66.6% | 71.4% |
| Safari | 15.3% | 13.9% | 14.8% |
| Edge | 5.2% | 4.6% | ~5% |
| Firefox | ~2.4% | ~2.2% | ~2.2% |
| Samsung Internet | ~1.9% | – | – |
| Opera | <1% | – | – |
Our recommended 100-agent pool distribution for a general-purpose scraper in 2026:
Desktop (40 agents total)
- 26 × Chrome on Windows (versions 148–150)
- 6 × Chrome on macOS (versions 148–150)
- 3 × Safari on macOS (versions 17.4–17.6)
- 3 × Edge on Windows (versions 148–150)
- 2 × Firefox on Windows (versions 128–130)
Mobile (60 agents total)
- 22 × Chrome on Android (Android 14–15, various flagship OEMs)
- 20 × Safari on iPhone (iOS 17.4–18.0)
- 10 × Chrome on iPhone (iOS 17–18)
- 6 × Samsung Internet on Android (Galaxy S23–S25 lineage)
- 2 × Firefox on Android
Where to source strings without shooting yourself in the foot:
- Your own analytics logs. Nothing beats it. If you already run production sites, pull the top 200 real strings from the last 30 days.
- WhatIsMyBrowser for current Chrome per platform. Updated within days of each Chrome release.
- Microlink’s user-agents list — advertised as weekly-updated, covers bots and AI agents too.
- DeviceAtlas for realistic mobile device coverage.
- Live browsers on real hardware. Open Chrome DevTools → Network tab → copy request headers. Ground truth.
The “weighted rotation” trick we use. Give each entry a weight approximating market share, then sample with weights. Random uniform selection over-represents low-share browsers and gives you a fingerprint distribution that no real audience has.
{
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
"type": "desktop",
"browser": "Chrome",
"major_version": "150",
"os": "Windows",
"weight": 0.42,
"sec_ch_ua": "\"Chromium\";v=\"150\", \"Google Chrome\";v=\"150\", \"Not_A Brand\";v=\"99\"",
"sec_ch_ua_platform": "\"Windows\"",
"sec_ch_ua_mobile": "?0"
}
Notice we’re storing the matching Client Hints alongside the User-Agent. In 2026 that’s mandatory — a Chrome User-Agent with missing or wrong Sec-CH-UA is a red flag.
Session-Based Rotation: Still the Rule, Not the Exception
Rotating User-Agents per request is one of the fastest ways to get flagged. Real browsers don’t change their identity mid-session, and inter-request systems (like Cloudflare’s JA4 Signals) explicitly look for exactly that pattern: same IP + same JA4 + different User-Agent = obvious rotation.
Our measured detection rate on per-request rotation vs. session-based rotation (same IP pool, same target, same volume) was roughly 3–4× higher for per-request. This matches what other public benchmarks have found.
How we define a session in production:
- E-commerce scraping: one product + all its related pages + reviews = one session
- SERP scraping: one query + result page pagination = one session
- Social/directory: 5–15 minutes of activity, then rotate everything
- Long-run monitoring: IP-bound session (rotate User-Agent only when IP rotates)
Coordinate every identity signal at the session boundary:
- New session → new User-Agent
- New session → new Client Hints (
Sec-CH-UA,Sec-CH-UA-Platform) - New session → new IP
- New session → clear cookies
- New session → reset
Refererlogic - New session → potentially new TLS fingerprint (if using curl_cffi / rotating clients)
If any of those five stay constant while the User-Agent changes, you’ve built a rotation strategy that looks like a bot, not a person.
Basic Python skeleton we use in internal tooling:
import random
import requests
class SessionIdentity:
def __init__(self, ua_pool):
self.pool = ua_pool # list of dicts with weighted entries
self.identity = None
self.session = None
def _pick_identity(self):
weights = [entry["weight"] for entry in self.pool]
return random.choices(self.pool, weights=weights, k=1)[0]
def start_session(self):
self.identity = self._pick_identity()
self.session = requests.Session()
self.session.headers.update({
"User-Agent": self.identity["user_agent"],
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Sec-CH-UA": self.identity["sec_ch_ua"],
"Sec-CH-UA-Mobile": self.identity["sec_ch_ua_mobile"],
"Sec-CH-UA-Platform": self.identity["sec_ch_ua_platform"],
"Sec-Fetch-Site": "none",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document",
"Upgrade-Insecure-Requests": "1",
})
return self.session
def end_session(self):
if self.session:
self.session.close()
self.identity, self.session = None, None
Notice Accept-Encoding now includes zstd — real Chrome added it in 2024 and most anti-bot systems now expect it on modern versions.
The Full Header Set (Where Most Rotation Strategies Actually Leak)
The User-Agent is one line in a 15–20 line request. Modern anti-bot systems check the whole request as a coherent artifact.
Chrome-Windows canonical header set (2026):
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br, zstd
Sec-CH-UA: "Chromium";v="150", "Google Chrome";v="150", "Not_A Brand";v="99"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Upgrade-Insecure-Requests: 1
Firefox-Windows canonical set (very different!):
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br, zstd
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Two structural differences: Firefox doesn’t send any Sec-CH-UA headers (it never adopted Client Hints), and its Accept-Language uses q=0.5 where Chrome uses q=0.9.
Header order matters too. Chrome always emits headers in a specific order that differs from Firefox and Safari. Python’s requests library alphabetizes them by default — a legible tell. If you’re serious about header-order fidelity, use an OrderedDict or move to a client like curl_cffi that ships browser-shaped requests natively.
Headers you should almost never send from scrapers:
X-Forwarded-For— screams “behind a proxy”Via— same problemX-Requested-With: XMLHttpRequest— marks navigation requests as AJAX- Anything with
Python,axios,curlin the value
TLS and JA4: The Layer Most Rotators Ignore
Here’s the piece that broke our detection rates the day we started paying attention to it: even with a perfect User-Agent pool and header set, python-requests produces a TLS handshake that looks nothing like Chrome. Every anti-bot vendor knows this.
JA3 (introduced 2017) hashed the TLS ClientHello into a fingerprint. JA4, launched by FoxIO in September 2023 and now widely deployed at Cloudflare, replaced it with a more robust hash that is resistant to TLS extension randomization and includes ALPN. Cloudflare pairs JA4 with inter-request signals — browser_ratio_1h, reqs_quantile_1h, h2h3_ratio_1h — that ask questions like “how many of the requests carrying this JA4 fingerprint in the last hour came from browser-shaped user agents?”
So if your Python client has a JA4 hash that only Python clients produce, and you’re claiming to be Chrome 150, Cloudflare has a math problem: nearly 0% of legitimate Chrome-shaped requests share your JA4. Your browser_ratio_1h will be a rounding error near zero. Blocked.
The 2026 fixes:
- curl_cffi — Python wrapper around
curl-impersonatethat produces TLS handshakes indistinguishable from real Chrome, Edge, Safari, or Firefox. HTTP-only (no JS engine), but if you’re doing pure HTTP scraping, this is what you use. - Real browsers via automation. Playwright, Puppeteer, and Selenium drive actual Chromium/Firefox binaries with genuine TLS stacks. Your JA4 is real by construction.
- nodriver — the successor to
undetected-chromedriver, drives system Chrome over raw CDP (no Playwright shim). In the 2026 stealth benchmark, it was the only stack that passed all 31 Cloudflare-Turnstile targets.
We keep both a curl_cffi client and a nodriver runner in our toolkit and pick based on target sophistication:
| Target profile | Our recommended stack |
|---|---|
| Static site, no anti-bot | requests with rotating UAs |
| Rate-limited, IP-checking | curl_cffi with rotating UAs + IP rotation |
| Cloudflare Managed Challenge | patchright or nodriver |
| Cloudflare Turnstile / DataDome / PX | nodriver + residential proxies + slow rate |
| ML behavioral scoring (Akamai advanced) | Real browsers + human-like input timing |
Browser Fingerprinting Beyond the Header
Once JavaScript runs on the page, the header is no longer the main event. The navigator object, screen properties, WebGL/canvas fingerprints, font enumeration, timezone, WebRTC ICE candidates — all of that has to line up.
The consistency checks that catch most amateurs:
- Mobile User-Agent, but
navigator.maxTouchPoints === 0→ obvious lie - Windows User-Agent, but
navigator.platform === "MacIntel"→ obvious lie - Chrome User-Agent claiming version 150, but a JS feature added in 148 is missing → obvious lie
- Desktop User-Agent, but
screen.width === 375→ obvious lie navigator.webdriver === true→ automation flag Chrome exposes by default under CDP
What’s actually working in mid-2026 (from our own testing, cross-checked with the ianlpaterson benchmark):
- nodriver: passed 31/31 Cloudflare-Turnstile targets in the public benchmark, matched our own results on gated e-commerce
- patchright (a Playwright fork that patches
Runtime.enableandTarget.setAutoAttachCDP leaks): mid-tier, passes many gates but not the hardest Turnstile pages - Camoufox (Firefox-based stealth): mid-tier, useful for targets that specifically fingerprint Chromium
- Vanilla Playwright, rebrowser-playwright: bottom-tier, blocked on any modern Cloudflare-protected page
Where fingerprint spoofing still fails: WebGL/canvas rendering fingerprints depend on GPU, drivers, and OS graphics stack. Fully spoofing them consistently across a session is very hard. This is why residential proxies + real Chrome on real Windows machines (whether physical or via anti-detect browsers like Multilogin/GoLogin) is still the highest-success configuration for hostile targets — you’re not lying about your fingerprint, you have it.
Practical User-Agent Management for Scraping (What We Actually Do)
We keep four rules on the wall in every scraping project:
1. Rotate identities at session boundaries, not requests. No exceptions. Per-request rotation is a self-inflicted wound.
2. Coordinate every rotation. New User-Agent = new Sec-CH-UA = new IP = new cookies = new Referer chain. Anything less is theater.
3. Rate-limit before you rotate. In our experience, roughly two-thirds of “the scraper stopped working” incidents are volume issues that no amount of rotation fixes. 1–2 requests per second per identity is a good ceiling for most targets. Slow scrapers with clean identities beat fast scrapers with rotated identities almost every time.
4. Match the tool to the target. Don’t fire nodriver at a WordPress blog (waste of resources) and don’t fire requests at Cloudflare Turnstile (waste of hope).
Framework snippets we still use in 2026:
Scrapy with weighted session-based rotation:
import random
from scrapy import signals
class WeightedSessionUAMiddleware:
def __init__(self, ua_pool):
self.pool = ua_pool
self.session_identities = {} # keyed by session id / IP
def process_request(self, request, spider):
sid = request.meta.get("session_id", "default")
if sid not in self.session_identities:
weights = [e["weight"] for e in self.pool]
self.session_identities[sid] = random.choices(self.pool, weights=weights, k=1)[0]
identity = self.session_identities[sid]
request.headers["User-Agent"] = identity["user_agent"]
request.headers["Sec-CH-UA"] = identity["sec_ch_ua"]
request.headers["Sec-CH-UA-Mobile"] = identity["sec_ch_ua_mobile"]
request.headers["Sec-CH-UA-Platform"] = identity["sec_ch_ua_platform"]
Playwright / Patchright (patchright is a drop-in replacement in most workflows):
from patchright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(channel="chrome") # uses real Chrome binary
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36",
locale="en-US",
timezone_id="America/New_York",
viewport={"width": 1920, "height": 1080},
)
page = context.new_page()
page.goto("https://example.com")
nodriver for the hard targets:
import nodriver as uc
async def scrape():
browser = await uc.start()
page = await browser.get("https://example.com")
html = await page.get_content()
await browser.stop()
return html
nodriver drives system Chrome over raw CDP with no Playwright layer, which is exactly why it wins on automation-protocol fingerprinting checks.
Testing Your Rotation Before You Break Production
We test every rotation change against the same battery of checks before pushing to production scrapers:
Passive fingerprint checks (see what leaks):
- BrowserLeaks — comprehensive; look at TLS, WebGL, canvas, fonts, and mismatch flags
- amiunique.org — see how unique your composite fingerprint is
- pixelscan.net — specifically flags header/JS mismatches
- tls.peet.ws — dumps your JA3/JA4/HTTP2/Akamai fingerprints
- tls.browserleaks.com — TLS-specific
Active anti-bot checks:
- Attempt a Cloudflare Managed Challenge site
- Attempt a Cloudflare Turnstile page
- Attempt a DataDome-protected site
- Attempt a PerimeterX / HUMAN-protected site
The consistency checklist we run:
- HTTP
User-Agentheader matchesnavigator.userAgentexactly (yes, byte-for-byte) Sec-CH-UAvalues match the User-Agent major versionSec-CH-UA-Platformmatches the OS in the User-AgentSec-CH-UA-Mobilematches desktop vs. mobile UAnavigator.platformmatches OS in UAnavigator.languages[0]matchesAccept-Languagenavigator.maxTouchPoints > 0iff mobile UAscreen.width/heightin realistic range for device classwindow.devicePixelRatiomatches typical device (1 for desktop, 2–3 for high-DPI mobile)navigator.webdriverisfalseorundefinedIntl.DateTimeFormat().resolvedOptions().timeZonematches your IP geolocation- JA4 hash matches a real browser (test at tls.peet.ws)
- HTTP/2 SETTINGS frame ordering matches a real browser (harder to verify without pcap)
A/B testing rotation strategies. We regularly run two identical scrape jobs — one with the current config, one with the change — split across two identity pools of the same size against the same targets. Compare block rates, CAPTCHA rates, and average response times. Sometimes a “better” rotation strategy actually reduces success. Measure, don’t guess.
Common Failure Modes We Still See Until Right Now
| Failure | Root cause | What to fix |
|---|---|---|
| Everything blocked instantly | Outdated UA pool (Chrome <140), Python-shaped TLS | Refresh pool, switch to curl_cffi or a real browser |
| Works then dies at ~500 requests | IP-based rate limit; UA rotation is a red herring here | Add IP rotation, slow request rate |
| CAPTCHA on every request | TLS/JA4 mismatch, or navigator.webdriver=true | Switch to nodriver / patchright / real browser |
| Random blocks | Some UAs in pool are stale or malformed | Prune pool; test each UA individually |
| Mobile pages don’t render | Sending desktop Sec-CH-UA with mobile UA | Store & rotate Client Hints alongside UA |
| Sessions get partial data | UA changed mid-session | Enforce session-scoped identity |
| High bounce on cold IPs | IP reputation is bad; no amount of UA rotation compensates | Better proxy provider (residential > datacenter for hostile targets) |
Legal and Ethical Reality Check
None of the above makes unauthorized scraping legal. Rotating User-Agents is a technical practice, not a legal shield. The framework we operate under, and recommend to clients:
- Public data, legitimate business purpose, and rate-limited respectful behavior is the safest posture (hiQ v. LinkedIn and subsequent US case law).
- Bypassing technical access controls (CAPTCHAs designed to gate access, login walls, IP bans intended to enforce a decision) moves you into different legal territory — Computer Fraud and Abuse Act, DMCA §1201 in some interpretations.
- Personal data of EU/UK residents is subject to GDPR regardless of how you obtained it. Scraping public data that happens to be personal doesn’t launder the compliance obligations.
- Terms of Service violations are contractual, not criminal, in most jurisdictions — but they can be the predicate for a tortious interference or CFAA claim in the US if the site is hostile.
Practical: document your legitimate business purpose, keep rate limits low and respectful, provide unsubscribe/opt-out on any downstream outreach, and don’t scrape data you don’t actually need. If you’re operating at scale, or in a regulated industry, get counsel — this is not optional.
Frequently Asked Questions
How often should I rotate User-Agents in 2026?
At session boundaries. In our tests, per-request rotation was detected 3–4× more often than session-based rotation. A “session” is 5–15 minutes of activity, or one logical scraping task, whichever is smaller. Never mid-request.
Do I still need to rotate at all if User-Agent Reduction froze the string?
Yes. UA-Reduction froze the content of the string, not the fact that anti-bot systems check it. And you still need to rotate other headers (Accept-Language, Sec-CH-UA) and IP addresses regardless. You just don’t need to fake fine-grained version diversity anymore.
Do I need to rotate Sec-CH-UA too?
If you’re claiming to be Chrome or Edge, yes — the Client Hints must match your User-Agent’s major version and platform. If you’re rotating between Firefox and Chrome UAs, Firefox UAs should have no Sec-CH-UA headers at all (Firefox doesn’t emit them). Missing Client Hints on a Chrome UA is a fingerprint mismatch.
How many User-Agents should be in my pool?
We use 80–120 in production. Diminishing returns above ~150. The distribution and freshness matter far more than the count.
Will User-Agent rotation prevent all blocks?
No, and it’s not close. It’s one signal in a 10–15 signal composite. On modern anti-bot systems, TLS/JA4, JavaScript runtime consistency, and behavioral patterns matter more.
Is it legal to rotate User-Agents?
The rotation itself isn’t a legal question. What you scrape, from whom, and how you use it is. See the Legal and Ethical Reality Check section above.
Should I use headless browsers or HTTP libraries?
Depends on the target. HTTP libraries (with curl_cffi for TLS realism) are faster and cheaper for anything not gated by Turnstile-class anti-bot. Real browsers (nodriver > patchright > Playwright > Selenium in our stealth ranking) are necessary for anything past a Managed Challenge.
What happens if I use an outdated User-Agent?
In 2026, “outdated” means anything more than 3 major versions behind. Chrome auto-updates aggressively; Chrome 140 in July 2026 is a red flag. Refresh your pool at least monthly.
Can websites detect that I’m rotating User-Agents?
Yes, via inter-request signals like Cloudflare’s browser_ratio_1h and reqs_quantile_1h grouped by JA4 fingerprint. Rotating UAs while your JA4 hash stays the same is one of the loudest bot patterns you can produce.
What about googlebot User-Agents?
Don’t. Real Googlebot verifies via reverse DNS lookup. Anti-bot systems flag fake Googlebot claims aggressively.
What’s the single most impactful change I can make?
Fix TLS fingerprinting. Switching from requests to curl_cffi (for HTTP) or from vanilla Playwright to nodriver (for browser automation) will lift your success rate more than any UA pool improvement in 2026.
Conclusion: What Actually Matters Now
User-Agent rotation in 2026 is still worth doing, but it’s the beginning of a good identity strategy, not the substance of one. The heavy lifting has moved to layers most rotation guides still don’t talk about: TLS fingerprinting (JA4), automation-protocol shape (CDP handshake), inter-request signals over time, and JavaScript-level fingerprint consistency.
The best-performing stack we’ve measured this year is boring: modest, weighted, session-scoped User-Agent rotation, paired with client-hint coherence, real-browser TLS (via curl_cffi or an actual browser under nodriver), residential IP rotation aligned with session boundaries, and slow polite request rates. No single trick. A dozen mediocre defenses stacked correctly.
Our recommended 8-step implementation path:
- Audit current fingerprint at tls.peet.ws and browserleaks.com — see exactly what leaks.
- Build a weighted pool of 80–120 current User-Agents matched to 2026 market share, with
Sec-CH-UAand platform hints stored alongside. - Adopt session-scoped rotation — no per-request UA changes, ever.
- Fix TLS. Move
requestscode tocurl_cffi, or Playwright code topatchright/nodriver. - Coordinate identities — UA + Client Hints + IP + cookies + Referer + timezone all change together at session boundaries.
- Rate-limit to 1–2 requests/second/identity as a starting ceiling.
- Validate the whole request — run your scraper against BrowserLeaks and pixelscan.net end-to-end; fix mismatches before production.
- Monitor block/CAPTCHA rates per identity and per JA4 — prune whatever underperforms and refresh the pool at least monthly.
Anti-bot vendors publish more ML-driven behavioral scoring every quarter. Rotation strategies from 2023 don’t work now, and the ones that work in 2026 will need re-tuning by 2027. The one durable principle: be internally consistent. A scraper that behaves like a single believable user for the length of a session, then like a completely different believable user in the next session, is very hard to distinguish from actual people. That’s the bar. Everything else is technique.
