Users were complaining about slow page loads. Chrome DevTools showed a TTFB (Time To First Byte) of 400ms, with HTTPS handshake alone taking 200ms. This post documents how I got that down to 50ms.
1. Diagnosing the Problem
1.1 Packet Analysis
1
2
3
4
5
6
7
8
9
| # Measure timing for each phase with curl
curl -w "@curl-format.txt" -o /dev/null -s https://terra-nas.com
# curl-format.txt:
# time_namelookup: %{time_namelookup}s\n
# time_connect: %{time_connect}s\n
# time_appconnect: %{time_appconnect}s\n
# time_starttransfer: %{time_starttransfer}s\n
# time_total: %{time_total}s\n
|
Results:
1
2
3
4
5
| time_namelookup: 0.020s # DNS
time_connect: 0.060s # TCP (RTT ≈ 40ms)
time_appconnect: 0.260s # TLS handshake complete!
time_starttransfer: 0.280s
time_total: 0.350s
|
TLS handshake took 200ms — the time between TCP connection and application data transfer.
1.2 Breaking Down the Handshake
Using openssl s_client for detailed analysis:
1
| openssl s_client -connect terra-nas.com:443 -status 2>&1 | head -50
|
Issues found:
- Server was using TLS 1.2 (requires 2-RTT handshake)
- OCSP response — client was fetching it separately (extra latency)
- Certificate chain included 3 certificates (more data to transfer)
2. TLS Handshake Internals
2.1 TLS 1.2 Handshake (2-RTT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Client Server
| |
|------- ClientHello --------------->| RTT 1
|<------ ServerHello + Cert ---------|
|<------ ServerKeyExchange ----------|
|<------ ServerHelloDone ------------|
| |
|------- ClientKeyExchange --------->| RTT 2
|------- ChangeCipherSpec ---------->|
|------- Finished ------------------>|
|<------ ChangeCipherSpec -----------|
|<------ Finished -------------------|
| |
|======= Application Data ==========>| Finally can send data!
|
Latency: At least 2 RTTs. With RTT=40ms, TLS handshake ≥ 80ms.
But we measured 200ms — there’s additional overhead somewhere.
2.2 TLS 1.3 Handshake (1-RTT)
1
2
3
4
5
6
7
8
9
10
11
| Client Server
| |
|------- ClientHello + KeyShare ---->| RTT 1
|<------ ServerHello + KeyShare -----|
|<------ EncryptedExtensions --------|
|<------ Certificate ----------------|
|<------ CertificateVerify ----------|
|<------ Finished -------------------|
|------- Finished ------------------>|
| |
|======= Application Data ==========>| Can send data after 1-RTT!
|
Key optimization: ClientHello includes key exchange parameters upfront, saving one RTT.
2.3 0-RTT (TLS 1.3 Early Data)
1
2
3
4
5
6
7
8
| # After first connection, server sends Session Ticket
# Subsequent connections can send encrypted data immediately
Client Server
| |
|------- ClientHello + EarlyData --->| 0-RTT!
|======= Application data already flowing ======>|
|<------ ServerHello + ... ----------|
|
Heads up: 0-RTT is vulnerable to replay attacks — only use for idempotent requests.
3. Optimization Steps
3.1 Enable TLS 1.3
Nginx config:
1
2
3
4
5
6
7
8
9
10
11
12
| server {
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3; # Support both 1.2 and 1.3
ssl_prefer_server_ciphers off; # Not needed for TLS 1.3
# TLS 1.3 cipher suites
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# Enable 0-RTT (use with caution)
ssl_early_data on;
}
|
Result: TLS handshake drops from 2-RTT to 1-RTT, saving 40ms.
3.2 OCSP Stapling
The problem: Clients need to verify if the certificate is revoked. Traditional approach sends an OCSP request to the CA.
1
2
3
4
5
| Traditional flow:
Client --> Server: Get certificate
Client --> CA (OCSP): Is this cert valid? ← Extra RTT!
CA --> Client: Yes
Client --> Server: Continue handshake
|
OCSP Stapling: Server pre-fetches the OCSP response and sends it during the handshake.
1
2
3
4
5
6
7
| ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.pem;
# Set OCSP responder
resolver 8.8.8.8 valid=300s;
resolver_timeout 5s;
|
Verify it’s working:
1
2
| openssl s_client -connect terra-nas.com:443 -status 2>&1 | grep -A 15 "OCSP"
# OCSP Response Status: successful
|
Result: Eliminates client-side OCSP lookup, saving 50-100ms.
3.3 Certificate Chain Optimization
The problem: Full certificate chain might have 3-4 certs, increasing transfer size.
1
2
3
4
5
| # Check the certificate chain
openssl s_client -connect terra-nas.com:443 -showcerts 2>/dev/null | grep "s:"
# s:/CN=terra-nas.com
# s:/CN=Let's Encrypt Authority X3 # Intermediate cert
# s:/CN=DST Root CA X3 # Root cert (shouldn't be sent!)
|
Fix: Only send site cert + intermediate cert. Skip root cert (browsers already have it).
1
2
3
4
5
6
7
| # Correct certificate file should contain:
# 1. Site certificate
# 2. Intermediate certificate
# (NOT the root certificate)
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
|
Result: Reduces transfer by ~1KB.
3.4 Session Resumption
Session Tickets (stateless resumption):
1
2
3
4
5
| ssl_session_tickets on;
ssl_session_timeout 1d; # 24 hours
# Custom ticket key (share across servers if needed)
ssl_session_ticket_key /path/to/ticket.key;
|
Session Cache (stateful resumption):
1
2
| ssl_session_cache shared:SSL:10m; # 10MB shared cache
ssl_session_timeout 1d;
|
Result: Returning visitors get 1-RTT or even 0-RTT handshakes.
3.5 Switch to ECDSA Certificates
RSA certificate verification is slower. ECDSA is faster and has shorter keys.
1
2
3
4
5
6
7
8
| # Generate ECDSA private key
openssl ecparam -genkey -name prime256v1 -out privkey.pem
# Generate CSR
openssl req -new -key privkey.pem -out csr.pem
# Request certificate (Let's Encrypt supports ECDSA)
certbot certonly --csr csr.pem
|
Comparison:
| Type | Key Length | Security Equivalent | Signing Speed |
|---|
| RSA 2048 | 2048 bits | 112 bits | Slow |
| ECDSA P-256 | 256 bits | 128 bits | 3-4x faster |
3.6 Enable HTTP/2
HTTP/2 multiplexes requests over a single TCP connection, so TLS handshake only happens once.
4.1 Before vs After
1
2
3
4
5
6
7
| # Before optimization
curl -w "TLS: %{time_appconnect}s\n" -o /dev/null -s https://terra-nas.com
# TLS: 0.260s
# After optimization
curl -w "TLS: %{time_appconnect}s\n" -o /dev/null -s https://terra-nas.com
# TLS: 0.050s
|
4.2 Time Savings Breakdown
| Optimization | Time Saved |
|---|
| TLS 1.3 (1-RTT) | 40ms |
| OCSP Stapling | 60ms |
| Session Resumption | 40ms |
| Certificate chain | 10ms |
| ECDSA certificate | 5ms |
| Total | ~150ms |
5. Monitoring & Verification
5.1 SSL Labs Test
Head to https://www.ssllabs.com/ssltest/ for a detailed score.
Key metrics to check:
- Protocol Support: TLS 1.3 ✓
- OCSP Stapling: Yes ✓
- Certificate Chain: 2 certificates ✓
1
2
3
4
5
| Timing panel:
DNS Lookup: 20ms
Initial connection: 50ms ← Significantly reduced
SSL: 30ms ← TLS handshake
TTFB: 80ms
|
5.3 Prometheus Monitoring
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # Monitor TLS handshake time
- job_name: 'nginx'
metrics_path: '/metrics'
static_configs:
- targets: ['localhost:9113']
# Alert rule
- alert: HighTLSHandshakeTime
expr: nginx_http_ssl_handshake_time_seconds_bucket{le="0.1"} / nginx_http_ssl_handshake_time_seconds_count < 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "Over 10% of TLS handshakes taking > 100ms"
|
6. Complete Config Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| server {
listen 443 ssl http2;
server_name terra-nas.com;
# Certificates
ssl_certificate /etc/letsencrypt/live/terra-nas.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/terra-nas.com/privkey.pem;
# Protocol and cipher suites
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/terra-nas.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# Session resumption
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;
# 0-RTT (use carefully)
ssl_early_data on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
# ...
}
|
7. Summary
| Technique | How It Works | Impact |
|---|
| TLS 1.3 | 1-RTT handshake | -40ms |
| OCSP Stapling | Server pre-fetches cert status | -60ms |
| Session Resumption | Reuse previous handshake | -40ms |
| Trim cert chain | Less data to transfer | -10ms |
| ECDSA certificate | Faster signing algorithm | -5ms |
Key takeaway: HTTPS optimization isn’t just about choosing algorithms — it’s about reducing round trips (RTT). These optimizations make an even bigger difference on high-latency networks (like cross-continent connections).