Request header · non-standard
> X-Request-ID: 7f3c1d0a9b2e4f5c8d6e1a2b3c4d5e6fX-Request-ID Header: Correlation IDs Across Services
X-Request-ID tags one request so you can find it in every log. nginx $request_id, Heroku behavior, propagation between services, and traceparent.
- Direction
- Request
- Category
- Diagnostics
- Status
- Non-standard: De-facto correlation ID; the standard form is traceparent
- JS can set it
- Yes
- CORS-safelisted
- No: may trigger a preflight
On this page
TL;DR:
X-Request-IDis an opaque per-request identifier, set at the edge (or by the client) and then copied into every downstream call and log line so one request can be followed across services. It is a convention, not a standard; the standard equivalent for tracing istraceparent.
How it flows
GET /orders/42 HTTP/1.1
Host: api.example.com
X-Request-ID: 7f3c1d0a9b2e4f5c8d6e1a2b3c4d5e6f
HTTP/1.1 200 OK
X-Request-ID: 7f3c1d0a9b2e4f5c8d6e1a2b3c4d5e6f
Content-Type: application/json
The rule is simple: use the ID if one arrived and passes validation, otherwise mint one; attach it to every log line; forward it on every outbound call; return it in the response.
Where it comes from
- nginx can mint one.
$request_idis a unique identifier generated from 16 random bytes, in hexadecimal (since 1.11.0). nginx does not automatically add it to headers; you do that:
log_format main '$remote_addr "$request" $status req_id=$request_id';
access_log /var/log/nginx/access.log main;
location / {
proxy_set_header X-Request-ID $request_id;
add_header X-Request-ID $request_id always;
proxy_pass http://app;
}
- Heroku router generates a request ID for each request and passes it to the app as
X-Request-ID. A client can supply its own, 20 to 200 characters from ASCII letters, digits and+ / = -; invalid values are ignored and replaced. The ID appears asrequest_id=in router logs, which is how you join router lines (including H13 errors) to application logs. - Frameworks: Rails exposes it via
config.log_tags = [:request_id], and Django needs middleware such asdjango-log-request-id. Other platforms and load balancers vary in whether they set the header at all, so check a real response before relying on it.
Propagation in application code
Node with an AsyncLocalStorage so the ID follows async calls without being passed around:
import { AsyncLocalStorage } from 'node:async_hooks'
import { randomUUID } from 'node:crypto'
const als = new AsyncLocalStorage()
const VALID = /^[A-Za-z0-9+/=_-]{16,200}$/
export function requestId(req, res, next) {
const incoming = req.get('x-request-id')
const id = incoming && VALID.test(incoming) ? incoming : randomUUID()
res.set('X-Request-ID', id)
als.run({ id }, next)
}
export const currentId = () => als.getStore()?.id
// outbound call
await fetch('http://billing.internal/charge', {
headers: { 'X-Request-ID': currentId() }
})
Log it as a structured field ("request_id": "..."), not inside the message text, so it is searchable.
Gotchas
- Validate client values. An unbounded client string goes into your logs verbatim. Restrict length and charset, or discard it and keep it in a separate
client_request_idfield. - Retries share an ID, new requests do not. A retry by the client is the same logical request, so reusing the ID is fine; a fan-out call to five services all carry the parent’s ID.
- CDNs and gateways may overwrite it. If the ID you see in app logs never matches what the client sent, a proxy minted a new one. Compare with a direct
curlto the origin. - Not for secrets or users. Do not derive it from a user id or session, and do not treat it as unguessable authentication.
- Case. HTTP header names are case-insensitive. Node lowercases them (
req.headers['x-request-id']).
Alternative: W3C traceparent
When you already run OpenTelemetry or an APM, prefer traceparent: the trace id is parsed by tools, spans link into a tree, and vendors interoperate. Many teams keep both, putting the trace id in logs next to the request ID so non-engineers have a short value to quote.
Verify
curl -si https://api.example.com/health -H 'X-Request-ID: 7f3c1d0a9b2e4f5c8d6e1a2b3c4d5e6f' | grep -i x-request-id
curl -si https://api.example.com/health | grep -i x-request-id # server-generated?
Related
Frequently asked questions
Is X-Request-ID a standard header?
No. It is a de-facto convention with no RFC and no IANA registration for that exact name, which is why proxies, frameworks and cloud platforms each choose their own rules for it. For standardized cross-service tracing use the W3C traceparent header.
What does the nginx $request_id variable contain?
A unique identifier generated from 16 random bytes in hexadecimal, so 32 hex characters. It exists since nginx 1.11.0 and is generated per request, independent of any incoming header.
Should I trust an X-Request-ID sent by the client?
Accept it only after validating length and character set, or generate your own and log the client value separately. A client-supplied ID can be used for log injection or to collide with another request. Heroku, for example, only honors client IDs of 20 to 200 characters from a limited ASCII set and replaces anything else.
What is the difference between X-Request-ID and traceparent?
X-Request-ID is a free-form opaque string whose meaning is local to whoever set it. traceparent has a fixed format with a 16-byte trace id and 8-byte parent span id, is understood by OpenTelemetry and most APM vendors, and carries a sampling flag. Use traceparent for distributed tracing and X-Request-ID as the human-friendly handle in logs and error pages.
Should I return X-Request-ID in the response?
Yes. Echoing it in the response and in error bodies lets a user or support engineer quote one ID that finds the whole request in your logs.
Sources
Related
X-Forwarded-For
X-Forwarded-For carries the client IP through proxies and load balancers, but clients can forge it. Trust it safely in nginx, Express, Cloudflare and AWS.
Server-Timing Header
Learn how the Server-Timing header communicates server-side performance metrics to browsers. Analyze backend timing, database queries, and optimize performance.
Via Header
Learn how the Via header tracks the path of HTTP requests through proxies and gateways. Debug routing issues and understand your network infrastructure.
X-Response-Time
Learn how the X-Response-Time header indicates server processing time in milliseconds. Useful for performance monitoring and debugging slow requests.