Kernel TLS (KTLS)
KTLS moves TLS record encryption out of NGINX and into the Linux kernel. Once
a session's keys are handed to the kernel, static files are served with
SSL_sendfile(): file pages go from the page cache straight to encrypted
packets, without ever being copied into user space.
Every GetPageSpeed NGINX build — the stock nginx package, nginx-mod and
edge — links against openssl35, which we compile with enable-ktls.
There is nothing to install or rebuild: KTLS capability is already in the
binary you are running, and it stays completely inactive until you switch it
on per server block.
Should you enable it?
KTLS is opt-in for a reason. Our measurements (NGINX 1.30.4, OpenSSL 3.5, TLS 1.3, AES-GCM, static file workload):
- EL9 stock kernel 5.14, x86_64 — roughly CPU-neutral. The kernel's TLS path and OpenSSL's AES-NI path cost about the same; there is little to gain.
- Kernel 6.8+ — a large win: about a third less NGINX worker CPU and
substantially higher throughput on the same hardware, thanks to
SSL_sendfile()and a faster in-kernel crypto path.
The benefit is concentrated in TLS-heavy static file serving (downloads,
package repositories, media). Proxied responses do not use SSL_sendfile()
and gain much less. KTLS applies to TCP only — HTTP/3 (QUIC) is unaffected.
Do not enable on Rocky/RHEL 9.7 kernel 5.14.0-611.24.1.el9_7
This specific EL 9.7 kernel corrupts data sent through KTLS
sendfile(): clients receive files whose checksums do not match.
We found this during release validation (our OpenSSL test suite carries a
skip for exactly this kernel). Since stock EL9 gains ~nothing from KTLS
anyway, the safe rule is simple: only enable KTLS when running a modern
kernel, e.g. kernel-lt / kernel-ml
6.x. Verify integrity after enabling (below) — a checksum comparison
over HTTPS takes a minute.
Enabling KTLS
Requirements: the tls kernel module (present in EL distro kernels and our
kernel-lt/kernel-ml builds; it auto-loads on first use) and TLS 1.2/1.3
with an AES-GCM or ChaCha20-Poly1305 cipher.
In the server block where you want it:
server {
listen 443 ssl;
...
sendfile on;
ssl_conf_command Options KTLS;
}
sendfile on; is usually already inherited from the http block; it is what
unlocks the zero-copy SSL_sendfile() path.
ssl_conf_command inheritance
ssl_conf_command directives are inherited from the http level only
when the server block defines none of its own. If you already use
ssl_conf_command at the http level, repeat those commands in any
server block where you add Options KTLS.
Verifying
The kernel counts KTLS sessions and errors:
$ cat /proc/net/tls_stat
TlsCurrTxSw 3
TlsTxSw 412
TlsTxSwError 0
TlsDecryptError 0
...
TlsTxSw climbing while you make HTTPS requests confirms KTLS transmit is
active (TlsRxSw stays 0 — NGINX only offloads the send side).
TlsTxSwError and TlsDecryptError must stay at 0.
Then confirm integrity end-to-end — this is the check that catches a bad kernel:
$ curl -so /tmp/f https://your.server/some-large-file
$ sha256sum /tmp/f # compare against the file on the server
If checksums mismatch, disable KTLS and update the kernel before re-trying.