Skip to content

Certificate Authority Role — Linux Hosts

This page covers issuing certificates to Linux hosts. Two workflows are supported:

  • Ansible-internal CA — the role generates a key and CSR on the Linux target, signs the CSR on the control node, and writes the certificate to the target.
  • Windows delegate host (Microsoft CA) — the CSR is generated and signed on a Windows delegate using certreq.exe, the resulting PFX is exported, and then the certificate and key are extracted and pushed to the Linux target.

See the overview for guidance on choosing a signing back-end and the full tag reference.

Tags Used

Tag Description
generate_key_linux Generate an RSA or ECC private key on the Linux host
generate_csr_linux Generate a CSR on the Linux host and load it for signing
sign_certificate Sign the CSR with the Ansible-managed CA on the control node
write_certificate_linux Write the signed certificate PEM to a file on the Linux host
deploy_certificate_linux Fetch a PFX from a Windows delegate host, extract the cert and key on the controller, and push them to the Linux target

Prerequisites

Ansible-internal CA workflow

  • A root CA must exist at extra_vars/certificate_authority.yml. See Generating the Ansible Root CA.
  • The vault file must be loaded at run time via -e @extra_vars/certificate_authority.yml.
  • python3-cryptography must be available on the target. The generate_key_linux and generate_csr_linux tasks install it automatically via dnf/apt.
  • The play must run with --become (or become: true) when writing to privileged paths such as /etc/ssl/private/.

Windows delegate host workflow

  • A Windows host must be available as certificate_authority_delegate_host and have enrollment access to the Microsoft CA.
  • The certificate must have been issued with certificate_authority_cert_exportable: true.
  • The Python cryptography library must be available on the Ansible control node (already required by community.crypto).
  • The play must run with --become when writing to privileged paths on the Linux target.
  • Always use --forks=1 when targeting multiple Linux hosts — concurrent certreq.exe operations against the same Windows delegate can corrupt pending requests.

Usage

Issue a certificate via Ansible CA (full chain)

ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
  -e @extra_vars/certificate_authority.yml \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  -e certificate_authority_linux_cert_path=/etc/ssl/certs/myhost.crt \
  --become

Deploying via a Windows delegate host (Microsoft CA)

Use this workflow when a Microsoft Enterprise CA must sign the certificate. The CSR, signing, and PFX export all happen on the Windows delegate. The deploy_certificate_linux tag then:

  1. Fetches the PFX from the Windows delegate to the controller.
  2. Extracts the certificate and private key from the PFX on the controller using the Python cryptography library.
  3. Pushes the certificate and key to the Linux target at certificate_authority_linux_cert_path and certificate_authority_linux_key_path.
  4. Removes the certificate from the delegate's LocalMachine\My store.
  5. Cleans up temporary PFX, cert, and key files from the controller's /tmp.
ansible-playbook --limit=mylinuxhost.example.com --forks=1 playbooks/certificate-authority-issue.yml \
  --tags generate_csr_windows,sign_certificate_microsoft_ca,import_certificate_windows,export_certificate_windows,deploy_certificate_linux \
  -e 'certificate_authority_cert_template=SANWebServer(90Days)' \
  -e certificate_authority_delegate_host=mywindowshost.example.com \
  -e certificate_authority_cert_exportable=true \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  -e certificate_authority_linux_cert_path=/etc/ssl/certs/myhost.crt \
  --become

One target at a time

Always use --forks=1 when running against multiple Linux hosts with a delegate. Concurrent certreq.exe operations on the same Windows delegate can corrupt pending requests.

Exportable flag must be set at request time

The private key can only be extracted from the PFX if the original CSR was generated with certificate_authority_cert_exportable: true. Set this before generate_csr_windows runs — there is no way to export an already-issued non-exportable certificate.


Running steps individually

# Generate the private key only
ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags generate_key_linux \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  --become

# Generate a CSR only (content is shown in task output and loaded into certificate_authority_sign_csr)
ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags generate_csr_linux \
  -e @extra_vars/certificate_authority.yml \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  --become

# Write a certificate that was already signed (pass cert content directly)
ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags write_certificate_linux \
  -e certificate_authority_linux_cert_path=/etc/ssl/certs/myhost.crt \
  -e certificate_authority_cert_content="$(cat signed.pem)" \
  --become

Replacing an existing certificate

Renew cert and key (keep 2 backups)

Set certificate_authority_cert_keep_copies to rotate old files into numbered backups before writing the new ones. .1 is the most recent backup; .2 is one older; and so on. Files older than the limit are pruned automatically.

ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
  -e @extra_vars/certificate_authority.yml \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  -e certificate_authority_linux_cert_path=/etc/ssl/certs/myhost.crt \
  -e certificate_authority_cert_keep_copies=2 \
  --become

After two renewals the target will have:

/etc/ssl/private/myhost.key    ← current
/etc/ssl/private/myhost.key.1  ← previous
/etc/ssl/private/myhost.key.2  ← one before that
/etc/ssl/certs/myhost.crt
/etc/ssl/certs/myhost.crt.1
/etc/ssl/certs/myhost.crt.2

