Post-quantum TLS on Ubuntu 22.04 / 24.04 with OpenSSL 3.0
Updated:
Ubuntu 22.04 and 24.04 ship OpenSSL 3.0. ML-KEM arrived in OpenSSL 3.5, so nginx or Apache built against the system library cannot negotiate X25519MLKEM768, whatever the config says. That is why so many servers scan as "classical" even though Chrome and Firefox offer the hybrid exchange on every connection. There are three ways out; pick the first one that fits.
openssl version # OpenSSL 3.0.x on Ubuntu 22.04 / 24.04 nginx -V 2>&1 | grep -o 'OpenSSL [0-9.]*' # the library nginx was built with
Option 1 (easiest): put Caddy or a CDN in front
Caddy is written in Go, whose TLS stack supports X25519MLKEM768 by default, independent of the system OpenSSL. Let Caddy terminate TLS on port 443 and pass requests to your existing server on localhost. This very site runs this way and grades A.
# /etc/caddy/Caddyfile
example.com {
reverse_proxy 127.0.0.1:8080 # nginx/Apache now listen on 8080 without TLS
}
Caddy obtains and renews the certificate itself. Move nginx's listen 443 ssl to listen 127.0.0.1:8080, reload both, and scan again. A CDN that already offers the hybrid (for example Cloudflare's proxy) has the same effect for visitors; the connection from the CDN to your origin is a separate leg.
Option 2 (production nginx): OpenSSL 3.5
Either move to a release that ships OpenSSL 3.5 (Debian 13 "trixie" does; check openssl version on newer Ubuntu releases), or build nginx against an OpenSSL 3.5 source tree. nginx links it statically, so the system library stays untouched:
sudo apt install -y build-essential libpcre2-dev zlib1g-dev # download and verify the nginx and OpenSSL 3.5.x source tarballs from nginx.org and openssl-library.org cd nginx-1.x.y ./configure --with-openssl=../openssl-3.5.x --with-http_ssl_module --with-http_v2_module --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx # match your current nginx -V flags make -j"$(nproc)" && sudo make install
Copy the remaining configure arguments from your current nginx -V output so modules and paths stay the same. Then follow the nginx guide for the ssl_ecdh_curve line. A self-built nginx no longer receives Ubuntu security updates: you have to rebuild it for every nginx or OpenSSL fix.
Option 3 (testing only): oqs-provider on OpenSSL 3.0
The Open Quantum Safe project's oqs-provider adds ML-KEM to OpenSSL 3.0 as a loadable provider, built on liboqs. The project itself states that it is intended for prototyping and evaluation, not production. Use it on a test server to try the hybrid exchange before you upgrade.
sudo apt install -y build-essential cmake ninja-build libssl-dev git git clone https://github.com/open-quantum-safe/liboqs.git cmake -S liboqs -B liboqs/build -GNinja -DBUILD_SHARED_LIBS=ON sudo cmake --build liboqs/build --target install && sudo ldconfig git clone https://github.com/open-quantum-safe/oqs-provider.git cmake -S oqs-provider -B oqs-provider/build -GNinja sudo cmake --build oqs-provider/build --target install openssl version -m # MODULESDIR: where oqsprovider.so was installed
Do not edit the system-wide /etc/ssl/openssl.cnf: apt, curl, Python and every other program read it. Give nginx its own file instead:
# /etc/nginx/openssl-oqs.cnf openssl_conf = openssl_init [openssl_init] providers = provider_sect [provider_sect] default = default_sect oqsprovider = oqs_sect [default_sect] activate = 1 [oqs_sect] activate = 1
sudo systemctl edit nginx # add: [Service] Environment=OPENSSL_CONF=/etc/nginx/openssl-oqs.cnf
Check the exact group name your build offers with OPENSSL_CONF=/etc/nginx/openssl-oqs.cnf openssl list -kem-algorithms, put it first in ssl_ecdh_curve (for example X25519MLKEM768:X25519:prime256v1), then nginx -t && systemctl restart nginx. These steps follow the OQS documentation; we have not run them on our own servers, so read the project's README for the requirements of the version you build.
Verify
Run the free scan: grade A means TLS 1.3 with a hybrid post-quantum key exchange. The scan also answers a HelloRetryRequest the way browsers do, so servers that ask for a different group are measured too. For continuous checks, the GitHub Action fails a build when the hybrid disappears.
Sources: OpenSSL 3.5 release notes (ML-KEM), nginx --with-openssl build option, Open Quantum Safe liboqs and oqs-provider READMEs, Go crypto/tls default key exchanges.