Cameleering

Cameleering / TLS handshake cost

Cameleering

Cameleering · capacity, recovery and efficiency

The cost of a TLS handshake, and why a reconnection wave is a load test

TLS on an open connection is cheap. Setting the connection up is not, and after a restart, a failover or a network blip, many clients set up at once. The handshake rate a server can sustain is a capacity limit that most teams have never measured.

No claim without a number.

The question: how many new connections per second can one server take?

The headroom essay and the degraded-fleet test both mention a connection storm: every client drops and reconnects together. The cold-start page adds that a restarted node's TLS session cache starts empty. This page takes that one thread and follows it: what a handshake asks of the server, why reconnections bunch up, what that does to a fleet that is already short, and what to do about it.

Nothing here is about a particular incident. It is a general property of any service that terminates TLS.

First, the good news: TLS on an open connection is cheap

In 2010 Adam Langley wrote that on Google's production front ends, SSL/TLS accounted for under 1% of the CPU load, under 10 KB of memory per connection and under 2% of network overhead [3]. That figure is a fair reason not to worry about TLS in steady state, and it is the reason this page is not an argument against TLS.

It is also a figure for a service in steady state, where connections last and are reused. It does not say what happens when a large share of the connections are new at the same moment. That is a different workload, and the next section shows where the work is.

What a handshake asks of the server

The table compares the four cases a server meets. "Server work" names the costly operations only; it is not a measurement.

caseround trips before application dataserver worknotes
TLS 1.3, full1a key exchange, a signature with the certificate's key, sending the certificate chainthe client has no state for this server
TLS 1.3, resumed1checking the ticket; a key exchange as well when forward secrecy is kept; no certificate, no signaturethe ticket is a pre-shared key from an earlier connection [1]
TLS 1.3, resumed with 0-RTT0 for the first requestas resumedno forward secrecy and no guarantee of non-replay for that data [1]
TLS 1.2, full2a key exchange and a signatureolder clients, and most middleboxes that have not been updated

TLS 1.3 saves a round trip, which is the client's wait [1]. In TLS 1.2, session IDs and tickets did the work that pre-shared keys do now [2]. The server still has to do public-key work for every full handshake. Bulk encryption of the data afterwards is the cheap part on current hardware, which is why Langley's steady-state figure is so low.

The signature depends on the certificate. An RSA key and an elliptic-curve key cost very different amounts to sign with, and the difference is large enough to matter. We do not quote a ratio here, because it depends on the CPU and the library. Run openssl speed on your own hardware for the algorithms you actually serve, as the istlsfastyet guide suggests [3].

Resumption saves less than it sounds like. A resumption that keeps forward secrecy still does a key exchange [1]. What it skips is the certificate and the signature. In our own test, resumed handshakes were about a third faster than full ones, not many times faster (see below).

Why reconnections arrive together

Normal clients reconnect spread out over hours. These events make them reconnect in the same few seconds.

A restart or crash

Every connection that node held is gone, and every client reconnects. Through a load balancer, they reconnect to the nodes that remain.

A deploy that drops connections

A rolling deploy that closes connections instead of draining them sends each node's clients back at once, and it does that node by node.

A load balancer or network failure

A failover moves every connection to a new path in the same instant.

An idle timeout that is the same everywhere

If every connection is dropped after the same idle time, and clients all started together, they come back together.

Retries without jitter

A client that retries after a fixed delay returns at the same moment as every other client that failed at the same moment. The AWS Builders' Library recommends adding jitter for exactly this reason [5].

A certificate or ticket-key change

If every server stops accepting the old tickets at once, every returning client does a full handshake instead of a resumed one.

These events happen when the fleet is least able to absorb them. The Google SRE book's chapter on cascading failures lists cold caches and slow start-up among the causes [7]. A reconnection wave adds a load that scales with the number of clients, not with the traffic they were sending a minute earlier.

The arithmetic of a reconnection wave

Offered handshake rate = clients ÷ the time they take to come back. Every number here is an example, not a measurement.

Take 100,000 clients and two fleets. Assume each server can sustain 5,000 handshakes a second while still doing its other work, so a fleet of 8 can take 40,000 a second and a fleet of 5 can take 25,000. The capacity is invented to show the shape. The table shows how loaded each fleet would be.

