Performance-Timing: Server-Timing, Timing-Allow-Origin and Performance APIs
There is no Performance-Timing HTTP header. Use Server-Timing, Timing-Allow-Origin, the Navigation and Resource Timing APIs, TTFB and INP instead.
- Direction
- Response
- Category
- Diagnostics
- Status
- Not a real header: Not a registered header; use Server-Timing
On this page
TL;DR: There is no
Performance-TimingHTTP header. To report backend durations sendServer-Timing, to let other origins read timings sendTiming-Allow-Origin, and to measure in the browser usePerformanceNavigationTimingandPerformanceObserver(Core Web Vitals now use INP, not FID).
This URL is kept because people search for it. The name usually comes from one of three things: a custom header invented by an in-house proxy, the old window.performance.timing JavaScript object, or confusion between the Performance APIs and HTTP headers. Here is what each one maps to.
What to use instead
| You want | Use |
|---|---|
| Backend durations visible in DevTools and JavaScript | Server-Timing response header |
| Third-party scripts, images or API calls to expose detailed timings | Timing-Allow-Origin response header |
| Page load phases in JavaScript | PerformanceNavigationTiming |
| Per-request phases for subresources | PerformanceResourceTiming |
| Real-user vitals | PerformanceObserver for LCP, CLS, INP |
Server-Timing
HTTP/1.1 200 OK
Server-Timing: db;dur=47.2;desc="Postgres", cache;desc="miss", app;dur=12.1, total;dur=83.5
Each metric has a name and optional dur (milliseconds) and desc. Chrome DevTools shows them in the Network panel under Timing, and JavaScript reads them from entry.serverTiming. Use short names, and consider omitting the header in production for unauthenticated users, because it exposes backend structure. CDNs commonly add their own entries (for example Cloudflare’s cfCacheStatus, or cdn-cache; desc=HIT on Fastly). Details on Server-Timing.
Express example:
app.use((req, res, next) => {
const start = process.hrtime.bigint()
const onHeaders = require('on-headers')
onHeaders(res, () => {
const ms = Number(process.hrtime.bigint() - start) / 1e6
res.setHeader('Server-Timing', `app;dur=${ms.toFixed(1)}`)
})
next()
})
nginx can expose upstream time with add_header Server-Timing "upstream;dur=$upstream_response_time" always;, but this value is in seconds, not milliseconds, so convert it or rename the metric.
Timing-Allow-Origin
For cross-origin resources, browsers hide most Resource Timing details (DNS, connect, request and response start, sizes) unless the response allows it:
Timing-Allow-Origin: https://www.example.com
Timing-Allow-Origin: * exposes timings to any page and is common on CDN assets. It is independent of CORS: it does not let pages read the body. See Timing-Allow-Origin.
Navigation Timing in JavaScript
performance.timing (Level 1) is deprecated. Use the Level 2 entry:
const [nav] = performance.getEntriesByType('navigation')
const ttfb = nav.responseStart // ms since navigation start
const serverTime = nav.responseStart - nav.requestStart
const dns = nav.domainLookupEnd - nav.domainLookupStart
const tcpTls = nav.connectEnd - nav.connectStart
const download = nav.responseEnd - nav.responseStart
console.log({ ttfb, serverTime, dns, tcpTls, download, protocol: nav.nextHopProtocol })
console.log(nav.serverTiming) // parsed Server-Timing entries
Resource Timing for subresources:
performance.getEntriesByType('resource')
.filter((e) => e.transferSize === 0 && e.decodedBodySize > 0) // from cache, or TAO missing
.forEach((e) => console.log(e.name, e.duration))
A transferSize of 0 on a cross-origin entry means either a cache hit or a missing Timing-Allow-Origin.
Core Web Vitals
Observe vitals with PerformanceObserver, or use the web-vitals library:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) console.log('LCP', entry.startTime, entry.element)
}).observe({ type: 'largest-contentful-paint', buffered: true })
The current Core Web Vitals are LCP (loading, good at 2.5 s or less), INP (responsiveness, good at 200 ms or less) and CLS (visual stability, good at 0.1 or less). INP replaced FID in March 2024. TTFB is a diagnostic, not a Core Web Vital, but a slow TTFB caps your LCP. HTTP-level fixes for it: caching (Cache-Control), early hints (103), compression, and HTTP/2 or HTTP/3.
Check it with curl
curl -s -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://example.com/
curl -sI https://example.com/ | grep -iE '^(server-timing|timing-allow-origin)'
Related
Frequently asked questions
Is there a Performance-Timing HTTP header?
No. It is not defined by any IETF, W3C or WHATWG specification and is not registered with IANA. Some in-house systems use the name for custom durations, but browsers do nothing with it. The standard header for backend durations is Server-Timing.
What is performance.timing and is it deprecated?
performance.timing is the Navigation Timing Level 1 JavaScript object, not an HTTP header. It is deprecated and replaced by PerformanceNavigationTiming, available through performance.getEntriesByType("navigation"), which uses high-resolution timestamps relative to the navigation start.
How do I measure TTFB in the browser?
Read the navigation entry: performance.getEntriesByType("navigation")[0].responseStart is the time from navigation start to the first byte of the response. It includes redirects, DNS, TCP/TLS and server think time; subtract requestStart for server time alone.
Why is FID no longer a Core Web Vital?
Interaction to Next Paint (INP) replaced First Input Delay as a Core Web Vital in March 2024. INP measures the latency of all interactions across the visit, not just input delay on the first one. A good INP is 200 ms or less at the 75th percentile.
Why are cross-origin resource timings zero?
Detailed Resource Timing fields such as transferSize and the DNS and connect phases are zeroed for cross-origin responses unless they send Timing-Allow-Origin. Add the header on the third-party resource, or on your CDN, to expose them.
Can the browser read my Server-Timing values?
Yes, via the serverTiming property of navigation and resource entries. For cross-origin resources the values are only exposed when Timing-Allow-Origin allows the page. The values are visible to every user, so do not put secrets or exact internal topology in them.
Sources
Related
Access-Control-Max-Age Header
Learn how Access-Control-Max-Age specifies how long browsers can cache CORS preflight results. Reduce preflight requests and improve cross-origin performance.
Content-Encoding
Learn how Content-Encoding specifies compression algorithms (gzip, br, deflate) used to encode response bodies. Reduce bandwidth and improve load times.
Early-Hints
Learn how 103 Early Hints allows servers to send preload hints while preparing the main response. Improve page load performance with early resource loading.
Link Header
Learn how the Link header provides resource hints and enables preloading of CSS, fonts, and scripts to improve page load performance and user experience.