Below is a list of real, current user agent strings (verified July 2026), followed by how to rotate them without making your scraper more detectable than it was with the default one. That second part matters more than the list: a random user agent attached to a header set that doesn't match it is a stronger bot signal than python-requests/2.32.3, because no real browser has ever sent that combination. This guide covers the strings, the header bundles that must travel with them, what Client Hints (Sec-CH-UA) changed, and the point at which rotation stops helping at all.
Key Takeaways
- Chrome's User-Agent Reduction froze the minor/build/patch version to
0.0.0, the Android version to10, and the device model toK— a Chrome UA claimingAndroid 14; SM-S918Bis fabricated and trivially flagged - Rotate profiles, not strings: one user agent plus its exact
Accept,Accept-Language,Sec-CH-UA, andSec-Fetch-*headers, changed together - Firefox sends no
Sec-CH-UAheaders at all; a Firefox user agent arriving with them is an instant mismatch requestsspeaks only HTTP/1.1, so claiming to be Chrome 150 already contradicts the connection itself —httpx[http2]orcurl_cffifixes this, plainrequestscannot- Keep one user agent per session/IP pair for its lifetime; rotating on every request from one IP is a pattern real users never produce
- Against Cloudflare, DataDome, or PerimeterX, user agent rotation contributes roughly nothing — TLS/JA3 and browser fingerprints decide the outcome
What does a user agent string look like in 2026?
The User-Agent header is one line of text the client sends on every HTTP request, naming the browser, engine, and platform. The important 2026 change is that it carries far less information than tutorials written before 2023 assume.
Chrome's User-Agent Reduction completed rollout with Chrome 113 and permanently froze several fields:
| Field | Real value | What Chrome now sends |
| Minor/build/patch | 150.0.7871.187 | 150.0.0.0 |
| macOS version | 26.1 | 10_15_7 |
| Windows version | 11 | Windows NT 10.0; Win64; x64 |
| Android version | 16 | 10 |
| Device model | Pixel 10 Pro | K |
This is the single most useful freshness check on any user agent list you find: if it offers you Chrome strings with four-part version numbers, real Android versions, or actual phone models, those strings were valid in 2022 and are now anomalies. Most published lists still carry them.
Safari and Firefox freeze less but do freeze: Safari reports Intel Mac OS X 10_15_7 on every macOS 11+ machine, and Firefox has capped the reported macOS version at 10.15 since Firefox 87, reporting Apple Silicon Macs as Intel.
User agent list for web scraping (verified July 2026)
Chrome Stable was 150, Firefox 153, and Safari 26.0 when this list was checked. The version numbers are the only part that decays — the surrounding structure is frozen, so bumping the major number keeps a string valid for the next release.
Chrome 150 — desktop
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Chrome 150 — mobile (note the frozen Android 10; K)
Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36
Edge 150 — Windows
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.0.0
Firefox 153
Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:153.0) Gecko/20100101 Firefox/153.0
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:153.0) Gecko/20100101 Firefox/153.0
Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0
Mozilla/5.0 (Android 10; Mobile; rv:153.0) Gecko/153.0 Firefox/153.0
Safari 26
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15
Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1
Mozilla/5.0 (iPad; CPU OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1
A hardcoded list decays about one major version every four weeks. Two ways to stay current without maintaining it by hand: read navigator.userAgent out of a real browser you already run for JavaScript-heavy pages (see the headless browser guide), or let your scraping API supply the browser identity so the list is somebody else's problem.
The fake-useragent package is the common shortcut here. It is fine for what it does — sampling a string from a bundled dataset — but it hands you a bare user agent with no matching headers, and its dataset includes long-tail browsers you do not want to impersonate. Used naively it produces exactly the mismatch described next.
Why a random user agent is worse than no rotation at all
Browsers are consistent. Chrome 150 on Windows always sends the same Accept, the same Accept-Encoding, the same Sec-Fetch-* set, and a Sec-CH-UA whose version agrees with the user agent. Detection systems do not evaluate the user agent in isolation; they check whether the whole request is internally coherent.
So this is a bot signature, not a disguise:
# WRONG: Chrome UA, requests' default everything else
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/150.0.0.0 Safari/537.36"}
requests.get(url, headers=headers)
That request claims to be Chrome 150 while sending Accept: */*, Accept-Encoding: gzip, deflate, no Sec-CH-UA, no Sec-Fetch-*, and negotiating HTTP/1.1. Chrome does none of those things. A default python-requests/2.32.3 user agent is at least honest and is only blocked where automation is blocked outright; the half-disguise is affirmatively suspicious.
The rule: rotate profiles, not strings. A profile is a user agent plus every header that browser sends with it, and they change together or not at all.
What Client Hints changed
User-Agent Client Hints are the mechanism replacing the information Chrome removed from the user agent. Chromium browsers send three headers on every request without being asked:
| Header | Example | Sent by |
Sec-CH-UA | "Google Chrome";v="150", "Chromium";v="150", "Not.A/Brand";v="24" | Chromium only |
Sec-CH-UA-Mobile | ?0 | Chromium only |
Sec-CH-UA-Platform | "Windows" | Chromium only |
Higher-entropy hints — Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Model — are only sent after the server requests them via an Accept-CH response header. If a site sends Accept-CH and your scraper never responds with the hint on the next request, that silence is itself measurable.
Three practical consequences most scraping content still misses:
- A Chrome user agent with no
Sec-CH-UAis contradictory. Real Chrome sends it on every secure-context request. - A Firefox or Safari user agent with
Sec-CH-UAis equally contradictory. Neither browser implements Client Hints. Copying one "browser header template" across all your profiles creates this bug. - The versions must agree.
Sec-CH-UAclaimingv="150"alongside aChrome/131.0.0.0user agent is a mismatch a single regex catches.
The "Not.A/Brand" entry is intentional GREASE — Chrome varies its position, name punctuation, and version specifically so servers can't hardcode the list. Copying one fixed value forever is a weak signal, but it is a signal.
How to rotate user agents in Python
Keep complete profiles, pick one per session, and hold it for that session's life:
import random
import requests
CHROME_150_WINDOWS = {
"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,application/signed-exchange;v=b3;q=0.7",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br, zstd",
"Sec-CH-UA": '"Google Chrome";v="150", "Chromium";v="150", "Not.A/Brand";v="24"',
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"Priority": "u=0, i",
}
FIREFOX_153_MAC = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:153.0) "
"Gecko/20100101 Firefox/153.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br, zstd",
# No Sec-CH-UA headers — Firefox does not implement Client Hints
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"Priority": "u=0, i",
}
PROFILES = [CHROME_150_WINDOWS, FIREFOX_153_MAC]
def new_session():
"""One browser identity per session, held for the session's lifetime."""
session = requests.Session()
session.headers.clear() # drop requests' own defaults, which leak
session.headers.update(random.choice(PROFILES))
return session
session = new_session()
for url in urls:
response = session.get(url, timeout=15)
session.headers.clear() is the line people miss. Without it requests merges its own Accept: */* and User-Agent defaults into every request, and on Python 3.7+ the insertion order also determines the order headers go out on the wire — which browsers keep stable and detection systems do check.
One limit no header dictionary can fix: requests is HTTP/1.1 only. Chrome 150 uses HTTP/2 or HTTP/3 for essentially every site it visits, so the protocol contradicts the user agent before any header is read. Use httpx with HTTP/2 enabled, or curl_cffi, which matches Chrome's TLS handshake as well:
from curl_cffi import requests as cffi_requests
# Matches Chrome's JA3/TLS fingerprint and HTTP/2 settings, not just headers
response = cffi_requests.get("https://example.com", impersonate="chrome")
Node.js
Node's built-in fetch (undici) sends headers in the order given and speaks HTTP/1.1 by default:
const PROFILES = [
{
'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,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'en-US,en;q=0.9',
'Sec-CH-UA': '"Google Chrome";v="150", "Chromium";v="150", "Not.A/Brand";v="24"',
'Sec-CH-UA-Mobile': '?0',
'Sec-CH-UA-Platform': '"Windows"',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Upgrade-Insecure-Requests': '1',
},
];
const profile = PROFILES[Math.floor(Math.random() * PROFILES.length)];
const response = await fetch(url, { headers: profile });
How often should you rotate?
Bind the identity to the session and the IP, not to the request:
- One user agent per session, for the whole session — a real browser never changes mid-visit
- One user agent per IP, consistently — five browsers behind one residential IP in a minute is the pattern rotation is supposed to hide
- Never per-request from a single IP — this is the most common mistake and it manufactures a signature
- Weight toward Chrome. Chrome and Chromium derivatives are the large majority of real desktop traffic; a fleet that is one-third Firefox does not resemble any real audience
Does user agent rotation still work?
Honestly: on its own, only against the weakest checks. It is table stakes, not a strategy. Here is what each layer actually defeats:
| Technique | Defeats | Does not defeat |
| Setting a real user agent | Naive UA blocklists, 403 on python-requests | Anything else |
| Full consistent header profile | Header-mismatch heuristics, WAF rules | TLS and browser fingerprinting |
TLS/JA3 matching (curl_cffi) | JA3/JA4 fingerprint checks | JavaScript challenges, behavioral analysis |
| Residential/mobile IPs | IP reputation and ASN blocks | Everything client-side |
| Real browser + stealth patches | Canvas/WebGL/CDP fingerprinting, JS challenges | Behavioral and account-level signals |
A decision rule that holds up in practice: start with a correct header profile over datacenter proxies. If you see 403s or challenge pages, the problem is almost never the user agent — move to residential proxies and TLS matching. If challenge pages persist, you need a real browser, because the site is running JavaScript you have to execute. The proxy types guide covers where each tier is worth its cost.
Sites behind Cloudflare Bot Management, DataDome, or PerimeterX fingerprint the TLS handshake before your headers are even parsed. No user agent list changes that outcome.
Rotating headers with the WebScraping.AI API
If you would rather not maintain profiles at all, the API renders pages in a real browser and manages the identity for you. Pass your own headers when you need specific ones — cookies, Referer, a language — with the nested headers[Name] syntax:
import requests
response = requests.get(
"https://api.webscraping.ai/html",
params={
"api_key": API_KEY,
"url": "https://example.com/products",
"proxy": "residential",
"country": "us",
"headers[Referer]": "https://www.google.com/",
"headers[Accept-Language]": "en-US,en;q=0.9",
},
timeout=60,
)
print(response.text)
Use device to switch between the desktop and mobile browser profiles — useful when a site serves a lighter, easier-to-parse page to phones:
params = {
"api_key": API_KEY,
"url": "https://example.com/products",
"device": "mobile",
"proxy": "residential",
}
Proxy tiers map to the escalation ladder above: datacenter (1 credit without JS rendering, 5 with) for sites that don't care, residential (10 / 25) when datacenter IPs get blocked, and stealth (50) for sites running commercial anti-bot systems. AI extraction adds 5 credits. Failed requests are never charged, so probing which tier a site actually requires costs nothing. Full parameter reference is in the API docs.
Frequently Asked Questions
What is the best user agent for web scraping?
The most recent Chrome release on Windows, because it is the single most common real browser identity and blends into the largest crowd:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
It only works paired with Chrome's matching Accept, Sec-CH-UA, and Sec-Fetch-* headers. Sent alone from an HTTP client, it is worse than no user agent at all.
Do I need to send Sec-CH-UA headers when scraping?
If you claim to be Chrome or Edge, yes — real Chromium sends them on every secure-context request, and their absence contradicts your user agent. If you claim to be Firefox or Safari, you must not send them, because those browsers do not implement Client Hints.
Is user agent rotation the same as proxy rotation?
No, and they solve different problems. User agent rotation varies the client identity in your headers; proxy rotation varies the IP the request comes from. Rate limits and IP reputation are enforced on the address, so proxy rotation is what stops volume-based blocks. Rotating user agents from one IP does not raise your request ceiling.
Is fake-useragent still safe to use?
It works for generating strings, but it does not solve the actual problem: it returns a bare user agent with no matching header set, and its dataset includes browsers with negligible real-world share. If you use it, pair each string with a hand-built header profile — at which point maintaining a few explicit profiles is simpler.
Why does my scraper get blocked even with a valid user agent?
Because the check that blocked you was probably not looking at the user agent. The usual causes, in order: IP reputation (fix with better proxies), TLS/JA3 fingerprint (fix with curl_cffi or a real browser), a JavaScript challenge (fix with a headless browser or a rendering API), and request timing. Work down that list before editing your user agent list again.
Is scraping with a rotated user agent legal?
Sending a User-Agent header is not itself a legal question — every HTTP client sends one. What matters is the data, the site's terms, and your jurisdiction. See is web scraping legal for the actual considerations, and don't treat rotation as a way around a site that has told you not to scrape it.
Correct headers get you past naive checks; past that, the identity has to be real all the way down to the TLS handshake. WebScraping.AI handles browser rendering, header consistency, and proxy rotation behind a single API call — 2,000 free credits per month, no credit card. It's the same engine behind use cases like price monitoring, where the target sites do run bot detection.