Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk11 min

How to Get a Let’s Encrypt Certificate with Ansible on Ubuntu

Issue Let’s Encrypt certificates on Ubuntu with Ansible by using the community.crypto ACME module in two passes, fulfilling HTTP-01 or DNS-01 challenges, deploying fullchain.pem and privkey.pem, and testing renewal safely.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The reliable Ansible workflow is a two-pass ACME transaction: request challenge data with community.crypto.acme_certificate, publish the HTTP-01 or DNS-01 challenge, then call the module again with the registered data to download the certificate. Use HTTP-01 when the domain reaches the Ubuntu host on port 80; use DNS-01 when port 80 cannot be exposed or when you need a wildcard name.

Let’s Encrypt certificates last 90 days. Automation is therefore part of the installation, not an optional follow-up: store the account and certificate keys safely, make issuance idempotent, deploy fullchain.pem and privkey.pem, and reload the service only when the files change.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What Ansible is automating

Let’s Encrypt is operated by the Internet Security Research Group and issues certificates through the Automated Certificate Management Environment (ACME) protocol. The certificates are valid for 90 days and can be renewed automatically. Ansible does not bypass ACME validation; it coordinates the same validation that a manual client performs and makes the result repeatable across hosts.

The community.crypto.acme_certificate module is part of the community.crypto collection, not ansible-core. Install the collection before running a playbook:

ansible-galaxy collection install community.crypto

The module deliberately runs twice. The first invocation creates an ACME order and returns the challenge information. Your playbook must then prove control of every requested name. The second invocation receives the first result through data and retrieves the issued certificate, chain, and full chain.

Choose the validation method before writing tasks

Decision HTTP-01 DNS-01
Network requirement The hostname must resolve to the Ubuntu server and inbound TCP port 80 must reach a web server or a temporary standalone listener. No inbound HTTP is required; the ACME TXT record is created in DNS.
Wildcard certificates Not supported for wildcard names. Required for names such as *.example.com.
Challenge publication Serve the returned value at /.well-known/acme-challenge/<resource>. Publish each returned value as a TXT record at the returned _acme-challenge name.
Operational complexity Usually simplest when you control the webroot and port 80. Requires DNS-provider automation or an API workflow and time for DNS propagation.

Use HTTP-01 for an ordinary web certificate

HTTP-01 is the straightforward option for example.com and names such as www.example.com when both DNS and port 80 are under your control. The web server must serve the challenge directory without redirecting it to an application that hides the file. A redirect to HTTPS can work in some configurations, but allowing the validation path over port 80 is less fragile.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use DNS-01 for wildcards or closed HTTP networks

DNS-01 is the only way to obtain wildcard certificates from Let’s Encrypt. It is also the better fit for internal load balancers, hosts behind a firewall, or services for which exposing port 80 is unacceptable. The playbook must have permission to create and remove TXT records in the authoritative DNS zone, and it must wait until the records are visible to the public resolvers used during validation.

Prerequisites on Ubuntu

  • A registered domain whose required names resolve to the intended service or whose DNS zone can be updated automatically.
  • An Ubuntu host reachable by Ansible with privilege escalation available for writing under /etc/letsencrypt and reloading the service.
  • Python and Ansible on the control node and the target host, plus OpenSSL tools for key and CSR generation.
  • The community.crypto collection installed on the control node.
  • For HTTP-01, a webroot served on port 80 or a coordinated standalone listener. For DNS-01, a provider module or API client with narrowly scoped credentials.

Install the collection on the machine from which Ansible runs:

ansible-galaxy collection install community.crypto

Do not confuse the collection with Certbot. You can use the Ansible module as the certificate client, or you can let Certbot manage issuance and renewal while Ansible configures the resulting files. Pick one owner for renewal on a host; running two independent clients against the same certificate can create competing files and reloads.

Create separate ACME and certificate keys

ACME uses an account key to authenticate requests to the certificate authority. The certificate itself has a different private key, represented by the CSR. Keep those keys separate. The account key should be long-lived and tightly protected; the certificate key is the key your web server presents and should be readable only by the service account or by root as required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The following tasks create a 0600 account key and a certificate key, then build a CSR. Adjust the subject alternative names to the complete set of names you intend to request.

- name: Create the ACME account key
  community.crypto.openssl_privatekey:
    path: '{{ acme_account_key }}'
    type: RSA
    size: 4096
    mode: '0600'

- name: Create the certificate private key
  community.crypto.openssl_privatekey:
    path: '{{ cert_key }}'
    type: RSA
    size: 2048
    mode: '0600'

- name: Create the certificate signing request
  community.crypto.openssl_csr:
    path: '{{ csr_path }}'
    privatekey_path: '{{ cert_key }}'
    common_name: '{{ domain }}'
    subject_alt_name:
      - 'DNS:{{ domain }}'
      - 'DNS:www.{{ domain }}'
    mode: '0644'

