How HTTP Works

Request header

> User-Agent: Mozilla/5.0 (X11; Linux x86_64) Firefox/130.0

User-Agent Header

Learn how the User-Agent header identifies the client software, browser, or application making HTTP requests. Understand user agent strings and best practices.

Reviewed 5 min readbeginner2 sourcesTry itMarkdown
Direction
Request
Category
Client info
Spec
RFC 9110 §10.1.5
JS can set it
Yes
CORS-safelisted
No: may trigger a preflight
On this page

TL;DR: User-Agent is the request header where a client names its software, such as curl/8.5.0 or a browser string. The client decides what goes in it, so it can be missing, trimmed down, or faked. Use it to debug and spot trends, not to detect features, identify users or verify crawlers.

What is User-Agent?

RFC 9110 §10.1.5 defines it as a sequence of product identifiers with optional comments. Clients should send one unless configured otherwise, and servers must cope when it’s missing.

How User-Agent Works

A simple request (this and the other outputs on this page are trimmed examples):

GET /health HTTP/1.1
Host: example.com
User-Agent: ExampleMonitor/1.0

Any HTTP client can send exactly that value. The server is reading a self-description, not a credential.

Common User-Agent Strings

Browser strings pile up compatibility tokens from each other. Seeing Safari doesn’t mean you’re talking to Safari, because Chromium-based browsers include it too. MDN describes the parsing pitfalls.

To see what your installed curl actually sends, ask it rather than copying a version string from a blog post:

curl -v -o /dev/null https://example.com/

The verbose output shows the outgoing User-Agent line. Skip -v on requests that carry credentials, since it prints those too.

TokenParsing gotcha
Safari/Can appear in Chromium-derived browser strings too
Chrome/Can appear alongside another browser’s identifying token
Edg/Identifies desktop Chromium-based Edge in its UA format
Firefox/Product token; still does not establish feature support
Mozilla/5.0Shared compatibility prefix, not proof of Firefox

Treat these as clues when you’re reading a captured request, not as a browser parser. Order matters: match Chrome/ before looking for Edg/ and you’ll file Edge under Chrome. Mobile formats and reduced strings make a handful of substring checks harder to keep correct over time.

Server-Side Detection

Reading the header for diagnostics is fine. Just handle the case where it’s absent:

app.get('/client-info', (req, res) => {
  res.json({ userAgent: req.get('User-Agent') || null })
})

req.get() reads a request header in Express. Keep a route like this narrow so it echoes only this one value, never other headers or secrets.

For logs, record the path and the supplied value together, with an explicit null when the field is missing:

app.use((req, res, next) => {
  console.log(JSON.stringify({
    method: req.method,
    path: req.path,
    userAgent: req.get('User-Agent') || null
  }))
  next()
})

This middleware only records what came in. Bot classification and access decisions belong somewhere else. It logs req.path rather than the full URL because query strings often carry sensitive values.

Content Adaptation

When you need to know what a browser can do, test for the feature on the client instead of mapping browser names to a support table:

if ('IntersectionObserver' in window) {
  // Install observation-based enhancements.
}
if (CSS.supports('display', 'grid')) {
  // Enable the layout that requires grid.
}

A feature being present doesn’t mean every behavior or subfeature you rely on works, so exercise the real code path in your tests.

If your server genuinely returns different content based on this header, send Vary: User-Agent so a shared cache won’t hand one client’s variant to another. The trade-off is that the cache now splits its entries by every distinct string it sees. When you control both client and server, a narrow capability signal of your own is much easier to reason about than a full browser string. RFC 9111 §4.1 defines how Vary selects stored responses.

Browsers have also been reducing their strings, cutting some OS, device and version detail. MDN’s reduction guide lists the affected fields. Old examples showing exact phone models or patch versions no longer reflect what current browsers send.

Security and Analytics

Anyone can put Googlebot in the header, so the substring tells you nothing about who’s calling. Google documents reverse/forward DNS and published IP ranges for verification. Likewise, blocking a tool by name only stops callers who don’t bother changing the string.

It’s also a poor user identifier. Lots of clients send the same value, and one client can change its value whenever it likes.

Claiming to be a crawler takes one command:

curl -A 'Googlebot' https://example.com/

To verify Google’s common crawlers, run a reverse DNS lookup on the source IP, check that the hostname ends in one of Google’s documented domain suffixes, then resolve that hostname forward and confirm it returns the original IP. A hostname that merely contains googlebot somewhere fails the suffix check. The verification guide also covers IP-range checks. Follow the documented procedure for the crawler category you care about.

Client-Side Customization

Set an explicit identifier from the command line:

curl -A 'ExampleMonitor/1.0' https://example.com/health

-A sets the value. To test what happens with no header at all, use curl -H 'User-Agent:'.

In browsers, User-Agent isn’t on the Fetch Standard’s forbidden-name list, yet MDN notes Chrome still drops a supplied value from Fetch requests. Check the outgoing request in DevTools to see whether your value actually went out.

Getting User-Agent right

User-Agent Client Hints expose structured browser information in fields such as Sec-CH-UA. They’re tidier than parsing a string, but they’re still client-supplied and say nothing trustworthy about the caller. MDN documents Sec-CH-UA and its limited availability. Not every browser sends them, so keep a fallback for clients that don’t.

Where a browser exposes the JavaScript API, read the structured values behind a presence check:

if (navigator.userAgentData) {
  console.log(navigator.userAgentData.brands)
  console.log(navigator.userAgentData.mobile)
} else {
  console.log('User-Agent Client Hints API unavailable')
}

navigator.userAgentData needs a secure context in browsers that support it. The mobile hint describes the device class. It tells you nothing about viewport width or touch support, so use media queries when the question is whether your layout fits the window.

Testing User-Agent

In a browser, compare navigator.userAgent in the console with Network → Request Headers. Against the Express diagnostic route:

curl -A 'ExampleMonitor/1.0' http://localhost:3000/client-info
curl -H 'User-Agent:' http://localhost:3000/client-info

The first echoes ExampleMonitor/1.0. The second returns null.

Common Patterns

For a client you own, send an application/version token so you can trace problems back to a release. If your API has versions, select them through an explicit part of the API contract, not by guessing from a browser string.

A Node client can do this with fetch. Save it as an ES module:

const response = await fetch('https://api.example.com/report', {
  headers: { 'User-Agent': 'ReportExporter/1.0' }
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
console.log(await response.text())

fetch() is available in Node, and this is a server-side client, not a way to change a browser’s identity. Replace ReportExporter/1.0 with your real application name and version, and keep credentials out of the value.

Frequently asked questions

What is the User-Agent header?

User-Agent identifies the client software making the request. It typically includes browser name, version, operating system, and device information.

Should I use User-Agent for feature detection?

No, use feature detection instead. User-Agent strings are unreliable, often spoofed, and browser sniffing leads to maintenance problems. Test for features directly.

Why are User-Agent strings so long?

Historical compatibility. Browsers add tokens like Mozilla/5.0 to appear compatible with older servers. The format evolved chaotically over decades.

What is User-Agent Client Hints?

User-Agent Client Hints provide structured browser information in fields such as Sec-CH-UA. Support is limited, and these client-supplied values do not authenticate the caller.

Sources

  1. MDN Web Docs: User-Agentdeveloper.mozilla.org
  2. RFC 9110: User-Agentrfc-editor.org

Keep going

Browse /search