A TLS flood never asks your server for anything. It opens a connection, lets the server complete the full cryptographic handshake, then destroys the socket. No request line, no Host header, no path. Nothing your web application firewall can read, because a firewall that inspects requests needs a request to exist.
That is what makes this one awkward. The traffic is well-formed. Every connection is a legitimate TLS negotiation your server chose to complete. The cost is real and it lands on your side, because the arithmetic of public-key cryptography is not symmetrical. The client spends very little to ask. The server spends a great deal to answer.
The short version
- The server pays and the client barely does. Obsidio’s own measurements put the server-side CPU cost of a completed handshake at roughly fifteen times the client’s. That asymmetry is the whole attack.
- There is no HTTP layer. The handshake finishes and the connection is abandoned before a byte of application data moves either way.
- A WAF sees literally nothing. It operates on requests, at a layer this traffic never reaches, so tuning it differently would not help.
- Where you terminate TLS decides the outcome. Handshakes landing on a CDN or load balancer built to absorb them never reach your origin. Landing on your origin, they land on your CPU.
- Session resumption is not the fix people assume. It needs a key from a previous handshake, and a fresh connection has nothing to resume.
This is one of a series covering each of the ten simulation types Obsidio runs, one post per attack. They fail in genuinely different places, which is why a test programme needs coverage across the categories rather than volume in one. For how they fit together, start with our guide on how to test your DDoS protection.
The ten simulation types
One post each, grouped the way the platform groups them.
Application-layer floods
Slow attacks
Connection exhaustion
- TLS Floodyou are here
- TCP Connection Flood
Protocol-specific and scripted
- DNS Floodcoming soon
- Puppeteer Scriptfunctional load, not an attack
What a TLS flood actually does
The client opens a TLS connection and lets the server run the negotiation to the end: key exchange, certificate, session setup. The moment the secure connection is established, a very short idle timeout expires and the socket is destroyed. Then the slot opens another. The server has done the expensive part and learned nothing about what the client wanted, because the client never said.
The expense is worth naming precisely. In a TLS 1.3 full handshake the server sends its key share, its certificate, and a CertificateVerify message that RFC 8446 describes as “a signature over the entire handshake using the private key”. TLS 1.3 also removed the static RSA and static Diffie-Hellman cipher suites, so every full handshake now includes an ephemeral key exchange. Private-key signing plus ephemeral key agreement, once per connection, before the server has any idea whether the connection is worth serving.
The client’s half is cheap. It does its side of the key exchange, signs nothing, and throws the session away microseconds later without validating the certificate. Obsidio’s own measurements put the server’s CPU cost at around fifteen times the client’s. That figure is our instrumentation, not a published standard, and the multiple moves with your key type, cipher selection and whether offload hardware sits in the path. The direction never moves.
Twenty people walk up to a high-security door. The guard runs the full identity check on each one, every document, every signature. Each then turns around and leaves. The guard burns out on visitors who never enter, and there is no visitor record afterwards because nobody was admitted.
The naming is historical. Obsidio’s internal identifier is ssl-flood and the attack is still widely discussed as an SSL flood, but the implementation uses modern TLS with Server Name Indication set to the target host.
Which of your controls should have caught this
It is worth walking the whole chain a connection passes through, because the answer is rarely the control people expect. Three groups matter: what sits in front, what absorbs whatever gets through, and whether anything tells you it happened.
Zone 1
Should any of these have stopped it?
- DDoS protection and scrubbing. Bandwidth stays low, so a volume threshold is the wrong trigger. Ask your provider whether it watches handshake rate per source.
- Firewall and WAF. A WAF sees literally nothing. AWS describes its own as monitoring “the HTTP and HTTPS requests that are forwarded to your protected web application resources”. No request is ever forwarded.
- Rate limiting. Usually keyed on requests. nginx documents that a connection is counted “only if it has a request being processed by the server and the whole request header has already been read”.
- Bot detection and challenges. A challenge is delivered in an HTTP response. Nothing gets that far.
- CDN. Potentially decisive, for one reason: it terminates TLS. Caching is irrelevant. Whether your origin still answers on its own IP settles it.
Zone 2
What takes the hit?
- Load balancer or reverse proxy. Absorbs the whole attack if it terminates TLS itself. Worth confirming yours does and that nothing bypasses it.
- Connection layer. Churns rather than fills. Sockets appear and vanish, so the table never looks alarming.
- TLS stack and CPU. This is where it bites. Asymmetric crypto per handshake, at whatever rate the fleet can sustain.
- Application and database. Never reached, because no request is ever routed to them.
- Auto-scaling. Depends on your scaling metric. Scale on CPU and it may respond. Scale on request rate and it never wakes.
Zone 3
Would you find out?
- Dashboards. The request-rate graph reads flat. The CPU graph on your TLS terminators does not. Whether anyone watches the second is the finding.
- Access logs and your SIEM. Access logs are written per request. There are no requests, so little to correlate afterwards.
- A check from outside your network. The one thing that reliably catches this, if you run one.
- Alerting. Depends on whether a CPU threshold exists and where it sits.
- On-call. Follows from the above. Nothing crosses a threshold, nobody is paged.
Purple marks what only a test on your own infrastructure can settle. The rest follows from how the attack works, not from how well your team configured things.
Zone 3 is unusual here. The signal exists, on a CPU graph most teams already have. The question is whether anyone ever wired it to an alert.
What you control, and how this differs from a TCP connection flood
Obsidio exposes four parameters, and two of them change the character of the run:
- Concurrent handshakes per worker. Twenty by default, adjustable from one to a hundred. It scales the simultaneous handshake load linearly.
- Idle timeout. One millisecond by default. At that setting the socket is destroyed almost the instant the secure connection is established, so the server pays the crypto cost and the client leaves. Raise it and sockets are held longer, occupying ports and file descriptors at the price of a lower handshake rate. You are choosing between CPU pressure and connection-table pressure.
- Think time. A pause between handshakes within each slot, zero by default. It paces the run down linearly when you want a sustained level rather than a peak.
- Port. Defaults to 443. Certificate validation is off on the client side, so an expired or self-signed certificate on a test endpoint will not abort the handshake.
The two attacks sit in the same category and behave nothing alike. A TCP connection flood does no negotiation beyond the TCP handshake, sends no payload, and holds each idle socket for minutes. Its target is your connection table: file descriptors, connection-tracking entries, the accept queue. A TLS flood is the mirror image. Its sockets live for milliseconds and its target is your processor. Generous connection limits with a modest CPU fail the TLS flood and shrug off the connection flood, and a fast CPU behind a tight file-descriptor limit does the reverse. Running one and calling the category covered is how that gets found late.
How to defend against a TLS flood
Where TLS terminates decides almost everything, so start there.
- Terminate TLS somewhere built to absorb handshakes. A CDN or managed load balancer splits the path in two. Cloudflare’s Full mode documents this shape: HTTPS between the visitor and Cloudflare, with Cloudflare making its own connection to the origin. AWS documents the same structure for an Application Load Balancer, which “maintains two connections: one connection with the client and one connection with a target”. The usual caveat applies: a proxied site whose origin IP still answers on 443 is not protected, only obscured.
- Do not file session resumption under mitigation. It is a performance feature and does nothing here. RFC 8446 explains why: resumption uses a pre-shared key identity the server issued after a handshake had already completed, and only then can the derived key “bootstrap the cryptographic state instead of a full handshake”. A connection with no prior session has nothing to present. nginx ships
ssl_session_cache none,ssl_session_timeout 5mandssl_session_tickets on, all of which govern returning users, not your defensive posture. - Check which layer your connection limits live at. This is the one most likely to be quietly wrong. nginx ships two per-source connection limiters. The HTTP one counts a connection only once a full request header has been read, so it never sees this traffic. The stream-level equivalent, available since nginx 1.9.3, counts connections per key without that condition. If your only per-source cap is the HTTP one, it is not in this path.
- Measure your handshake headroom, because nobody can quote it to you. Handshakes per second depends on your certificate key type, cipher selection, processor and whether offload hardware is in the path. We found no published figure worth repeating and will not invent one. One related default: nginx’s stream
ssl_handshake_timeoutis sixty seconds, which bounds slow handshakes and does nothing about fast ones.
What you can check yourself, and what it will get wrong
Several of these you can answer today without help. It is worth knowing where a cheap check misleads you.
Read the configuration. Establish where TLS terminates, whether your origin answers directly on its own IP, and whether your per-source connection limit sits at the HTTP layer or below it. This takes an afternoon and it tells you the design is sound. It does not tell you the processor holds.
Test alert routing on its own. Fire a synthetic CPU alert and see whether it reaches the right rotation and whether anyone acts. That verifies the plumbing and needs no attack traffic.
Run a single-source test against a staging environment, with change-management sign-off, on infrastructure you own. This is legitimate and useful. It is also where the trap sits.
A test from one machine does not merely fail to answer the Zone 1 questions. It answers several of them wrongly, in a reassuring direction. Your per-source connection limit catches one address immediately. Reputation filtering handles a datacenter IP without breaking stride. The asymmetry works against you too: one client’s processor caps how many handshakes it can initiate, so the peak you reach says more about your test machine than your defenses. Every control in Zone 1 appears to hold, and you conclude you are covered.
Then real traffic arrives from thousands of residential addresses across dozens of countries, no single source doing anything remarkable, and those same controls behave completely differently, because they key on source concentration and traffic realism. One machine reproduces neither. The cheap check produces a false pass on the controls you most need to trust, and a synthetic alert cannot tell you whether anyone would have read a rising processor curve as an attack rather than a bad deploy.
How Obsidio tests a TLS flood
Authorization comes first. Domain ownership is verified by DNS TXT record before any traffic flows, runs ramp up gradually, and any run can be aborted live. You choose the concurrency, the idle timeout and the pacing. Load comes from 100,000+ globally distributed real devices rather than a handful of datacenter instances, so a per-source cap gets tested the way distributed traffic would test it.
Two things to watch:
- Handshakes per second, not request rate. Each completed handshake counts as one request in the report, so the rate figure here is a handshake rate. Compare it against whatever headroom you measured beforehand.
- Post-handshake connection counts. Active and peak connections show how many sockets sit between handshake completion and teardown. At the default idle timeout that stays low even at full pressure, and a low count alongside a high handshake rate is the signature of this attack rather than a connection flood.
The split is worth stating plainly. Obsidio reports how many handshakes completed, how many failed, how your edge controls responded and what the connection counts did, because all of that is visible from outside. Whether the processors on your TLS terminators saturated, whether your SIEM recorded anything and whether anyone was paged live only on your own consoles. Have someone watching them during the window, or those questions go home unanswered.
Strength is proven, not promised. A TLS flood does not ask your server for anything. It asks it to prove who it is, thousands of times over, and then walks away without a word.
Where this fits in a testing programme
TLS Flood belongs in the connection-exhaustion category alongside TCP Connection Flood. Both bypass the application entirely, and between them they separate two failure modes teams habitually conflate. One exhausts the crypto path, the other the connection table. Testing only the second leaves your handshake capacity an open question, and no application-layer flood test closes it, because an HTTP flood amortises handshake cost across reused connections.
The ten simulation types fail in genuinely different places. Slow attacks like Slowloris starve your connection pool by never finishing a request. A TLS flood finishes everything it starts and leaves. Coverage across the categories is the point, not volume in any one of them.
For regulated institutions the test also has to leave a record. Obsidio produces cryptographically attested, tamper-evident reports mapped to FINMA, DORA and NIS2, generated inside Trusted Execution Environments, so the result of a run is an artifact you can file rather than a screenshot you have to vouch for.
To scope an authorized TLS flood simulation against your own infrastructure, see the Obsidio platform or get in touch. Independent, neutral, verifiable: resilience you can prove.
