HttpFixerBlogHeaders → Timing-Allow-Origin — Why Your RUM Tool Shows Zero Timing Data
Headers

Timing-Allow-Origin — Why Your RUM Tool Shows Zero Timing Data

Updated April 2026

Reading this? Verify your fix in real-time. Audit your performance headers → EdgeFix

Your RUM (Real User Monitoring) tool shows that your CDN images load in 0ms. That is not correct — it is the browser hiding timing data from your JavaScript because the resources are cross-origin. Timing-Allow-Origin fixes it.

Why this happens

The Resource Timing API (performance.getEntriesByType("resource")) returns timing data for all resources a page loads. But for cross-origin resources, the browser zeroes out most fields to prevent timing side-channel attacks. Your RUM script cannot see how long your CDN took to respond.

// Without Timing-Allow-Origin:
const entries = performance.getEntriesByType("resource");
const cdnEntry = entries.find(e => e.name.includes("cdn.yourapp.com"));
console.log(cdnEntry.responseEnd - cdnEntry.startTime); // → 0 (blocked)

// With Timing-Allow-Origin:
console.log(cdnEntry.responseEnd - cdnEntry.startTime); // → 234.5ms (real data)

Add Timing-Allow-Origin to your CDN or asset server

# Allow your origin to read timing data
Timing-Allow-Origin: https://yourapp.com

# Allow all origins (safe for public CDN assets)
Timing-Allow-Origin: *

Config by location

Nginx — CDN or asset server

location ~* \.(js|css|woff2|png|jpg|webp|svg)$ { add_header Timing-Allow-Origin "*" always; add_header Cache-Control "public, max-age=31536000, immutable" always;
}

Cloudflare (Transform Rules)

Rules → Transform Rules → Modify Response Headers → Add header: Timing-Allow-Origin = * (for public assets). Apply to paths matching your static files.

Vercel (vercel.json)

{ "headers": [ { "source": "/static/(.*)", "headers": [ { "key": "Timing-Allow-Origin", "value": "*" }, { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ]
}

Verify it is working

// Run in browser console on your site:
performance.getEntriesByType("resource") .filter(e => e.name.includes("cdn.yourapp.com")) .map(e => ({ name: e.name.split("/").pop(), duration: Math.round(e.responseEnd - e.startTime), dns: Math.round(e.domainLookupEnd - e.domainLookupStart), connect: Math.round(e.connectEnd - e.connectStart), ttfb: Math.round(e.responseStart - e.requestStart) }));
// Should now show real millisecond values instead of 0

Which timing fields become available

FieldWithout TAOWith TAO
duration0Real value
startTimeReal valueReal value
responseEnd0Real value
domainLookupStart/End0Real value
connectStart/End0Real value
responseStart (TTFB)0Real value
Audit your performance headers → EdgeFix
Check if your domain is on the HSTS preload list → HSTS Preload Checker