Store the account email and any DNS API credentials in Ansible Vault or an equivalent secret store. Never place a token directly in a task, commit it to a repository, or print it with a debug task.

The two-pass Ansible playbook

The skeleton below shows the control flow. Set letsencrypt_directory to the current production ACME directory value used by your environment, and choose paths that match your service. The first task registers the challenge response; the second task consumes it.

---
- name: Issue or renew a Let’s Encrypt certificate
  hosts: web
  become: true
  collections:
    - community.crypto
  vars:
    domain: example.com
    webroot: /var/www/html
    acme_account_key: /etc/letsencrypt/account.key
    cert_key: /etc/letsencrypt/live/{{ domain }}/privkey.pem
    csr_path: /etc/letsencrypt/{{ domain }}.csr
    cert_path: /etc/letsencrypt/live/{{ domain }}/cert.pem
    chain_path: /etc/letsencrypt/live/{{ domain }}/chain.pem
    fullchain_path: /etc/letsencrypt/live/{{ domain }}/fullchain.pem
    letsencrypt_directory: '{{ acme_directory_from_your_environment }}'
    acme_email: [email protected]

  tasks:
    - name: Request ACME challenge data
      community.crypto.acme_certificate:
        account_key_src: '{{ acme_account_key }}'
        account_email: '{{ acme_email }}'
        acme_directory: '{{ letsencrypt_directory }}'
        terms_agreed: true
        challenge: http-01
        csr: '{{ csr_path }}'
      register: acme_challenge

    # Fulfil the returned challenge here.

    - name: Retrieve the certificate after validation
      community.crypto.acme_certificate:
        account_key_src: '{{ acme_account_key }}'
        account_email: '{{ acme_email }}'
        acme_directory: '{{ letsencrypt_directory }}'
        terms_agreed: true
        challenge: http-01
        csr: '{{ csr_path }}'
        data: '{{ acme_challenge }}'
        dest: '{{ cert_path }}'
        chain_dest: '{{ chain_path }}'
        fullchain_dest: '{{ fullchain_path }}'
        remaining_days: 30
      notify: Reload web service

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

remaining_days: 30 tells the module not to request a replacement when the existing certificate still has more than 30 days left. This check, existing output files, and a change-triggered handler keep repeated playbook runs safe and avoid unnecessary issuance.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fulfil an HTTP-01 challenge

After the first module call, the registered result contains challenge data for each identifier in the CSR. For HTTP-01, publish the returned resource_value at the returned resource path beneath the webroot. The web server must be able to read the file before the second module call runs.

- name: Ensure the ACME challenge directory exists
  ansible.builtin.file:
    path: '{{ webroot }}/.well-known/acme-challenge'
    state: directory
    owner: root
    group: root
    mode: '0755'

- name: Publish each HTTP-01 challenge
  ansible.builtin.copy:
    content: '{{ item.value[''http-01''].resource_value }}'
    dest: '{{ webroot }}/.well-known/acme-challenge/{{ item.value[''http-01''].resource }}'
    owner: root
    group: root
    mode: '0644'
  loop: '{{ acme_challenge.challenge_data | dict2items }}'
  when: item.value[''http-01''] is defined

Before requesting the certificate, test one of the files from a network outside the server. A failure usually means the name resolves elsewhere, port 80 is filtered, a reverse proxy sends the path to the wrong backend, or the web server denies dot-directories. Correct that routing problem rather than repeatedly rerunning the ACME task.

Standalone HTTP-01

Standalone mode temporarily binds port 80 instead of using an existing webroot. It cannot bind while another process owns that port. Stop the web service for the validation window, coordinate a maintenance interval, or use webroot or DNS-01 instead. A stop-and-start sequence should be managed with Ansible handlers or a block with an always section so the service is restored even if validation fails.

Fulfil a DNS-01 challenge

For DNS-01, inspect every returned challenge entry and create a TXT record at its supplied _acme-challenge name with the supplied value. The exact Ansible task depends on your DNS provider, so keep the provider module behind a small role and pass credentials through Vault.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Loop over every identifier returned by the first acme_certificate call.
  2. Create the TXT record at the returned record name, preserving the leading underscore and the correct DNS zone.
  3. Wait for propagation. Query authoritative servers or use the provider’s asynchronous completion status instead of assuming an immediate update.
  4. Run the second acme_certificate task with the unchanged registered result in data.
  5. Remove temporary TXT records only after the order has completed, and retain any records needed by another simultaneous order.

Common DNS failures are a record created in the wrong zone, a stale value left beside the current value, propagation that has not completed, or an API token without permission to edit TXT records. Wildcard orders can contain more than one identifier, so publish every value rather than handling only the first loop item.

