Zum Inhalt

handshake-padding: TLS 1.3 handshake record padding against ECH vhost fingerprinting

Installation

You can install this module in any RHEL-based distribution, including, but not limited to:

  • RedHat Enterprise Linux 7, 8, 9 and 10
  • CentOS 7, 8, 9
  • AlmaLinux 8, 9
  • Rocky Linux 8, 9
  • Amazon Linux 2 and Amazon Linux 2023
dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install nginx-module-handshake-padding
yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install nginx-module-handshake-padding

Enable the module by adding the following at the top of /etc/nginx/nginx.conf:

load_module modules/ngx_http_ssl_handshake_padding_module.so;

This document describes nginx-module-handshake-padding v1.0.0 released on Sep 10 2026.


NGINX module that pads outgoing TLS 1.3 handshake records to a multiple of a configured block size, so that virtual hosts sharing an ECH (Encrypted Client Hello) listener cannot be told apart by the exact sizes of their encrypted server handshake flight.

Why

ECH encrypts and pads the client's side of the handshake, but the server's encrypted flight (EncryptedExtensions / Certificate / CertificateVerify / Finished) still has vhost-specific TLS record lengths — TLS 1.3 exposes exact ciphertext sizes, and a single byte of certificate difference is enough to identify which vhost behind an ECH cover a client connected to. Certificate compression does not fix this. In lab and production measurements, every unpadded vhost was uniquely identifiable from record sizes alone, reducing ECH's SNI protection to a size-lookup table.

RFC 9849 §6.1.3 anticipates exactly this and calls for handshake messages to be "padded using TLS record layer padding". This module is that mitigation.

Directive

Syntax:  ssl_handshake_padding <bytes>;
Default: ssl_handshake_padding 0;   (off)
Context: http, server

Pads each TLS 1.3 handshake record's plaintext up to a multiple of <bytes> (0–16384; 0 and 1 disable padding). Recommended value: 512 — it merges all vhosts with same-shape certificate chains (e.g. all Let's Encrypt EC certificates on a server) into one indistinguishable anonymity group, at roughly 1.5 KB per full handshake.

http {
    ssl_handshake_padding 512;
    ...
}

Properties

  • Zero application-data cost — only handshake records are padded. Resumed handshakes carry no Certificate, so their overhead is minimal.
  • No extra round trips at the recommended 512 (and up to 1024). Values ≥ 4096 can push the padded flight beyond a typical initial congestion window and cost +1 RTT on new connections; nginx logs a warning if you configure that.
  • KTLS-compatible — handshake-only block padding does not disable kernel TLS offload.
  • Padding rounds sizes up; a record of wire size L becomes ceil((L - 16) / N) * N + 16 bytes on the wire (16 = AEAD tag + header overhead beyond plaintext).

RPM-based

dnf -y install https://extras.getpagespeed.com/release-latest.rpm dnf -y install nginx-module-handshake-padding

DEB-based

curl -sSL https://extras.getpagespeed.com/deb.sh | bash apt -y install nginx-module-handshake-padding

Then load it:

```nginx
load_module modules/ngx_http_ssl_handshake_padding_module.so;

Or build from source like any nginx dynamic module:

./configure --with-compat --add-dynamic-module=/path/to/ngx_ssl_handshake_padding
make modules