TLS Hardening — Validation Checks
An opt-in section. Nothing here is measured and nothing here affects a score — it is a hardening bar an entry chooses to be held to, and passing it earns the TLS badge on the HTTP/1.1 composite.
Subscribe with the tls_check field in meta.json — a capability the entry opts into, not a profile it is measured on, so it is its own field rather than an entry in tests:
"enabled": true,
"tls_check": true
A listener of its own, on :9000
The section needs a second TLS listener on port 9000, reading its certificate and key from /certs-tls. That directory is mounted for this entry alone and seeded from the usual pair.
This is not incidental. The section replaces certificates underneath a running server, and doing that to the shared /certs would move the ground under json-tls, static-tls and every h2 profile in the same validation run. A dedicated port and a private directory keep it from touching anything else — /certs is never written to.
The listener is HTTP/1.1 over TLS. The h2 and h3 listeners are separate and not covered here, which is why the badge only appears on the H1 composite.
An entry that opts in without opening :9000 fails the section with a clear message rather than being skipped.
Why it is opt-in
Most of these need the entry to have done something deliberate. Binding a certificate once at startup — which is what almost every entry does — fails the first check on this page. Opting in is a statement that the entry has gone further.
Checks
Certificate rotation
The certificate and key at /certs-tls are replaced with a freshly generated RSA-2048 pair while the server is running. The server must serve the new certificate without a restart, within 30 seconds, and must still answer requests on it.
A certificate is renewed roughly every 60 days in production. A server that needs a restart to pick one up is a weaker server, and nothing else in the suite notices the difference.
The usual way to pass is a per-handshake certificate callback rather than a value bound at startup — in Kestrel, ServerCertificateSelector; in Go, tls.Config.GetCertificate; in Rust with rustls, a ResolvesServerCert.
Rotation keeps serving
Thirty requests are issued across the swap. All of them must succeed. Rotating by dropping traffic is not rotating.
SNI
The server must complete a handshake both with a server name and without one. A client that omits SNI has to get a usable answer rather than a dropped connection.
Session resumption
Reported, not required. If the server issues a session ticket, a second connection presenting it should resume. An entry that issues no ticket makes every connection pay a full handshake, which is worth knowing but is not a failure.
close_notify
The server must close at the TLS layer rather than dropping the socket. Without the alert, a truncated response is indistinguishable from a complete one.
Vulnerability suite
testssl.sh -U, which covers Heartbleed, CCS, Ticketbleed, ROBOT, secure renegotiation, CRIME, BREACH, POODLE, TLS_FALLBACK_SCSV, SWEET32, FREAK, DROWN, LOGJAM, BEAST, LUCKY13, Winshock and RC4 — about 30 seconds. Any HIGH or CRITICAL finding fails the section.
Set HTTPARENA_SKIP_TLS_SCAN=1 to skip it; it also skips itself rather than failing an entry when the scanner image is unavailable.
The shared TLS checks
The section also runs everything the TLS-carrying profiles already run — the certificate must be the one the harness mounted, the connection must negotiate TLS 1.3 with an AEAD cipher, ALPN must not name a protocol the client did not offer, and no obsolete protocol or weak cipher may be accepted. See json-tls validation.
The badge
Two badges, and they mean different things:
| badge | meaning |
|---|---|
| green shield | the TLS basics, checked on any entry with a TLS profile |
| gold shield | opted into this section and passed it |
Both are earned by the probes, never declared in meta.json. No badge means not verified — most entries have no TLS profile at all — and never failed.
Running locally
./scripts/validate.sh <framework>
The rotation checks replace files in the private /certs-tls directory and put them back afterwards, including when a check fails midway. The shared certs/ directory is never written to.