Key staging

The new private key is written to certificate_authority_linux_key_stage_path (default /tmp/<hostname>_pending.key) during generate_key_linux and only promoted to its final path by write_certificate_linux after the certificate is signed. If signing fails, the existing key at the final path is untouched.

Renew cert only, reuse existing key

Set certificate_authority_linux_reuse_key: true to sign a new certificate against the key already on the target. Key files are not touched at all — no generation, no backup, no overwrite.

ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
  -e @extra_vars/certificate_authority.yml \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  -e certificate_authority_linux_cert_path=/etc/ssl/certs/myhost.crt \
  -e certificate_authority_linux_reuse_key=true \
  -e certificate_authority_cert_keep_copies=1 \
  --become

The generate_key_linux tag must still be included in the tag list — when reuse_key is true it runs but immediately verifies the key exists and exits, rather than generating a new one. The role fails fast with a clear error if the key file is not found.


ECC key

Set certificate_authority_linux_key_type: ecc in host_vars or pass it on the command line. The default curve is prime256v1; override with certificate_authority_linux_ecc_curve.

ansible-playbook --limit=myhost.example.com playbooks/certificate-authority-issue.yml \
  --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux \
  -e @extra_vars/certificate_authority.yml \
  -e certificate_authority_linux_key_path=/etc/ssl/private/myhost.key \
  -e certificate_authority_linux_cert_path=/etc/ssl/certs/myhost.crt \
  -e certificate_authority_linux_key_type=ecc \
  --become

Variables

All variables use the full certificate_authority_* name. They can be set in host_vars, group_vars, or via -e on the command line.

Required Variables

Variable Description
certificate_authority_linux_key_path Destination path for the private key file (e.g. /etc/ssl/private/myhost.key). No default — must be set.
certificate_authority_linux_cert_path Destination path for the certificate file (e.g. /etc/ssl/certs/myhost.crt). No default — must be set.

Linux-specific Variables

Variable Default Description
certificate_authority_linux_key_type rsa Key algorithm. Accepted values: rsa, ecc.
certificate_authority_linux_ecc_curve prime256v1 ECC curve name. Only used when key_type is ecc.
certificate_authority_linux_key_mode 0600 File mode applied to the private key file.
certificate_authority_linux_cert_mode 0644 File mode applied to the certificate file.
certificate_authority_linux_csr_path /tmp/{{ inventory_hostname }}.csr Path where the CSR is written on the target before signing.
certificate_authority_linux_csr_cleanup true Remove the CSR file from the target after it has been read and loaded for signing.
certificate_authority_linux_reuse_key false When true, skip key generation and reuse the existing key at certificate_authority_linux_key_path. Key rotation is also skipped. The key file must already exist on the target — the role fails immediately with a clear error if it does not.
certificate_authority_linux_key_stage_path /tmp/{{ inventory_hostname }}_pending.key Temporary path on the target where a newly generated key is written before the certificate is confirmed. Promoted to certificate_authority_linux_key_path by write_certificate_linux only after issuance succeeds, so the old key remains in service until the new cert is in hand.

Certificate Variables

The Linux workflow reuses the same certificate shaping variables as the Windows workflow. See the Ansible Internal CA variables table for the full list. The most commonly set ones are:

Variable Default Description
certificate_authority_cert_sans [] Additional SANs. Bare values are treated as DNS SANs.
certificate_authority_cert_include_default_names true Auto-add FQDN, inventory hostname, and short hostname as DNS SANs.
certificate_authority_cert_include_ip_san false Auto-add the host's primary IPv4 as an IP SAN.
certificate_authority_cert_validity_days 825 Certificate validity in days.
certificate_authority_cert_key_length 2048 RSA key size in bits.
certificate_authority_cert_key_usage [digitalSignature, keyEncipherment] Key usage values.
certificate_authority_cert_extended_key_usage [serverAuth] Extended key usage OIDs.
certificate_authority_cert_keep_copies (unset — no backup) How many numbered backup copies of the cert and key files to keep before overwriting. 0 deletes the current files and all existing backups; N keeps N backups (.1 = most recent) and prunes the rest. Leave unset to overwrite with no backup. Key backup only applies when certificate_authority_linux_reuse_key is false; deploy_certificate_linux always backs up both since the PFX always contains a new key.

Sample host_vars

# host_vars/myhost.example.com.yml

certificate_authority_linux_key_path: /etc/ssl/private/myhost.key
certificate_authority_linux_cert_path: /etc/ssl/certs/myhost.crt
certificate_authority_linux_key_type: rsa
certificate_authority_cert_include_ip_san: true
certificate_authority_cert_validity_days: 365
certificate_authority_cert_keep_copies: 2
# certificate_authority_linux_reuse_key: true  # uncomment to renew cert without regenerating the key

Sample Playbook Run

ansible-playbook playbooks/certificate-authority-issue.yml -l nginx.sapphire.dev,PRDODB.sapphire.dev -e @extra_vars/proxy.yml -e @extra_vars/certificate_authority.yml --tags generate_key_linux,generate_csr_linux,sign_certificate,write_certificate_linux --become