// DevOps

SSL certificates via DNS: automating Let’s Encrypt issuance

Published on 2025-10-06

Introduction

Let’s Encrypt is the standard for obtaining free TLS certificates. Most often certificates are issued via the HTTP-01 method, which requires a reachable web server on port 80. However, for internal services or wildcard certificates (for example, *.example.com) it is more convenient to use DNS-01, which verifies domain ownership via TXT records in DNS and does not require open ports.

This article covers:

  • Issuing certificates via the Cloudflare API,
  • Issuing certificates via Amazon Route 53 (AWS),
  • Integration with web servers Nginx, HAProxy and Traefik,
  • Automation of certificate renewal.

Note: Instructions are relevant for Certbot 2.x, acme.sh 3.x, Nginx 1.18+, HAProxy 2.4+, Traefik 2.x on Linux (Ubuntu/Debian). For other OSes or tool versions adjustments may be required.


Cloudflare

1. Create an API token

  1. Log in to Cloudflare → API TokensCreate Token.
  2. Use the Edit zone DNS template with permissions:
    • Zone.DNS:Edit
    • Zone.Zone:Read
  3. Restrict the token to a specific zone.
  4. Store the token in a secure place (CI/CD secrets, environment variables).

⚠️ Token leakage gives full control over the DNS zone.

2. Install Certbot and the plugin

Ubuntu/Debian:

sudo apt-get update
sudo apt-get install -y certbot python3-certbot-dns-cloudflare

CentOS/Fedora:

sudo dnf install -y certbot python3-certbot-dns-cloudflare

Via snap:

sudo snap install --classic certbot
sudo snap install certbot-dns-cloudflare

3. Configure the token

File /etc/letsencrypt/cloudflare.ini:

dns_cloudflare_api_token = YOUR_CF_API_TOKEN

Permissions:

sudo chown root:root /etc/letsencrypt/cloudflare.ini
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

4. Issue the certificate

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d example.com -d '*.example.com' \
  --non-interactive

AWS Route 53

1. Configure IAM permissions

Create a policy with minimal permissions for the specific zone:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets",
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
      ],
      "Resource": "arn:aws:route53:::hostedzone/<YOUR_HOSTED_ZONE_ID>"
    }
  ]
}

Set up an AWS CLI profile:

aws configure --profile certbot

Provide AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and a region (for example, us-east-1).

2. Install the plugin

Ubuntu/Debian:

sudo apt-get install -y certbot python3-certbot-dns-route53

CentOS/Fedora:

sudo dnf install -y certbot python3-certbot-dns-route53

3. Issue the certificate

AWS_PROFILE=certbot \
sudo certbot certonly \
  --dns-route53 \
  -d example.com -d '*.example.com' \
  --non-interactive

Note: you can use environment variables instead of a profile (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY).


Integration with web servers

Nginx

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

Hook to reload /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh:

#!/bin/sh
if nginx -t; then
    systemctl reload nginx
else
    echo "Nginx configuration test failed"
    exit 1
fi
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

HAProxy

Combine certificate and key:

cat /etc/letsencrypt/live/example.com/fullchain.pem \
    /etc/letsencrypt/live/example.com/privkey.pem \
    | tee /etc/haproxy/certs/example.com.pem >/dev/null

Config snippet:

frontend https-in
    bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
    default_backend web-backend

Traefik

File /etc/traefik/dynamic.yml:

tls:
  certificates:
    - certFile: /etc/letsencrypt/live/example.com/fullchain.pem
      keyFile: /etc/letsencrypt/live/example.com/privkey.pem

traefik.yml:

providers:
  file:
    filename: /etc/traefik/dynamic.yml

If Traefik runs in Docker:

-v /etc/letsencrypt:/etc/letsencrypt:ro

Renewal automation

Certbot

Cron job:

sudo crontab -e
0 0,12 * * * certbot renew --quiet --deploy-hook /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Error logs: /var/log/letsencrypt/letsencrypt.log.

acme.sh

When installed acme.sh sets up a cron job itself. Check:

crontab -l | grep acme.sh

Alternative: acme.sh

Install:

curl https://get.acme.sh | sh

Cloudflare:

export CF_Token="YOUR_CF_API_TOKEN"
~/.acme.sh/acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

AWS Route 53:

export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_REGION="us-east-1"
~/.acme.sh/acme.sh --issue --dns dns_aws -d example.com -d '*.example.com'

Install certificate:

~/.acme.sh/acme.sh --install-cert -d example.com \
  --key-file       /etc/ssl/example.com/privkey.pem \
  --fullchain-file /etc/ssl/example.com/fullchain.pem \
  --reloadcmd     "systemctl reload nginx"

Certbot vs acme.sh

CriterionCertbotacme.sh
LanguagePythonPOSIX sh
InstallationOS packages or snapOne script `curlsh`
DNS supportOfficial plugins~40 providers built-in
UpdatesVia package managerBuilt-in auto-update
Hooks and deployLimited hooksConvenient --reloadcmd, --deploy
DependenciesPython, certbot-* packagescurl, socat, openssl
ResourcesHeavier (Python)Lighter, minimalist
PopularityDe-facto standard, more documentationMore convenient for CI/CD

When to choose Certbot:

  • if you need official Let’s Encrypt support,
  • if installing packages via apt/yum is convenient,
  • if priority is the standard ecosystem.

When to choose acme.sh:

  • if you have a minimal environment without Python,
  • if issuance needs to be integrated into a CI/CD pipeline,
  • if the DNS provider is supported directly in acme.sh.

Limitations and risks

  • API token leakage = full control over the DNS zone.
  • Let’s Encrypt limits: up to 50 certificates per week per domain.
  • DNS propagation: delay from seconds to minutes.
  • Renewal failures: incorrect hooks can lead to expiration.

Tip: add monitoring (Zabbix, Prometheus, Nagios) to track certificate expiry dates.


Conclusion

DNS validation allows you to:

  • automate issuance and renewal of SSL,
  • use wildcard certificates,
  • not depend on open ports.

Cloudflare and AWS Route 53 provide APIs for full automation. Integration with Nginx, HAProxy and Traefik takes only a few lines of configuration. Automatic renewal and monitoring are essential steps for reliable operation.

// Reviews

Related reviews

apande

apande

Configuring Nginx and OpenCart

2024-09-07 · ★ 5/5

A very powerful buyer
kireevk

kireevk

Diagnosing Nginx Proxy Manager in a Docker container and resolving the issue

2024-03-15 · ★ 5/5

Settled-in customer

// Contact

Need help?

Get in touch with me and I'll help solve the problem