Deploy the files to Ubuntu services

Ubuntu’s documented Let’s Encrypt layout places live certificate material under /etc/letsencrypt/live/<domain>/. Configure the service to use fullchain.pem for the certificate chain and privkey.pem for the private key. The full chain allows clients to build trust through the required intermediate certificate; serving only the leaf certificate can cause verification failures on some clients.

Protect the private key with restrictive permissions and ensure the service account can read it without making it world-readable. Do not copy the private key into a public webroot, an Ansible fact, or a log file. If your playbook uses a different directory, apply the same separation and permissions.

Reload, rather than restart, a service after a successful certificate change when the daemon supports it. A handler notified by the certificate task prevents a reload on every idempotent run. Verify the active listener after deployment; a successful file update does not prove that the running process has opened the new keypair.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Renewal: Ansible module or Certbot

Renewal owned by the Ansible module

Schedule the playbook with your existing automation system and retain remaining_days so it renews only when necessary. The play must be able to perform the selected challenge unattended, and the reload handler must run after a changed certificate. For DNS-01, test both API authentication and propagation on a non-production name before relying on the schedule.

Renewal owned by Certbot

If you install the snap version of Certbot, its systemd renewal timer runs twice daily and attempts renewal when a certificate is close to expiry. Run:

certbot renew --dry-run

This exercises the renewal path without replacing the live certificate. Certbot-managed web-server integrations can reload supported services automatically, but a service outside that integration may continue using the old certificate. Add a Certbot deploy hook or an equivalent Ansible-triggered reload, then inspect the active listener after a test renewal.

Do not make Ansible issue a second certificate while Certbot is also renewing the same names. If Ansible only deploys configuration and Certbot owns the certificate files, have Ansible reference the existing paths and leave renewal to the timer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshoot the failures that matter most

The HTTP-01 order fails immediately

  • Confirm every requested DNS name resolves to the host that serves the challenge.
  • Confirm inbound port 80 reaches that host through firewalls, security groups, NAT, and the reverse proxy.
  • Fetch the exact /.well-known/acme-challenge/... URL from outside the network and check that the returned body matches resource_value.
  • Check that the web server does not rewrite the path into an application route or deny hidden directories.

Standalone mode reports that port 80 is busy

Find the process listening on port 80, then either stop it for the short validation interval, configure a controlled handoff, or switch to webroot or DNS-01. Repeated attempts without changing ownership of the port will produce the same failure.

The DNS-01 order cannot see the TXT record

  • Check that the record was created in the authoritative zone, not in a similarly named parent or child zone.
  • Query the authoritative DNS servers and allow for propagation before the second module call.
  • Verify that the API token can create TXT records and that Ansible is using the intended account.
  • Publish all returned values when multiple identifiers are present.

Renewal succeeds but clients still see the old certificate

Inspect the service configuration and the process actually bound to the listening port. Confirm that the deploy hook or Ansible handler ran after the files changed, then reload the daemon and verify the presented certificate from an external client. File timestamps alone do not establish what the running process is serving.

A replacement certificate is missing an existing name

When intentionally replacing a multi-domain certificate with Certbot, request the complete domain set. Supplying only a subset creates a certificate that does not cover the omitted names and can break clients that still use them.

Security and reliability checklist

  • Keep the ACME account key separate from the certificate private key.
  • Store DNS API credentials in Ansible Vault or an equivalent secret manager and suppress them from task output.
  • Use the production ACME directory only after the playbook works against a safe test environment.
  • Request only the names that need to share a certificate; include every required name in the CSR.
  • Use restrictive permissions on private keys and avoid copying them to application directories.
  • Make challenge publication and cleanup idempotent.
  • Set a renewal threshold such as 30 remaining days rather than forcing issuance on every run.
  • Notify a reload handler only when certificate files change.
  • Exercise the renewal path with certbot renew --dry-run when Certbot owns renewal.
  • Monitor expiry and the service’s active certificate, not merely the success status of the automation job.

End-to-end run order

  1. Verify DNS, port 80 reachability, and the names in the CSR.
  2. Install community.crypto with ansible-galaxy collection install community.crypto.
  3. Create or retrieve the protected ACME account key and generate the domain CSR with a separate certificate key.
  4. Run community.crypto.acme_certificate once to obtain challenge data.
  5. Publish every HTTP-01 file or DNS-01 TXT record and wait until the challenge is reachable.
  6. Run the same module with data set to the registered result and write the certificate, chain, and full chain destinations.
  7. Configure the service for fullchain.pem and privkey.pem, then reload only after a change.
  8. Schedule idempotent renewal and test it before the 90-day certificate reaches expiry.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.