example, not a measurementclients come back withinoffered handshakes per secondload on 8 servers (40,000/s)load on 5 servers (25,000/s)
reconnect spread out60 s1,6674%7%
a slow wave10 s10,00025%40%
a fast wave5 s20,00050%80%
a very fast wave2 s50,000125%200%
everyone at once1 s100,000250%400%

Three things follow, and all of them depend on the invented capacity, so test your own.

  • The window matters more than the client count. The same 100,000 clients are harmless over a minute and an overload in a second or two.
  • Losing servers moves the line. The 5-server fleet is at 80% for a five-second wave. That leaves little for the requests the connected clients are sending, because the handshakes and the requests share the same cores.
  • Overload makes it worse, not just longer. A handshake that waits in a queue until the client gives up is CPU spent on a connection nobody will use. The client retries, and if it retries without jitter, the retries arrive as a second wave. Load that the server cannot serve should be refused early, which is what load shedding is for [7].

The page on recovery arithmetic has the same shape: what looks small in steady state decides the outcome when the fleet is short.

The ladder of fixes

Climb it one rung at a time. The first rungs cost the least and change nothing for your users.

1. Measure the ceiling

Find how many full and how many resumed handshakes per second one server sustains with p99 within target, and what CPU that uses. You cannot manage a limit you have not read.

2. Reuse connections

Keep-alive and HTTP/2 multiplexing mean fewer handshakes in the first place. Check that load balancers and clients do not close connections more often than they need to.

3. Choose the certificate with the cost in mind

Measure signing with each key type you could serve, on your own CPU. Serve the cheapest one every client you care about accepts.

4. Make resumption work across the fleet

A ticket is useful only if the server that receives it can read it. Servers behind one name must share ticket keys, and the keys must rotate. nginx documents this need for several servers [4]. A ticket may not be given a lifetime of more than seven days [1].

5. Spread the wave

Drain connections instead of dropping them: an HTTP/2 server can send GOAWAY and let clients move over gradually [6]. Give clients exponential backoff with jitter [5].

6. Admit what you can serve

Limit new connections per second and refuse the rest quickly, so the handshakes you do accept finish. Refusing early is cheaper than failing late [7].

7. Spend less CPU per handshake

Last, and only with measurements: a different library, a different server, or hardware that offloads the public-key work. Do the cheaper rungs first.

A word on 0-RTT

0-RTT lets a returning client send its first request with the handshake, saving a round trip. RFC 8446 says that data is not forward secret and that TLS gives no guarantee of non-replay between connections. It describes anti-replay measures a server can use and says every 0-RTT payload should be replay-safe [1]. If you cannot say that for every request a client might send in it, leave it off. simoom never accepts 0-RTT [8].

Resumption trades something too. Tickets that live for days, under keys that never change, weaken forward secrecy for the sessions they protect. Rotate the keys and keep lifetimes short enough for your security policy to accept.

How to find your limit

This is Test 3 of the degraded-fleet test, narrowed to the handshake.

  1. Use one server, not the fleet, with its real certificate chain and TLS settings, and a client machine with CPU to spare. If the generator is at 100% you have measured the generator.
  2. Open new connections, and make sure the client does not reuse them. k6 has noConnectionReuse for this [9]. Check on the server side that the handshake count matches what you meant to send.
  3. Run once with resumption off and once with it on. Record full and resumed handshakes per second, p99 of the time to the first response byte, error rate and CPU.
  4. Raise the rate in steps until p99 or errors move. The step before is the handshake ceiling for that server.
  5. Repeat with real request traffic running at the same time, since in production the handshakes share cores with it.
  6. Write down the pass line first: for example, "the fleet minus a third takes a ten-second wave of all clients with p99 under X".

Say which loop you used. A closed-loop test (a fixed number of workers, each opening the next connection when the last finishes) measures what the server can sustain. An open-loop test (connections opened at a fixed rate whatever happens) measures what happens when clients keep coming, which is closer to a wave. The first is easier to run and the second answers the question on this page.

Our own numbers, and what they do not show

All of these come from simoom's campaign 4: DigitalOcean c-4 droplets, 2026-11-12, each server against simoom on the same droplet, median of three rounds. They are in the ledger with their losses.

