--- title: "Random user agent strings for scraping (October 2026 list)" category: "guides" url: https://keirolabs.cloud/blog/random-user-agent --- ## The 60-second version **A user agent is the string your browser sends to say what it is. Scrapers rotate them so their traffic looks like many ordinary browsers instead of one script.** Below is a current list (checked October 27, 2026, against Google's and Mozilla's release feeds), copy-paste rotation code for Python and Node, and the part most guides skip: why rotating the string alone stopped working. - **Current stable versions:** Chrome 157 on Windows and Android, Chrome 156 still rolling out on macOS, Firefox 159. - **Chrome reports `.0.0.0` for the minor version** since its user-agent reduction. A string with a full build number like `157.0.8103.15` looks wrong, not more real. - **The string is not the fingerprint.** Bot detection also checks client hints, header order and the TLS handshake. A Chrome string on a Python TLS handshake is a known mismatch. ## Current user agent strings (October 2026) ### Desktop ```text Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36 Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36 Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36 Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:159.0) Gecko/20100101 Firefox/159.0 Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:159.0) Gecko/20100101 Firefox/159.0 Mozilla/5.0 (X11; Linux x86_64; rv:159.0) Gecko/20100101 Firefox/159.0 ``` ### Mobile ```text Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Mobile Safari/537.36 Mozilla/5.0 (Android 15; Mobile; rv:159.0) Gecko/159.0 Firefox/159.0 ``` **Why every Windows string says "Windows NT 10.0" and every Mac says "10_15_7":** browsers froze those values. Windows 11 and every recent macOS still report them, so a string claiming "Windows NT 11.0" or "Mac OS X 15_0" is fake by definition. Android Chrome likewise reports `Android 10; K` regardless of the real device. Versions move fast now: Chrome and Firefox both switched to two-week releases in September 2026. Refresh your list monthly, or read the current versions from `https://versionhistory.googleapis.com/v1/chrome/platforms/win/channels/stable/versions` and `https://product-details.mozilla.org/1.0/firefox_versions.json` at startup. ## How to read a user agent string | Part | Example | Meaning | |---|---|---| | `Mozilla/5.0` | always present | Historical compatibility token | | Platform | `(Windows NT 10.0; Win64; x64)` | Operating system and architecture | | Engine | `AppleWebKit/537.36 (KHTML, like Gecko)` | Rendering engine; frozen in Chrome | | Browser | `Chrome/157.0.0.0` | Browser and major version | | Suffix | `Safari/537.36`, `Mobile` | Compatibility token; `Mobile` on phones | ## Rotate user agents in Python ```python import random, requests USER_AGENTS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:159.0) Gecko/20100101 Firefox/159.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:159.0) Gecko/20100101 Firefox/159.0", ] def get(url: str) -> requests.Response: headers = { "User-Agent": random.choice(USER_AGENTS), "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", } return requests.get(url, headers=headers, timeout=30) print(get("https://httpbin.org/user-agent").json()) ``` **Keep one user agent per session.** A real visitor doesn't switch from Firefox on Linux to Chrome on Mac between two clicks. Pick a string when you open a `requests.Session` or a proxy connection, and keep it for that session. ## Rotate user agents in Node.js ```js const USER_AGENTS = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:159.0) Gecko/20100101 Firefox/159.0", ]; const pick = (a) => a[Math.floor(Math.random() * a.length)]; const r = await fetch("https://httpbin.org/user-agent", { headers: { "User-Agent": pick(USER_AGENTS), "Accept-Language": "en-US,en;q=0.9" }, }); console.log(await r.json()); ``` ## Why rotating the user agent isn't enough anymore **Modern bot detection checks whether everything about the request agrees with the user agent.** Four checks catch most rotated-UA scrapers: 1. **Client hints.** Real Chrome sends `Sec-CH-UA`, `Sec-CH-UA-Mobile` and `Sec-CH-UA-Platform` headers that must match the string. Firefox doesn't send them. A Chrome string with no client hints, or with Firefox's header set, stands out. 2. **TLS fingerprint.** The TLS handshake of Python's `requests` (and of Node's `fetch`) differs from Chrome's. Services such as Cloudflare compare the handshake fingerprint (JA3/JA4) with the claimed browser. `curl_cffi` in Python can impersonate Chrome's handshake: `requests.get(url, impersonate="chrome")`. 3. **Header order and HTTP/2 settings.** Browsers send headers in a fixed order and negotiate HTTP/2 in recognizable ways. 4. **JavaScript checks.** Challenge pages run scripts that read `navigator.userAgent`, `navigator.webdriver` and dozens of other properties. Only a real browser passes these, which is why headless Playwright with a matching user agent works where `requests` doesn't. **So the honest order of escalation is:** a current, consistent user agent → `curl_cffi` impersonation → a real browser with Playwright ([guide](/blog/playwright-scraping)) → a scraping API that handles all of it. ## Skip the fingerprint game If you are rotating user agents to get page content for an app or an LLM, the fingerprinting work isn't your product. [browserfoundry by Keirolabs](https://www.browserfoundry.cloud/?utm_source=keirolabs&utm_medium=blog&utm_campaign=random-user-agent) `/extract` fetches the page, steps up to a full browser render when a site blocks simple requests, and returns clean markdown: ```bash curl -X POST https://api.keirolabs.cloud/api/v2/extract \ -H "Authorization: Bearer $KEIRO_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/pricing"}' ``` 2 credits a page, about $0.005 on the Essential plan, with 750 free credits at signup and no card. The full Python walkthrough is in [web scraping with Python](/blog/web-scraping-python).