Introduction

NIST finalized three post-quantum cryptography (PQC) standards in 2024: ML-KEM (key encapsulation), ML-DSA (digital signatures), and SLH-DSA (stateless hash signatures). Organizations must migrate before quantum computers can break RSA and ECC. Ansible automates this migration across thousands of systems — updating crypto libraries, rotating certificates, reconfiguring SSH, and validating compliance.

PQC Standards Quick Reference

StandardNIST NamePurposeReplaces
ML-KEM (CRYSTALS-Kyber)FIPS 203Key encapsulationRSA key exchange, ECDH
ML-DSA (CRYSTALS-Dilithium)FIPS 204Digital signaturesRSA signatures, ECDSA
SLH-DSA (SPHINCS+)FIPS 205Hash-based signaturesRSA, ECDSA (conservative)

Crypto Inventory Audit

First, discover what crypto is deployed:

---
- name: PQC readiness audit
  hosts: all
  become: true
  tasks:
    - name: Check OpenSSL version
      ansible.builtin.command: openssl version
      register: openssl_ver
      changed_when: false

    - name: Check SSH key types
      ansible.builtin.shell: |
        for key in /etc/ssh/ssh_host_*_key.pub; do
          echo "$(ssh-keygen -lf $key) $key"
        done
      register: ssh_keys
      changed_when: false

    - name: Find all TLS certificates
      ansible.builtin.find:
        paths:
          - /etc/ssl/certs
          - /etc/pki/tls/certs
          - /etc/nginx/ssl
          - /etc/letsencrypt/live
        patterns: "*.pem,*.crt"
        recurse: true
      register: certs_found

    - name: Check certificate algorithms
      ansible.builtin.shell: >
        openssl x509 -in {{ item.path }} -noout -text 2>/dev/null |
        grep -E "Signature Algorithm|Public Key Algorithm" | head -2
      loop: "{{ certs_found.files }}"
      register: cert_algos
      changed_when: false

    - name: Generate crypto inventory report
      ansible.builtin.template:
        src: crypto-audit.j2
        dest: /tmp/crypto-audit-{{ inventory_hostname }}.txt
      delegate_to: localhost

    - name: Flag vulnerable algorithms
      ansible.builtin.debug:
        msg: >
          ⚠️ {{ inventory_hostname }}: Found {{ item.stdout_lines | select('search', 'RSA|ECDSA|sha256WithRSA') | list | length }}
          certificates using pre-quantum algorithms
      loop: "{{ cert_algos.results }}"
      when: item.stdout is search('RSA|ECDSA')

Update Crypto Libraries

- name: Deploy PQC-ready crypto libraries
  hosts: all
  become: true
  vars:
    oqs_openssl_version: "3.2.0"

  tasks:
    - name: Update OpenSSL to PQC-capable version
      ansible.builtin.package:
        name: "{{ openssl_package }}"
        state: latest
      vars:
        openssl_package: "{{ 'openssl' if ansible_os_family == 'Debian' else 'openssl' }}"

    - name: Install liboqs (Open Quantum Safe)
      block:
        - name: Install build dependencies
          ansible.builtin.package:
            name:
              - cmake
              - gcc
              - ninja-build
              - libssl-dev
            state: present

        - name: Clone liboqs
          ansible.builtin.git:
            repo: https://github.com/open-quantum-safe/liboqs.git
            dest: /opt/liboqs
            version: "main"

        - name: Build liboqs
          ansible.builtin.shell: |
            cd /opt/liboqs
            mkdir -p build && cd build
            cmake -GNinja -DBUILD_SHARED_LIBS=ON ..
            ninja
            ninja install
          args:
            creates: /usr/local/lib/liboqs.so

    - name: Install OQS provider for OpenSSL
      ansible.builtin.git:
        repo: https://github.com/open-quantum-safe/oqs-provider.git
        dest: /opt/oqs-provider
        version: "main"

    - name: Build OQS provider
      ansible.builtin.shell: |
        cd /opt/oqs-provider
        mkdir -p build && cd build
        cmake -DOPENSSL_ROOT_DIR=/usr -DCMAKE_PREFIX_PATH=/usr/local ..
        make
        make install
      args:
        creates: /usr/local/lib/ossl-modules/oqsprovider.so

    - name: Enable OQS provider in OpenSSL config
      ansible.builtin.blockinfile:
        path: /etc/ssl/openssl.cnf
        marker: "# {mark} ANSIBLE MANAGED - OQS PROVIDER"
        insertafter: "\\[openssl_init\\]"
        block: |
          providers = provider_sect
          [provider_sect]
          default = default_sect
          oqsprovider = oqs_sect
          [default_sect]
          activate = 1
          [oqs_sect]
          activate = 1
          module = /usr/local/lib/ossl-modules/oqsprovider.so

Migrate SSH to Hybrid PQC

