Post-quantum TLS for NGINX 1.31.2 on OpenSSL 4.0.1 — ML-KEM & ML-DSA, hybrid key exchange
1.6K
Post-quantum nginx — nginx 1.31.2 statically linked against OpenSSL 4.0.1, which ships ML-KEM, ML-DSA and SLH-DSA (SPHINCS+) in its default provider. No liboqs / oqs-provider — pure native PQC.
Built for Kubernetes / Docker Compose. The PQC server certificate and key are mounted at runtime (volume / Secret) and are never baked into the image.
docker pull pqcrypto/pqcrypto-pqc-nginx:latest
| Category | Algorithms |
|---|---|
| Signatures | ML-DSA-44 / 65 / 87 (Dilithium) |
| Signatures | SLH-DSA SHA2 / SHAKE — 128 / 192 / 256, s/f (SPHINCS+) |
| Key exchange | ML-KEM-512 / 768 / 1024 (Kyber) |
| Hybrid KEM | X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024 |
| Classical | RSA, ECDSA, X25519, X448, P-256 / 384 / 521 |
All from the OpenSSL default provider compiled into nginx — no extra modules.
| Tag | Description |
|---|---|
1.31.2.4.0.1 | nginx 1.31.2 + OpenSSL 4.0.1 |
latest | Same build as the current version tag |
Platforms: linux/amd64, linux/arm64.
| Port | 4433 (TLS) |
| User | non-root nginx (uid/gid 101) |
| Cert path | /etc/nginx/pki/server.crt (mount this) |
| Key path | /etc/nginx/pki/server.key (mount this) |
| Web root | /usr/share/nginx/html |
| Config | /etc/nginx/nginx.conf (TLS 1.3, PQC hybrid groups) |
The image ships without a certificate — supply your own PQC cert + key.
Most hosts' local openssl is older than 3.5 and can't create ML-DSA keys. So mint a
throwaway self-signed ML-DSA-87 cert with the OpenSSL 4.0.1 bundled in this image
and write it straight into a local pki/ folder — no PQC OpenSSL needed on your machine:
mkdir -p pki
docker run --rm -u "$(id -u):$(id -g)" -v "$PWD/pki:/out" \
pqcrypto/pqcrypto-pqc-nginx:latest sh -c '
openssl genpkey -algorithm ML-DSA-87 -out /out/server.key
openssl req -x509 -new -key /out/server.key -out /out/server.crt -nodes -subj "/CN=localhost" -days 365
'
This leaves pki/server.key and pki/server.crt on your host, owned by you
(-u "$(id -u):$(id -g)"). Inspect it:
docker run --rm -v "$PWD/pki:/out" pqcrypto/pqcrypto-pqc-nginx:latest \
openssl x509 -in /out/server.crt -noout -subject -dates
# remove any previous container of the same name first (ignore if none)
docker rm -f pqc-nginx 2>/dev/null
docker run -d --name pqc-nginx -p 4433:4433 \
-v "$PWD/pki/server.crt:/etc/nginx/pki/server.crt:ro" \
-v "$PWD/pki/server.key:/etc/nginx/pki/server.key:ro" \
pqcrypto/pqcrypto-pqc-nginx:latest
docker ps --filter name=pqc-nginx
services:
pqc-nginx:
image: pqcrypto/pqcrypto-pqc-nginx:1.31.2.4.0.1
ports:
- "4433:4433"
volumes:
- ./pki/server.crt:/etc/nginx/pki/server.crt:ro
- ./pki/server.key:/etc/nginx/pki/server.key:ro
restart: unless-stopped
kubectl create secret tls pqc-nginx-tls \
--cert=pki/server.crt --key=pki/server.key
apiVersion: apps/v1
kind: Deployment
metadata:
name: pqc-nginx
spec:
replicas: 2
selector: { matchLabels: { app: pqc-nginx } }
template:
metadata: { labels: { app: pqc-nginx } }
spec:
securityContext: { runAsNonRoot: true, runAsUser: 101, runAsGroup: 101 }
containers:
- name: pqc-nginx
image: pqcrypto/pqcrypto-pqc-nginx:1.31.2.4.0.1
ports: [ { containerPort: 4433 } ]
volumeMounts:
- { name: pqc-tls, mountPath: /etc/nginx/pki, readOnly: true }
volumes:
- name: pqc-tls
secret:
secretName: pqc-nginx-tls
items:
- { key: tls.crt, path: server.crt }
- { key: tls.key, path: server.key }
Connect offering an ML-KEM hybrid group. The client also needs a PQC-capable
OpenSSL (3.5+ / 4.0) — most hosts' local openssl is older and will fail on
-groups X25519MLKEM768. The reliable way is to use this image's own 4.0.1 openssl
as the client, so you don't depend on what's installed locally:
docker run --rm --network host pqcrypto/pqcrypto-pqc-nginx:latest sh -c \
'echo | openssl s_client -connect 127.0.0.1:4433 -groups X25519MLKEM768 -tls1_3 2>&1 \
| grep -iE "Negotiated TLS|Peer signature type|Cipher is"'
If your local OpenSSL is already 3.5+ / 4.0 you can run it directly instead:
echo | openssl s_client -connect localhost:4433 -groups X25519MLKEM768 -tls1_3 2>&1 \
| grep -iE "Negotiated TLS|Peer signature type"
Expected:
Peer signature type: mldsa87
Negotiated TLS1.3 group: X25519MLKEM768
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
→ ML-DSA-87 server authentication + ML-KEM-768 hybrid key exchange, end to end.
Confirm the OpenSSL link inside the container:
docker run --rm pqcrypto/pqcrypto-pqc-nginx:latest nginx -V 2>&1 | grep -i "built with OpenSSL"
# built with OpenSSL 4.0.1 ...
Override the bundled nginx.conf by mounting your own:
-v "$PWD/nginx.conf:/etc/nginx/nginx.conf:ro"
The default config serves TLS 1.3 on port 4433 with:
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:SecP256r1MLKEM768:SecP384r1MLKEM1024:X25519;
libssl.© PQCrypto AI, Inc.
Content type
Image
Digest
sha256:527d73ff1…
Size
38.5 MB
Last updated
3 months ago
docker pull pqcrypto/pqcrypto-pqc-nginx