Time to First Byte (TTFB) is the foundational web performance metric that measures how quickly your server responds to requests. It spans from the moment a user's browser initiates a request until it receives the first byte of response data. While not a Core Web Vital itself, TTFB directly impacts LCP and overall page load performance—you can't paint content that hasn't arrived yet.
What is Time to First Byte?
TTFB encompasses the entire journey of a web request's initial phase:
- DNS lookup: Resolving the domain name
- Connection establishment: TCP handshake and SSL/TLS negotiation
- Server processing: Backend processing time
- Network transit: Time for the first byte to travel back
The metric effectively measures your infrastructure's responsiveness—combining network latency, server processing power, application efficiency, and database performance into a single number.
Google's recommended TTFB thresholds:
- Good: Under 600ms (ideally under 200ms)
- Needs Improvement: 600ms to 1500ms
- Poor: Over 1500ms
These thresholds apply to the 75th percentile of page loads, meaning 75% of your users should experience "good" TTFB.
Why TTFB Matters for Performance and SEO
The Cascade Effect
TTFB is the first domino in the performance cascade. A slow TTFB delays everything:
- LCP impact: Can't render content that hasn't loaded
- FID impact: JavaScript can't execute until it arrives
- User perception: Users see blank screens during high TTFB
Research shows that every 100ms improvement in TTFB correlates with:
- 0.6% increase in conversion rates
- 1% improvement in page views per session
- 0.4% reduction in bounce rate
SEO Implications
While TTFB isn't a direct Core Web Vital, it affects SEO through:
Crawl Budget Optimization: Googlebot allocates crawl time based on server responsiveness. Slow TTFB means:
- Fewer pages crawled per visit
- Slower index updates
- Potential ranking delays
Indirect Ranking Factors:
- Poor TTFB worsens Core Web Vitals
- Increases bounce rates (negative user signal)
- Reduces pages per session
- Limits crawl efficiency
Competitive Advantage: In competitive niches, TTFB can be the differentiator. Amazon found that every 100ms of latency cost them 1% in sales.
Global User Impact
TTFB varies dramatically by geography:
- Same-region requests: 10-50ms
- Cross-country requests: 50-150ms
- International requests: 150-500ms+
- Emerging markets: Often 500ms+ due to infrastructure
This geographical variance makes CDNs and edge computing critical for global audiences.
How TTFB Works: Technical Components
Breaking Down TTFB
TTFB consists of several sequential components:
1. DNS Resolution (0-200ms)
User enters URL → Browser checks DNS cache →
→ OS DNS cache → Router cache → ISP DNS →
→ Root servers → TLD servers → Authoritative servers
2. TCP Connection (10-150ms per round trip)
SYN → (network latency) → Server
SYN-ACK ← (network latency) ← Server
ACK → (network latency) → Server
3. TLS Handshake (20-200ms)
ClientHello → Server
ServerHello, Certificate ← Server
ClientKeyExchange, Finished → Server
Finished ← Server
4. HTTP Request (5-50ms)
GET /page HTTP/1.1 → Server
Host: example.com
[Headers...]
5. Server Processing (Variable, 50-5000ms)
Request received → Route matching → Authentication →
→ Database queries → Business logic → Template rendering →
→ Response generation
6. First Byte Transit (5-150ms)
Server → Network infrastructure → User's browser
Server Processing Deep Dive
The server processing phase often dominates TTFB:
Request Lifecycle:
- Web server receives request (nginx, Apache)
- Passes to application server (Node.js, Python, PHP)
- Router determines handler
- Middleware executes (auth, logging, etc.)
- Controller/handler processes request
- Database queries execute
- Template renders or JSON serializes
- Response buffers and sends
Common Bottlenecks:
- Unoptimized database queries
- Synchronous external API calls
- Heavy computation on request thread
- Session storage retrieval
- Template compilation
Network Factors
Physical Distance: Speed of light limitations mean:
- Same city: ~1ms round trip
- Cross-country (US): ~40ms round trip
- Transoceanic: ~100-150ms round trip
Network Congestion:
- Peak hours increase latency
- ISP routing inefficiencies
- Peering agreements affect routes
- Mobile networks add 20-100ms
Best Practices for Optimizing TTFB
1. Implement Smart Caching Strategies
Server-Side Caching
// Redis caching example
const redis = require("redis")
const client = redis.createClient()
async function getCachedData(key, fetchFunction, ttl = 3600) {
// Check cache first
const cached = await client.get(key)
if (cached) return JSON.parse(cached)
// Fetch and cache if miss
const data = await fetchFunction()
await client.setex(key, ttl, JSON.stringify(data))
return data
}
// Usage
app.get("/api/products", async (req, res) => {
const products = await getCachedData(
"products:all",
() => db.products.findAll(),
300 // 5-minute cache
)
res.json(products)
})
Edge Caching with CDN
# Nginx caching configuration
location /api/ {
proxy_cache api_cache;
proxy_cache_valid 200 302 5m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}
Database Query Caching
-- MySQL query cache
SET SESSION query_cache_type = ON;
-- PostgreSQL with materialized views
CREATE MATERIALIZED VIEW product_summary AS
SELECT category, COUNT(*), AVG(price)
FROM products
GROUP BY category;
-- Refresh periodically
REFRESH MATERIALIZED VIEW CONCURRENTLY product_summary;
2. Optimize Database Performance
Query Optimization
-- Bad: N+1 query problem
SELECT * FROM posts;
-- Then for each post:
SELECT * FROM comments WHERE post_id = ?;
-- Good: Single query with JOIN
SELECT p.*, c.*
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
ORDER BY p.created_at DESC;
-- Better: With selective columns
SELECT
p.id, p.title, p.excerpt,
COUNT(c.id) as comment_count
FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
GROUP BY p.id
LIMIT 20;
Indexing Strategy
-- Analyze slow queries
EXPLAIN ANALYZE SELECT * FROM users
WHERE email = 'user@example.com' AND status = 'active';
-- Add composite index
CREATE INDEX idx_users_email_status
ON users(email, status);
-- Monitor index usage
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
ORDER BY idx_scan;
Connection Pooling
// Node.js with connection pooling
const { Pool } = require("pg")
const pool = new Pool({
max: 20, // Maximum connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
})
// Reuse connections
app.get("/api/data", async (req, res) => {
const client = await pool.connect()
try {
const result = await client.query("SELECT * FROM data")
res.json(result.rows)
} finally {
client.release() // Return to pool
}
})
3. Use Content Delivery Networks (CDNs)
CDN Configuration
// Cloudflare Workers for edge compute
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
// Check edge cache
const cache = caches.default
const cached = await cache.match(request)
if (cached) return cached
// Fetch from origin
const response = await fetch(request)
// Cache at edge
if (response.status === 200) {
const headers = new Headers(response.headers)
headers.set("Cache-Control", "public, max-age=300")
const cachedResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: headers
})
event.waitUntil(cache.put(request, cachedResponse.clone()))
return cachedResponse
}
return response
}
Multi-Region Deployment
# AWS CloudFront configuration
Distributions:
MyDistribution:
Type: AWS::CloudFront::Distribution
Properties:
Origins:
- DomainName: origin.example.com
S3OriginConfig:
OriginAccessIdentity: !Sub origin-access-identity
Enabled: true
PriceClass: PriceClass_All # Use all edge locations
ViewerCertificate:
AcmCertificateArn: !Ref Certificate
CacheBehaviors:
- PathPattern: /api/*
TargetOriginId: APIOrigin
ViewerProtocolPolicy: https-only
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
4. Optimize Server Configuration
Enable HTTP/2 and HTTP/3
# Nginx HTTP/2 and HTTP/3 configuration
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 443 http3 reuseport;
listen [::]:443 http3 reuseport;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Enable OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# HTTP/3 advertisement
add_header Alt-Svc 'h3=":443"; ma=86400';
}
Optimize Application Server
// Node.js cluster for multi-core utilization
const cluster = require("cluster")
const os = require("os")
if (cluster.isMaster) {
const cpuCount = os.cpus().length
for (let i = 0; i < cpuCount; i++) {
cluster.fork()
}
cluster.on("exit", worker => {
console.log(`Worker ${worker.process.pid} died, restarting...`)
cluster.fork()
})
} else {
require("./app.js") // Your application
}
5. Implement Server Push and Preloading
HTTP/2 Server Push
// Express with HTTP/2 push
app.get("/", (req, res) => {
// Push critical resources
if (res.push) {
res
.push("/css/main.css", {
request: { accept: "text/css" },
response: { "content-type": "text/css" }
})
.end(cssContent)
res
.push("/js/app.js", {
request: { accept: "application/javascript" },
response: { "content-type": "application/javascript" }
})
.end(jsContent)
}
res.send(htmlContent)
})
Early Hints (103 Status)
// Send early hints while processing
app.get("/page", async (req, res) => {
// Send 103 Early Hints immediately
res.writeHead(103, {
Link: [
"</css/main.css>; rel=preload; as=style",
"</js/app.js>; rel=preload; as=script",
"</api/data>; rel=preload; as=fetch"
].join(", ")
})
// Process request (database queries, etc.)
const data = await processRequest(req)
// Send final response
res.render("page", data)
})
Common TTFB Mistakes to Avoid
1. Synchronous External API Calls
// Bad: Blocking API call
app.get("/product/:id", async (req, res) => {
const product = await db.getProduct(req.params.id)
const reviews = await externalAPI.getReviews(product.id) // Blocks response
res.json({ product, reviews })
})
// Good: Parallel fetching or async loading
app.get("/product/:id", async (req, res) => {
const product = await db.getProduct(req.params.id)
// Send product immediately
res.json({
product,
reviewsUrl: `/api/reviews/${product.id}` // Load async
})
})
2. Missing Database Indexes
-- Identify missing indexes
SELECT
schemaname,
tablename,
attname,
n_distinct,
correlation
FROM pg_stats
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
AND n_distinct > 100
AND correlation < 0.1
ORDER BY n_distinct DESC;
3. Not Using Keep-Alive Connections
# Enable keep-alive
upstream backend {
server backend1.example.com:8080;
server backend2.example.com:8080;
keepalive 32; # Connection pool size
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # Required for keep-alive
}
4. Inefficient Session Storage
// Bad: Database sessions
app.use(session({
store: new MySQLStore({...}), // Database hit on every request
}));
// Good: Redis sessions
app.use(session({
store: new RedisStore({
client: redisClient,
ttl: 3600
}),
}));
// Better: Stateless JWT tokens
app.use(jwt({
secret: process.env.JWT_SECRET,
algorithms: ['HS256']
}));
5. No Warmup Strategy
// Implement cache warming
async function warmCache() {
const criticalRoutes = ["/", "/products", "/api/featured"]
for (const route of criticalRoutes) {
await fetch(`http://localhost:3000${route}`)
}
}
// Run on deploy or periodically
setInterval(warmCache, 3600000) // Every hour
Tools for Measuring TTFB
Command Line Tools
# cURL with timing information
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}\n" https://example.com
# Detailed timing breakdown
curl -o /dev/null -s -w "\
DNS Lookup: %{time_namelookup}s\n\
Connect: %{time_connect}s\n\
TLS Handshake: %{time_appconnect}s\n\
TTFB: %{time_starttransfer}s\n\
Total: %{time_total}s\n" \
https://example.com
Browser DevTools
Chrome DevTools Network Panel shows:
- DNS lookup time
- Initial connection
- SSL negotiation
- TTFB (Waiting/TTFB)
- Content download
Online Tools
- WebPageTest: Detailed waterfall with TTFB breakdown
- GTmetrix: TTFB by location
- Pingdom: Global TTFB monitoring
- Rankwise Speed Test: Competitive TTFB analysis
Monitoring Scripts
// Browser-based TTFB measurement
const measureTTFB = () => {
const [navigation] = performance.getEntriesByType("navigation")
if (navigation) {
const ttfb = navigation.responseStart - navigation.requestStart
console.log(`TTFB: ${ttfb}ms`)
// Send to analytics
if (window.gtag) {
gtag("event", "timing_complete", {
name: "TTFB",
value: Math.round(ttfb)
})
}
}
}
window.addEventListener("load", measureTTFB)
Frequently Asked Questions
What's the difference between TTFB and server response time?
TTFB includes network latency (DNS, connection, SSL) plus server response time. Server response time is just the backend processing duration. TTFB is what users experience; server response time is what you can directly control.
How does HTTPS affect TTFB?
HTTPS adds a TLS handshake, typically 20-200ms depending on distance and cipher suites. Use TLS 1.3 for faster handshakes, enable OCSP stapling, and consider session resumption to minimize this overhead.
Should I prioritize TTFB over other metrics?
Balance is key. A fast TTFB with slow render is still a poor experience. Aim for:
- TTFB < 600ms
- FCP < 1.8s
- LCP < 2.5s Focus on your worst metric first.
How much does hosting location matter?
Immensely for global audiences. A US-hosted site serving Asian users might have 200ms+ network latency alone. Use CDNs or multi-region deployments for global sites. Every 100ms of distance-based latency directly adds to TTFB.
Can service workers improve TTFB?
Yes, dramatically for repeat visits. Service workers can serve cached responses with 0ms TTFB. However, they don't help first visits and add complexity. Use them for frequently accessed resources and offline functionality.