A continuation of Janady13/crawler-signal-reference / framework-http-performance-headers.md. The complete source identity, every recorded occurrence, and linked equations are on its first page.
Source transcript · continued
Download complete source text ↓1,967 lines · 75.0 KB
```bash
# Server header reduced
curl -sI https://example.com/ | grep -i "^server:"
# Alt-Svc set
curl -sI https://example.com/ | grep -i alt-svc
# HTTP/3 negotiable
curl --http3 -sI https://example.com/ | head -1
```
---
## 12. AUDIT CHECKLIST
Run through these 50 items for any production site.
### Server-Timing
1. [ ] Dynamic responses from FastAPI sidecar include `Server-Timing` header.
2. [ ] Server-Timing values do not expose sensitive data (file paths, queries, internal service names).
3. [ ] Metric names are short and consistent across endpoints.
4. [ ] `total` metric is exposed for at least one comprehensive duration.
5. [ ] Static assets are NOT decorated with Server-Timing (no application timing applies).
6. [ ] Server-Timing values appear in browser DevTools Network panel timing section.
7. [ ] `performance.getEntriesByType("navigation")[0].serverTiming` returns an array in the console.
8. [ ] RUM beacon captures Server-Timing values and ships to monitoring.
### Timing-Allow-Origin
9. [ ] If assets are on a cross origin (CDN subdomain), TAO is set appropriately.
10. [ ] Same origin assets do NOT have TAO (it is unnecessary noise).
11. [ ] Cross origin partner widgets have TAO matching the consuming origin or `*`.
12. [ ] Resource Timing data in RUM shows non zero transferSize for monitored resources.
13. [ ] If using third party fonts or scripts, blind spots are documented and accepted.
### Server
14. [ ] `server_tokens off` is set in `nginx.conf` at http level.
15. [ ] Server header value is `nginx` (no version) at minimum.
16. [ ] If maximum opacity required, Server header is removed entirely via headers-more.
17. [ ] No `X-Powered-By` or similar disclosure headers.
18. [ ] Custom error pages do not mention nginx version.
19. [ ] Scanner does not flag version disclosure (Invicti, Acunetix, Qualys, etc).
20. [ ] FastAPI sidecar does not emit its own `Server: uvicorn` to the client.
### Alt-Svc
21. [ ] `Alt-Svc: h3=":443"; ma=86400` header sent on every response.
22. [ ] nginx version 1.25+ confirmed (run `nginx -v`).
23. [ ] nginx built with HTTP/3 module (run `nginx -V 2>&1 | grep http_v3`).
24. [ ] `listen 443 quic` directive present in canonical server block.
25. [ ] `http3 on;` directive present.
26. [ ] UDP/443 open in firewall.
27. [ ] `ssl_protocols TLSv1.2 TLSv1.3` (HTTP/3 requires 1.3).
28. [ ] `ssl_early_data on` set (for 0-RTT, optional but recommended).
29. [ ] Exactly one server has `reuseport` on its quic listen (default server).
30. [ ] Other servers have plain `listen 443 quic` (no reuseport).
31. [ ] `curl --http3 https://example.com/` negotiates HTTP/3 successfully.
32. [ ] Browser DevTools Network panel shows `h3` protocol for repeat visits.
33. [ ] Access log includes HTTP/3 indicator (`$http3` variable).
34. [ ] HTTP/3 traffic percentage in logs is non zero (verifies real world adoption).
### Cross cutting
35. [ ] `nginx -t` passes without warnings.
36. [ ] `nginx -T` shows all expected directives in effect.
37. [ ] HTTP/3 works from external networks (not just localhost).
38. [ ] Server-Timing visible in browser PerformanceServerTiming.
39. [ ] Resource Timing populates for same origin assets.
40. [ ] TTFB measurements correlate with Server-Timing total.
41. [ ] Alt-Svc value uses ma=86400 or longer (not the absent default).
42. [ ] No protocol downgrade attempts (HTTP/3 stays sticky once negotiated).
43. [ ] Access log has both HTTP/2 and HTTP/3 entries (mix is healthy).
44. [ ] No errors in nginx error log related to QUIC.
45. [ ] Performance monitoring backend ingests Server-Timing values correctly.
46. [ ] Server header consistent across all server blocks and locations.
47. [ ] Alt-Svc header consistent across all server blocks.
48. [ ] No accidental Server header reintroduction by FastAPI sidecar or other upstream.
49. [ ] Documentation lists which third party scripts are timing blind (no TAO).
50. [ ] Quarterly review confirms HTTP/3 adoption rate and Server-Timing coverage.
A site that passes all 50 has correctly configured performance observability and protocol advertisement.
---
## 13. COMMON PITFALLS
Fifteen patterns to recognize and avoid.
**Pitfall 1: HTTP/3 enabled in nginx but no Alt-Svc header set.**
Symptom: zero HTTP/3 traffic in access logs despite valid nginx config.
Why it breaks: browsers never attempt HTTP/3 without Alt-Svc telling them it is available.
Fix: add `Alt-Svc 'h3=":443"; ma=86400'` to every server block.
**Pitfall 2: Multiple servers with `reuseport` on quic listen.**
Symptom: nginx fails to start with EADDRINUSE on UDP/443.
Why it breaks: only one server block per address may declare `reuseport`.
Fix: one server (typically the default) has `reuseport`; all others have plain `listen 443 quic`.
**Pitfall 3: Server-Timing exposing sensitive details.**
Symptom: pen tester reports information disclosure via Server-Timing.
Why it breaks: `desc="users.lookup_by_email"` reveals database schema.
Fix: restrict `desc=` values to generic identifiers, or omit `desc` entirely.
**Pitfall 4: Timing-Allow-Origin set to `*` on a private internal resource.**
Symptom: scanner reports overly permissive TAO.
Why it breaks: any origin can profile timing of an internal resource.
Fix: restrict to specific origins, or remove TAO if not needed (defaults are restrictive enough).
**Pitfall 5: server_tokens off but Server header still leaking version.**
Symptom: scanner flags version after applying the directive.
Why it breaks: nginx was not reloaded, or directive is in wrong context.
Fix: `nginx -t && systemctl reload nginx`. Verify with curl.
**Pitfall 6: Vanity Server header that looks suspicious.**
Symptom: `Server: hacker-mcgee` triggers WAF rules at downstream proxies.
Why it breaks: some WAFs and security tools flag unusual Server values.
Fix: use a plausible looking value (`Server: web`, `Server: bubbles`, or just remove entirely).
**Pitfall 7: HTTP/3 advertised but UDP blocked.**
Symptom: browsers try HTTP/3, fail, fall back to HTTP/2 every time.
Why it breaks: firewall or upstream router blocks UDP/443.
Fix: open UDP/443 (`sudo ufw allow 443/udp`). Verify with `nc -u -l 443` and external test.
**Pitfall 8: nginx version too old for HTTP/3.**
Symptom: `listen 443 quic` rejected with "unknown directive".
Why it breaks: nginx versions before 1.25 lack HTTP/3 module.
Fix: install from official nginx APT repository to get 1.26+.
**Pitfall 9: Cross origin assets blind in RUM without TAO.**
Symptom: dashboard shows transferSize=0 for half the resources.
Why it breaks: cross origin browser policy zeroes detailed timing without TAO opt in.
Fix: self host the assets (preferred on Bubbles), or coordinate with the third party to add TAO.
**Pitfall 10: Server-Timing not visible in browser.**
Symptom: curl shows the header but browser Network panel does not list "Server Timing" section.
Why it breaks: typically the response is cross origin and Timing-Allow-Origin is missing.
Fix: set TAO on cross origin endpoints that emit Server-Timing.
**Pitfall 11: Alt-Svc with ma=0 or ma not set.**
Symptom: HTTP/3 negotiates once, then browser forgets.
Why it breaks: default ma is unspecified; some browsers treat as 0.
Fix: always include `ma=86400` (1 day) or longer.
**Pitfall 12: HTTP/3 works in curl but Chrome refuses.**
Symptom: `curl --http3 -sI https://example.com/` succeeds; Chrome stays on HTTP/2.
Why it breaks: Chrome had a failed HTTP/3 attempt and is in cooldown, OR Chrome flag disabled HTTP/3.
Fix: clear browsing data including network state. Check `chrome://flags/#enable-quic`.
**Pitfall 13: Server-Timing on every request, including for static assets.**
Symptom: log volume explosion; thousands of empty Server-Timing entries.
Why it breaks: the upstream is generating timing for every request including ones where nothing meaningful happens.
Fix: emit Server-Timing only on dynamic responses. Static asset locations should not invoke the upstream.
**Pitfall 14: Server header reintroduced by FastAPI sidecar.**
Symptom: nginx server_tokens off applied, but client still sees `Server: uvicorn`.
Why it breaks: when nginx proxies, it passes through the upstream's Server header.
Fix: configure uvicorn with `server_header=False`, or strip in nginx with `proxy_hide_header Server`.
**Pitfall 15: Alt-Svc but no nginx HTTP/3 actually configured.**
Symptom: Alt-Svc header set, but HTTP/3 fails on first attempt. Browsers stop trying.
Why it breaks: advertising a non existent alternative.
Fix: either add the nginx http3 listen directive, or remove the Alt-Svc header until ready.
---
## 14. DIAGNOSTIC COMMANDS
Reference of every command useful for performance header investigation.
### Inspect all performance headers
```bash
# Full performance family
curl -sI https://example.com/ | grep -iE "^(server-timing|timing-allow-origin|server|alt-svc):"
# Including via HTTP/3 if available
curl --http3 -sI https://example.com/ | grep -iE "^(server-timing|timing-allow-origin|server|alt-svc):"
# As different user agents (different Server-Timing may be exposed)
for ua in "Googlebot" "Mozilla/5.0 Chrome" "curl"; do
echo "=== $ua ==="
curl -sI -A "$ua" https://example.com/ | grep -i server-timing
done
```
### Server-Timing verification
```bash
# Header presence
curl -sI https://example.com/page | grep -i server-timing
# Parse and display
curl -sI https://example.com/page | grep -i server-timing | tr ',' '\n' | sed 's/^ */ /'
# Verify upstream emits it
curl -sI http://127.0.0.1:9090/page | grep -i server-timing
# Browser DevTools console:
# performance.getEntriesByType("navigation")[0].serverTiming
# performance.getEntriesByType("resource")[0].serverTiming
# Sanity check: total dur matches observed
TOTAL=$(curl -sI https://example.com/page | grep -i server-timing | grep -oE "total;dur=[0-9.]+" | cut -d= -f2)
OBSERVED=$(curl -so /dev/null -w "%{time_starttransfer}" https://example.com/page)
echo "Server reports: $TOTAL ms"
echo "Client observed: $(echo "$OBSERVED * 1000" | bc) ms"
```
### Timing-Allow-Origin verification
```bash
# Header presence on cross origin asset
curl -sI https://cdn.example.com/main.css | grep -i timing-allow-origin
# Browser DevTools console:
# const r = performance.getEntriesByName("https://cdn.example.com/main.css")[0];
# console.log({transferSize: r.transferSize, responseStart: r.responseStart});
# Expected: real numbers with TAO; zeros without
# Audit which resources are missing timing
# Open DevTools, run:
# performance.getEntriesByType("resource").filter(r => r.transferSize === 0 && r.duration > 0)
```
### Server header verification
```bash
# Current value
curl -sI https://example.com/ | grep -i "^server:"
# Across all error states
for status in 200 404 500; do
curl -sIo /dev/null -w "Status: %{http_code} Server: " "https://example.com/test-status-$status"
curl -sI "https://example.com/test-status-$status" | grep -i "^server:" | cut -d: -f2-
done
# Check error pages
curl -s https://example.com/this-does-not-exist | grep -i nginx
# Confirm server_tokens setting in active config
nginx -T 2>/dev/null | grep -i server_tokens
# Check headers-more is loaded if used
nginx -V 2>&1 | grep -o headers-more
# Sanity check: no Server header from upstream sneaking through
curl -sI http://127.0.0.1:9090/api/anything | grep -i "^server:"
```
### Alt-Svc and HTTP/3 verification
```bash
# Header presence
curl -sI https://example.com/ | grep -i alt-svc
# Test HTTP/3 with curl (requires curl built with HTTP/3 support)
curl --http3 -sI https://example.com/
# Look for: HTTP/3 200
# Verify nginx config
nginx -T 2>/dev/null | grep -E "http3 on|listen.*quic|reuseport"
# Check UDP/443 listening
sudo ss -lunpt | grep ":443"
# Check firewall
sudo ufw status | grep 443
sudo iptables -L INPUT -n | grep 443
# External tester
echo "Visit: https://http3check.net/?host=example.com"
# Browser:
# DevTools > Network > any request > Protocol column should show "h3"
# chrome://net-internals/#alt-svc > search example.com
# Count HTTP/3 traffic in logs (requires $http3 in log_format)
grep "h3" /var/log/nginx/access.log | wc -l
grep -c "\"h3\"" /var/log/nginx/access.log
echo "HTTP/3 vs HTTP/2 ratio:"
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c
```
### Server side investigation
```bash
# Active nginx config
nginx -T 2>/dev/null > /tmp/nginx-active.conf
# Find performance directives
grep -E "server_tokens|http3|quic|early_data" /tmp/nginx-active.conf
# Find all add_header directives related to performance
grep -A1 "add_header" /tmp/nginx-active.conf | grep -iE "alt-svc|server-timing|timing-allow"
# Check uvicorn configuration
ps auxww | grep uvicorn | grep -oE "server.header[a-z]*"
# Apply changes
nginx -t && systemctl reload nginx
```
### Browser DevTools quick reference
In Chrome DevTools Network panel:
* **Protocol column**: shows `h3`, `h2`, or `http/1.1`. Add via column header right click if not visible.
* **Timing tab on each request**: shows the timeline including any Server-Timing metrics.
* **Console PerformanceObserver test:**
```javascript
// Show navigation timing including serverTiming
const nav = performance.getEntriesByType("navigation")[0];
console.log({
ttfb: nav.responseStart - nav.requestStart,
serverTiming: nav.serverTiming
});
// Show all resources with their timing
performance.getEntriesByType("resource").forEach(r => {
console.log(r.name, {
protocol: r.nextHopProtocol,
transferSize: r.transferSize,
duration: r.duration
});
});
```
* **Network panel filter**: type `protocol:h3` to show only HTTP/3 requests.
---
## 15. CROSS-REFERENCES
* [framework-http-caching-headers.md](framework-http-caching-headers.md): Cache-Control, ETag, Last-Modified, Expires, Vary, Age. Caching directives interact with Server-Timing because cached responses do not pass through the upstream's timing instrumentation.
* [framework-http-content-headers.md](framework-http-content-headers.md): Content-Type, Content-Language, Content-Encoding, Content-Length, Content-Disposition. Content-Encoding (compression) affects Resource Timing transfer size measurements.
* [framework-http-seo-headers.md](framework-http-seo-headers.md): X-Robots-Tag, Link, Location. The Link header preload + Early Hints (103) pairs with HTTP/3 advertisement for the fastest possible TTFB optimization.
* [framework-http-security-headers.md](framework-http-security-headers.md): HSTS, CSP, X-Frame-Options, etc. Server suppression pairs with the broader security header stack for fingerprint reduction. HTTP/3 advertisement (Alt-Svc) layers on top of HSTS for transport security plus performance.
* [UNIVERSAL-RANKING-FRAMEWORK.md](UNIVERSAL-RANKING-FRAMEWORK.md): the master ranking reference. Performance headers contribute to Core Web Vitals.
* [SEO-BUILD-REFERENCE.md](SEO-BUILD-REFERENCE.md) v2.4: the build playbook.
* [framework-corewebvitals.md](framework-corewebvitals.md): LCP, INP, CLS targets. Server-Timing and HTTP/3 directly improve TTFB which improves LCP.
* [framework-rum.md](framework-rum.md): Real User Monitoring implementation. Companion to Server-Timing and Timing-Allow-Origin.
* [framework-http3-quic.md](framework-http3-quic.md): deeper dive on HTTP/3, QUIC, 0-RTT, connection migration.
* Server Timing W3C spec: https://www.w3.org/TR/server-timing/
* Resource Timing W3C spec: https://www.w3.org/TR/resource-timing/
* MDN Server-Timing: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Server-Timing
* MDN Timing-Allow-Origin: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Timing-Allow-Origin
* MDN Alt-Svc: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Alt-Svc
* Nginx HTTP/3 documentation: https://nginx.org/en/docs/http/ngx_http_v3_module.html
* RFC 9114 (HTTP/3): https://www.rfc-editor.org/rfc/rfc9114
* RFC 7838 (HTTP Alternative Services): https://www.rfc-editor.org/rfc/rfc7838
* RFC 9110 (HTTP Semantics, Server header): https://www.rfc-editor.org/rfc/rfc9110
* HTTP/3 check: https://http3check.net/
---
## APPENDIX A: ONE PAGE QUICK REFERENCE
For the person who just wants the answer.
### Bubbles default performance configuration
```nginx
# http context
http {
server_tokens off;
log_format perf '$remote_addr - [$time_local] "$request" $status $body_bytes_sent $server_protocol "$http3" rt=$request_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log perf;
}
# Per server block
server {
listen 443 ssl;
listen 443 quic;
http2 on;
http3 on;
# On the default server only:
# listen 443 quic reuseport;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_early_data on;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
}
```
### Header purpose table
| Header | One line purpose |
|---|---|
| Server-Timing | Expose backend phase durations to browser RUM |
| Timing-Allow-Origin | Grant cross origin access to Resource Timing |
| Server | Identify server software (suppress for fingerprint reduction) |
| Alt-Svc | Advertise HTTP/3 availability |
### Server-Timing format
```
Server-Timing: name;dur=ms[;desc="text"], next;dur=ms
```
### Timing-Allow-Origin values
| Value | Use case |
|---|---|
| `*` | Public embeddable resource |
| `https://example.com` | Specific consuming origin |
| (absent) | Same origin only (default) |
### Server suppression levels
| Level | Method |
|---|---|
| Version hidden | `server_tokens off` |
| Header removed | `more_clear_headers Server` (headers-more) |
| Vanity replacement | `more_set_headers "Server: Bubbles"` |
### Alt-Svc minimum value
```
Alt-Svc: h3=":443"; ma=86400
```
### Five commands every operator should know
```bash
# 1. View all performance headers
curl -sI https://example.com/ | grep -iE "^(server-timing|timing-allow-origin|server|alt-svc):"
# 2. Test HTTP/3 negotiation
curl --http3 -sI https://example.com/
# 3. Check Server-Timing in browser console
# performance.getEntriesByType("navigation")[0].serverTiming
# 4. Count HTTP/3 traffic share
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c
# 5. Apply nginx changes
nginx -t && systemctl reload nginx
```
### Three end to end tests
```bash
# 1. Server header is minimized
curl -sI https://example.com/ | grep -i "^server:"
# Expected: server: nginx (not nginx/1.x.x)
# 2. HTTP/3 is negotiable
curl --http3 -sI https://example.com/ | head -1
# Expected: HTTP/3 200
# 3. Alt-Svc is set
curl -sI https://example.com/ | grep -i alt-svc
# Expected: alt-svc: h3=":443"; ma=86400
```
If all three produce expected output AND access logs show non zero HTTP/3 traffic share AND RUM dashboard shows populated Server-Timing values, the performance header stack is correctly wired.
---
End of framework-http-performance-headers.md.