← Recipes

阅读中文版本

Measure TLS Handshake Overhead and Session Reuse

Time full versus resumed TLS handshakes with curl and openssl, quantify the certificate chain cost, and decide where connection reuse pays off.

~20 minapiperformancetls

Task

Measure cold versus warm TLS handshake latency for an endpoint and quantify how much certificate chain size and session reuse affect it.

When to use this

A CLI tool makes hundreds of sequential HTTPS calls and feels sluggish. Everyone blames the server, but nobody has measured what the handshake adds on every single call.

Prerequisites

  • curl with time_appconnect support and openssl on PATH
  • The target endpoint reachable directly and through any CDN
  • Control over the client connection settings

Steps

1. Time a cold handshake with no session reuse.

for i in 1 2 3; do curl -s -o /dev/null -H "Connection: close" -w "tls=%{time_appconnect} ttfb=%{time_starttransfer}\n" https://api.example.com/v1/ping; done

Expected output: Each run reconnects and pays the full handshake, for example tls=0.190 with a three-certificate chain.

2. Time warm handshakes reusing the TLS session.

curl -s -o /dev/null --next -s -o /dev/null -w "tls=%{time_appconnect}\n" https://api.example.com/v1/ping https://api.example.com/v1/ping

Expected output: The second request over the same connection reports tls=0.000 because no new handshake occurs at all.

3. Inspect the chain the server sends.

openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | grep -c "^-----BEGIN CERTIFICATE"

Expected output: A count of 3 certificates shows the chain size that every cold client must download and verify.

4. Compare handshake cost against the response itself.

curl -s -o /dev/null -H "Connection: close" -w "tls=%{time_appconnect} total=%{time_total}\n" https://api.example.com/v1/ping

Expected output: When tls is 0.19 of a 0.21 total, the payload is nearly free and the handshake is the entire cost of the call.

5. Enable keep-alive in the client and verify the saving.

const agent = new https.Agent({
  keepAlive: true,
  maxSockets: 4,
});
fetch(url, { dispatcher: new Agent({ connections: 4 }) });

Expected output: Repeated calls reuse the connection, and total time per call drops close to the pure response time measured earlier.

Troubleshooting

Problem

Session resumption never happens

Solution

The server disables session tickets or the load balancer terminates TLS per connection; enable session tickets or move termination to a component that pools connections.

Problem

Handshake slow only from office network

Solution

Corporate proxies add a full extra handshake; measure from a neutral network before concluding the server chain is the problem.

Problem

time_appconnect is always 0

Solution

The request never reached TLS because it fell back to HTTP or failed earlier; check the scheme and that DNS resolves before timing handshakes.

Limitations

  • Handshake cost is highly network-dependent, so numbers are only comparable from the same vantage point.
  • TLS 1.3 changes resumption mechanics; measure with the protocol version you actually deploy.

The handshake you pay on every call

A client that opens a fresh connection per request pays DNS, TCP, and a full TLS handshake before sending a single byte of payload. curl time_appconnect isolates exactly the TLS portion, and comparing cold and warm requests quantifies what reuse is worth. For small API calls the handshake can dominate the entire request, which makes keep-alive the cheapest performance win available.

# Cold: force a new connection each time
for i in 1 2 3; do curl -s -o /dev/null -H "Connection: close" \
  -w "tls=%{time_appconnect} total=%{time_total}\n" https://api.example.com/v1/ping; done

# Warm: second transfer reuses the connection
curl -s -o /dev/null --next -s -o /dev/null -w "tls=%{time_appconnect}\n" \
  https://api.example.com/v1/ping https://api.example.com/v1/ping

Chain size is latency you ship

Every certificate in the chain is bytes the client downloads and crypto it verifies. Counting the certificates with openssl s_client is a one-liner, and an unnecessary intermediate certificate adds measurable milliseconds to every cold client. Serve the minimal correct chain and let clients cache it.

Fix the client before the server

When the numbers show handshake dominating small calls, the remedy is client-side connection pooling with keep-alive, not faster server hardware. One connection amortizes the handshake across hundreds of requests, and a bounded socket pool keeps the server safe at the same time.