Sign inSign up

pqcrypto/pqcrypto-pqc-nginx

By pqcrypto

•Updated 3 months ago

Post-quantum TLS for NGINX 1.31.2 on OpenSSL 4.0.1 — ML-KEM & ML-DSA, hybrid key exchange

Image
Networking
Security
Web servers
1

1.6K

pqcrypto/pqcrypto-pqc-nginx repository overview

⁠pqcrypto-pqc-nginx

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

⁠Supported algorithms

CategoryAlgorithms
SignaturesML-DSA-44 / 65 / 87 (Dilithium)
SignaturesSLH-DSA SHA2 / SHAKE — 128 / 192 / 256, s/f (SPHINCS+)
Key exchangeML-KEM-512 / 768 / 1024 (Kyber)
Hybrid KEMX25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024
ClassicalRSA, ECDSA, X25519, X448, P-256 / 384 / 521

All from the OpenSSL default provider compiled into nginx — no extra modules.

⁠Tags

TagDescription
1.31.2.4.0.1nginx 1.31.2 + OpenSSL 4.0.1
latestSame build as the current version tag

Platforms: linux/amd64, linux/arm64.

⁠Image facts

Port4433 (TLS)
Usernon-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)

⁠Quick start

The image ships without a certificate — supply your own PQC cert + key.

⁠1. Generate a PQC test certificate — using the image itself

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
⁠2. Run
# 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
⁠docker compose
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
⁠Kubernetes
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 }

⁠Verify the PQC handshake

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 ...

⁠Customizing the config

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;

⁠Notes

  • TLS 1.3 is required for ML-KEM hybrid key exchange.
  • nginx must be ≥ 1.29.8 to build against OpenSSL 4.0 (this image uses 1.31.2).
  • OpenSSL is statically linked into nginx, so the runtime image carries no external libssl.

⁠License

  • nginx — 2-clause BSD
  • OpenSSL — Apache License 2.0

© PQCrypto AI, Inc.

Tag summary

Content type

Image

Digest

sha256:527d73ff1…

Size

38.5 MB

Last updated

3 months ago

docker pull pqcrypto/pqcrypto-pqc-nginx