How to Enable Hybrid PQC Key Exchange on Apache & Nginx Using ML-KEM
This lab aims to integrate ML-KEM into Apache and Nginx web servers to mitigate Harvest Now, Decrypt Later (HNDL) attacks, in alignment with regulatory guidance from ENISA, BSI (Germany), and NCSC pushing organizations toward Hybrid Post-Quantum Cryptography (PQC).
What is HNDL?
Harvest Now, Decrypt Later (HNDL) is a cyberattack strategy in which adversaries collect encrypted data today and store it until future quantum computers become capable of decrypting it. Also known as Store Now, Decrypt Later, HNDL creates an immediate risk for sensitive data that must remain confidential for years or decades.[1]
Key points:
- HNDL is a present-day risk: Attackers can intercept and store encrypted data now, then decrypt it once sufficiently powerful quantum computers are available.
- Long-lived sensitive data is most exposed: Government records, financial data, healthcare information, intellectual property, and defense data may retain value for decades.
- Public-key cryptography is the primary concern: RSA and Elliptic Curve Cryptography (ECC) are expected to be vulnerable to cryptographically relevant quantum computers.
- Q-Day does not need to arrive for the risk to begin. The threat starts the moment encrypted data is intercepted, copied, or stolen — not when quantum computers become sufficiently powerful.
ML-KEM addresses this issue by using mathematical problems that are believed to be computationally hard for both classical and quantum computers to solve.
ML-KEM
ML-KEM is an algorithm that runs on classical computers, but relies on mathematical problems that are computationally hard to solve for both classical and quantum computers. It was standardized by NIST under FIPS 203.
ML-KEM allows two devices to securely agree on a shared secret key over an insecure channel. The process involves three main operations:
- Key Generation (KeyGen): The client generates a public/private key pair. The public key (encapsulation key) is shared with the server. The private key (decapsulation key) is kept secret.
- Encapsulation (Encaps): The server uses the client’s public key to generate a random shared secret and a ciphertext. Then the server sends the ciphertext to the client.
- Decapsulation (Decaps): The client uses their private key to recover the same shared secret. Both devices now hold an identical key for symmetric encryption.
NIST defines three parameter sets for different use cases:
- ML-KEM-512: Smallest footprint, highest performance
- ML-KEM-768: Balanced security and performance for most applications
- ML-KEM-1024: Maximum security for long-lived systems
Even though ML-KEM protects the key exchange phase, the TLS certificate is still typically signed using classical algorithms such as ECDSA (e.g., P256). This means the authentication layer remains vulnerable to future quantum attacks. Full post-quantum TLS requires deployment of ML-DSA (FIPS 204) certificates in addition to ML-KEM key exchange.
Negative effects of enabling ML-KEM into webserver
Before enabling ML-KEM, you should be aware of two main trade-offs introduced by hybrid PQC key exchange:
Network Packet Size Increase (Fragmentation Risk)
A classical key exchange using X25519 requires only 32 bytes of data. The hybrid key exchange mechanism X25519MLKEM768 requires roughly 1,200 bytes. The 1,200-byte ClientHello introduced by hybrid key exchange can exceed MTU limits in enterprise networks. This may trigger fragmentation issues or failures in environments with strict firewalls or broken PMTUD (Path MTU Discovery). Testing behind load balancers and security appliances is strongly recommended before production rollout.
Increased CPU Overhead
Your server must now run two cryptographic operations simultaneously to establish a single shared secret:
- The classical operation — X25519 elliptic curve math
- The post-quantum operation — ML-KEM lattice-based math
Consequently, each new TLS connection requires roughly twice the CPU resources of a classical-only handshake. Under high traffic, this can lead to:
- Increased CPU utilization per connection
- Reduced maximum concurrent connections before performance degrades
- Higher latency on resource-constrained servers
The tradeoff is clear: ML-KEM is fast (faster than RSA, comparable to ECDH) but has significantly larger keys and ciphertexts.
I built a lab to integrate ML-KEM into web servers (Apache and Nginx). The following sections explain the process step by step.
Lab Requirements
Nginx: Version 1.19.4 or later required for ssl_conf_command directive support
Apache: Version 2.4.36 or later
Get Mohammad Amin Kokash’s stories in your inbox
Join Medium for free to get updates from this writer.
OpenSSL: Version 3.5+ required for X25519MLKEM768 support (openssl version should be checked before setup). Most Ubuntu LTS versions ship with older OpenSSL (3.0.x or 3.2.x), which does not support hybrid PQC groups.
Ubuntu: Any version that supports the above web server versions
Browser: Google Chrome version 100 (used for compatibility testing with hybrid PQC key exchange groups)
Hybrid cryptography key exchange into Nginx
First, generate a private key and a self-signed certificate using OpenSSL. These will be used by Nginx. The choice of algorithm used to generate and sign the certificate is independent of ML-KEM because ML-KEM affects the key exchange mechanism rather than the certificate itself.
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -x509 -nodes -days 365 -out server.crt -keyout server.key -sha256 -subj "/CN=localhost"Open the default Nginx server configuration file.
sudo nano /etc/nginx/sites-enabled/defaultPaste the configuration. Please note I enable only TLSv1.3 it is better to enable TLSv1.2 also for client interoperability
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name localhost;
ssl_certificate /home/mokokash/Desktop/server.crt;
ssl_certificate_key /home/mokokash/Desktop/server.key;
ssl_protocols TLSv1.3;
ssl_conf_command Curves X25519MLKEM768:X25519;
location / {
root /var/www/html;
index index.html index.htm;
}
}Test the configuration and restart Nginx.
nginx -t
sudo systemctl restart nginxAs shown below, the updated browser successfully negotiates ML-KEM.
Important: X25519MLKEM768 is only supported in TLS 1.3. Any TLS 1.2 session will automatically fall back to classical X25519 (ECDH). This means TLS 1.2 connections do not benefit from post-quantum key exchange.
You can verify whether ML-KEM was negotiated using OpenSSL:
openssl s_client -connect localhost:443 -groups X25519MLKEM768Moving to Apache
Hybrid cryptography key exchange into Apache
The configuration steps for Apache are slightly different, but the overall concept remains the same.
Generate a self-signed certificate.
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \ -x509 -nodes -days 365 \ -out server.crt -keyout server.key \ -sha256 -subj "/CN=localhost" sudo mkdir -p /etc/apache2/ssl sudo mv server.crt server.key /etc/apache2/ssl/ sudo chmod 600 /etc/apache2/ssl/server.keyConfigure HTTP → HTTPS Redirect
This ensures that all traffic is redirected to HTTPS so that communications remain protected.[4]
sudo nano /etc/apache2/sites-available/000-default.conf<VirtualHost *:80>
ServerName localhost
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
# Redirect all HTTP traffic to HTTPS
Redirect permanent / https://localhost/
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>Move to modifying Apache settings
sudo nano /etc/apache2/sites-available/default-ssl.confPaste the following configuration into the file.
<VirtualHost *:443>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
# TLS 1.3 only
SSLProtocol -all +TLSv1.3
# Hybrid PQC Key Exchange (ML-KEM + X25519 classical fallback)
SSLOpenSSLConfCmd Curves X25519MLKEM768:X25519
# Hybrid cipher suites
SSLOpenSSLConfCmd Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
</VirtualHost>Enable and Restart
sudo apache2ctl configtest
sudo a2enmod ssl
sudo a2ensite default-ssl
sudo a2ensite 000-default
sudo systemctl restart apache2Glossary
[1]- https://www.paloaltonetworks.com/cyberpedia/harvest-now-decrypt-later-hndl
[2]- https://nginx.org/en/docs/http/ngx_http_ssl_module.html
[4]-https://linuxize.com/post/redirect-http-to-https-in-apache/
[5]-https://www.qcecuring.com/blog/ml-kem-kyber-post-quantum-standard