measuresimoombaseline, same droplet
new TLS 1.3 clients, full handshakes per second9,396axum 0.8.9 with rustls: 11,633 (24% more than simoom)
returning TLS 1.3 clients, resumed handshakes per second12,388axum 0.8.9 with rustls: 13,537 (9% more than simoom)
full handshakes against H2O and Go net/http1.39x H2O; 79% more than Go net/httpH2O; Go net/http
full and resumed handshakes against cameleer-http, the server simoom replaced5.9x and 2.2x1,517 and 4,487 per second
  • Against the other server that is built for speed here, we lost: axum with rustls does more handshakes per second than simoom. We say so in the ledger and on every page that quotes the number.
  • Resumed handshakes were about a third faster than full ones in our test (12,388 against 9,396 per second). That is the size of the saving to expect from resumption alone; your certificate and hardware will move it.

The limits, said plainly

  • These tests are closed-loop. 192 workers each opened a new connection per request, so the figures are handshakes per second at saturation, not a rate we offered and not a wave.
  • They are for one droplet type and one certificate setup, in a campaign about HTTP servers, not a fleet. We have not run a wave against a degraded fleet on either project.
  • We have not published CPU per handshake. Handshakes per second at saturation is related to it but is not the same number.
  • The figures differ a little between pages because each one is a median of ratios against one rival on the same droplet; the 9,396 and 12,388 here are the pair measured against axum.
  • These figures say nothing about Microsoft's systems, or about any outage.

A handshake and reconnection checklist

Ten items a team can start next week. Record each result with its build and date, as the method asks.

  1. Measure full and resumed handshakes per second per server, with the real certificate, and write the number down.
  2. Count how many of your connections are new in a normal minute, and how many would be after a restart of one node.
  3. Find the shortest window in which your clients can all come back, and compare the offered rate with the ceiling of the fleet minus a third.
  4. Check that ticket keys are shared across every server behind one name, and that they rotate.
  5. Check how often a certificate or ticket-key rotation leaves every returning client with a full handshake.
  6. Replace fixed retry delays with exponential backoff and jitter in every client you control.
  7. Turn dropped connections in deploys into drained ones, and check that the load balancer supports it.
  8. Put a limit on new connections per second, and test that the server refuses quickly when it is exceeded.
  9. Run the connection-storm test in staging, with full and resumed TLS and with request traffic running.
  10. Add the handshake ceiling to the release checks, so a change that lowers it fails the build.

Common questions

Is TLS expensive?

Per request on an open connection, usually not: Google reported in 2010 that TLS took under 1% of the CPU on its production front ends. The cost is in setting a connection up, and it matters when many connections are set up at once.

Does TLS 1.3 fix the handshake cost?

It removes a round trip, so a client waits less. The server still does a key exchange and, in a full handshake, signs with the certificate's key, so the CPU cost per new connection does not go away. Measure it on your own servers.

Is session resumption worth it?

It lets a returning client skip the certificate and the signature. In our own test, resumed handshakes ran about a third faster than full ones, not many times faster, because a resumption with forward secrecy still does a key exchange. It also needs ticket keys that every server can read, and it must be rotated.

Should I turn on 0-RTT?

Only for requests that are safe to replay, if at all. RFC 8446 says 0-RTT data has no guarantee of non-replay between connections and is not forward secret. simoom never accepts it.

How do I find my own limit?

Open new connections at a set rate against one server, with a client that does not reuse them, and raise the rate until p99 or the error rate moves. Do it with full handshakes and with resumed ones, and watch CPU. Test 3 of the degraded-fleet test is the procedure.

Sources

Checked on 2026-11-13.

  1. IETF, RFC 8446, The Transport Layer Security (TLS) Protocol Version 1.3: section 2.2 (resumption and pre-shared keys), section 4.6.1 (tickets and their lifetime), section 8 and appendix E.5 (0-RTT and replay).
  2. IETF, RFC 5077, TLS session resumption without server-side state: the TLS 1.2 ticket mechanism that RFC 8446 replaces.
  3. Adam Langley, quoted in Is TLS Fast Yet?: "Overclocking SSL" and the advice to run openssl speed.
  4. nginx, ngx_http_ssl_module: ssl_session_ticket_key and sharing keys between servers.
  5. Amazon Builders' Library, Timeouts, retries and backoff with jitter.
  6. IETF, RFC 9113, HTTP/2: the GOAWAY frame, section 6.8.
  7. Google, Site Reliability Engineering, chapter 22, "Addressing Cascading Failures": load shedding, slow start-up and cold caching.
  8. simoom, TLS guide: tickets are on by default with rotating keys; 0-RTT is never accepted.
  9. Grafana k6, options reference: noConnectionReuse.
  10. Cameleering measurements: the ledger and simoom benchmarks. The headroom context is in the headroom essay.