- name: Configure SSH for post-quantum
  hosts: all
  become: true
  tasks:
    - name: Generate ML-KEM hybrid SSH host key
      ansible.builtin.command: >
        ssh-keygen -t ml-kem-768-x25519
        -f /etc/ssh/ssh_host_mlkem_key -N ""
      args:
        creates: /etc/ssh/ssh_host_mlkem_key

    - name: Configure sshd for hybrid key exchange
      ansible.builtin.blockinfile:
        path: /etc/ssh/sshd_config
        marker: "# {mark} ANSIBLE MANAGED - PQC"
        block: |
          # Post-quantum hybrid key exchange
          KexAlgorithms mlkem768x25519-sha256,curve25519-sha256,curve25519-sha256@libssh.org

          # Host key algorithms (PQC + classical fallback)
          HostKeyAlgorithms ssh-ml-dsa-65,ssh-ed25519,rsa-sha2-512

          # Accept PQC and classical keys
          PubkeyAcceptedAlgorithms ssh-ml-dsa-65,ssh-ed25519,rsa-sha2-512
      notify: Restart sshd

    - name: Configure SSH client for PQC
      ansible.builtin.blockinfile:
        path: /etc/ssh/ssh_config
        marker: "# {mark} ANSIBLE MANAGED - PQC"
        block: |
          Host *
            KexAlgorithms mlkem768x25519-sha256,curve25519-sha256

  handlers:
    - name: Restart sshd
      ansible.builtin.service:
        name: sshd
        state: restarted

Deploy PQC TLS Certificates

- name: Generate hybrid PQC TLS certificates
  hosts: webservers
  become: true
  vars:
    pqc_cert_dir: /etc/ssl/pqc

  tasks:
    - name: Create PQC certificate directory
      ansible.builtin.file:
        path: "{{ pqc_cert_dir }}"
        state: directory
        mode: "0700"

    - name: Generate ML-DSA CA key and certificate
      ansible.builtin.shell: |
        openssl genpkey -algorithm ml-dsa-65 -out {{ pqc_cert_dir }}/ca-key.pem
        openssl req -new -x509 -key {{ pqc_cert_dir }}/ca-key.pem \
          -out {{ pqc_cert_dir }}/ca-cert.pem \
          -days 3650 -subj "/CN=PQC Internal CA"
      args:
        creates: "{{ pqc_cert_dir }}/ca-cert.pem"

    - name: Generate server hybrid certificate
      ansible.builtin.shell: |
        # Generate hybrid key (ML-DSA + ECDSA)
        openssl genpkey -algorithm ml-dsa-65 -out {{ pqc_cert_dir }}/server-key.pem

        # Create CSR
        openssl req -new -key {{ pqc_cert_dir }}/server-key.pem \
          -out {{ pqc_cert_dir }}/server.csr \
          -subj "/CN={{ inventory_hostname }}"

        # Sign with PQC CA
        openssl x509 -req -in {{ pqc_cert_dir }}/server.csr \
          -CA {{ pqc_cert_dir }}/ca-cert.pem \
          -CAkey {{ pqc_cert_dir }}/ca-key.pem \
          -CAcreateserial \
          -out {{ pqc_cert_dir }}/server-cert.pem \
          -days 365
      args:
        creates: "{{ pqc_cert_dir }}/server-cert.pem"

    - name: Configure Nginx with hybrid TLS
      ansible.builtin.template:
        src: nginx-pqc.conf.j2
        dest: /etc/nginx/conf.d/pqc.conf
      notify: Reload Nginx

  handlers:
    - name: Reload Nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

PQC Compliance Validation

- name: Validate PQC compliance
  hosts: all
  become: true
  tasks:
    - name: Verify SSH uses PQC key exchange
      ansible.builtin.shell: >
        ssh -vv localhost 2>&1 | grep -i "kex:" | head -1
      register: ssh_kex
      changed_when: false
      failed_when: "'mlkem' not in ssh_kex.stderr"

    - name: Check TLS supports PQC
      ansible.builtin.shell: >
        openssl s_client -connect localhost:443 2>/dev/null |
        grep "Server Temp Key"
      register: tls_key
      changed_when: false
      when: "'webservers' in group_names"

    - name: Verify no RSA-only certificates remain
      ansible.builtin.shell: |
        find /etc/ssl /etc/pki -name "*.pem" -exec \
          openssl x509 -in {} -noout -text 2>/dev/null \; |
        grep "Public Key Algorithm" | sort | uniq -c
      register: algo_report
      changed_when: false

    - name: Generate compliance report
      ansible.builtin.template:
        src: pqc-compliance.j2
        dest: "/tmp/pqc-compliance-{{ inventory_hostname }}.json"
      delegate_to: localhost

Migration Roadmap Playbook

---
# Phase 1: Audit (run immediately)
- import_playbook: pqc-audit.yml
  tags: [audit]

# Phase 2: Libraries (deploy PQC-capable crypto)
- import_playbook: pqc-libraries.yml
  tags: [libraries]

# Phase 3: SSH (hybrid PQC key exchange)
- import_playbook: pqc-ssh.yml
  tags: [ssh]

# Phase 4: TLS (hybrid certificates)
- import_playbook: pqc-tls.yml
  tags: [tls]

# Phase 5: Validate (compliance check)
- import_playbook: pqc-validate.yml
  tags: [validate]
# Run audit first
ansible-playbook site.yml --tags audit

# Deploy libraries
ansible-playbook site.yml --tags libraries

# Full migration
ansible-playbook site.yml

Conclusion

Post-quantum cryptography migration is a fleet-wide operation — exactly what Ansible excels at. Start with a crypto audit to inventory all RSA/ECC usage. Deploy PQC-capable libraries (liboqs, OQS provider). Migrate SSH to hybrid ML-KEM key exchange. Deploy hybrid TLS certificates. Validate compliance across the fleet. The key strategy for 2026: use hybrid mode (PQC + classical) during transition so nothing breaks, while becoming quantum-resistant. Ansible makes this repeatable across thousands of hosts.