Menu
SMORTLER + SI / FIELD GUIDE / SECURITY AUTOMATION

Automatic Let's Encrypt
DNS-01 validation with Route 53.

No public-facing web server. No quarterly DNS tickets. One controlled AWS setup and clear, repeatable Ubuntu commands.

Customize the guide ↓
Violet, the SI robot, welcoming administrators to the DNS validation guideViolet says: secure the process, automate the routine.
STEP BY STEP / PRIVATE SERVERS

Configure once. Renew automatically.

A color-coded, step-by-step guide for two private Ubuntu 24.04 servers. Enter your names once; the certificate names, IAM permissions, commands, and instructions update immediately.

All initial names are examples. The guide assumes Apache and an existing APT-installed Certbot 2.9.0 or newer certificate for each hostname. No inbound public HTTP or HTTPS access is needed for DNS-01 validation.

Color key - who runs each step?

Light violet - AWS / Route 53DNS administrator: DNS Admin. IAM setup and policy.
Light green - Server 01private-01 - commands run as root on this server.
Light blue - Server 02private-02 - commands run as root on this server.

Personalize this guide

Type the actual names below. All steps and copyable commands update as you type. Nothing is sent to a server or saved by this page. Never enter AWS access keys or passwords here.

Example: private-01 (no domain here)

Example: private-02 (no domain here)

Example: sub1 or team.office. Leave empty for no subdomain.

Example: smortler.com

Example: Smortler - not used in DNS records.

Starts with Z. Required to enable copying the IAM policy. Not an AWS secret.

Used in instructions only.

Used in instructions only.

SERVER 01 CERTIFICATEprivate-01.sub1.smortler.com
SERVER 02 CERTIFICATEprivate-02.sub1.smortler.com
DNS-01 TXT RECORD - SERVER 01_acme-challenge.private-01.sub1.smortler.com
DNS-01 TXT RECORD - SERVER 02_acme-challenge.private-02.sub1.smortler.com
Example values are valid. Fill in the hosted zone ID before copying the IAM policy.
Hosted Zone ID not entered yet. The IAM policy Copy button is disabled until it is filled in.
How to use: First let DNS Admin complete the violet AWS steps one time. Then Local Server Admin follows the green Server 01 commands, tests them, and then follows the blue Server 02 commands. Every colored command box has its own Copy button.
Before starting: This guide only automates existing Let's Encrypt certificates. The certbot certificates results must contain the exact full certificate names displayed above. If you have different names or a Snap-based installation, stop rather than blindly running these commands. Confirm that the DNS records are publicly served by Route 53 and that the AWS account owns the authoritative public hosted zone.
Optional: verify public DNS from Server 01 before starting AWS setup

If dig is available, these read-only checks can help identify the public authoritative DNS zone. Run them as root on private-01 - light green means Server 01. Some corporate networks block direct public resolver access; that alone does not prove DNS validation will fail.

DNS

Read-only public DNS lookup from Server 01

Server 01 / optional DNS verification
dig @1.1.1.1 smortler.com NS +short
dig @1.1.1.1 sub1.smortler.com NS +short
dig @1.1.1.1 _acme-challenge.private-01.sub1.smortler.com SOA +noall +answer +authority

An empty NS answer for the subdomain can be normal when it is not separately delegated. The authoritative SOA response can help identify the correct hosted zone. If dig is missing, Ubuntu provides it through dnsutils.

PART 1 / LIGHT VIOLET / DNS ADMINISTRATOR
Violet thinking through secure DNS permissions

One-time AWS setup - DNS Admin

Only four AWS Console steps. Create one restricted IAM user or EC2 role that can modify the two exact TXT record names shown at the top. No permanent TXT records need to be created manually.

1

Find the actual authoritative PUBLIC hosted zone

  1. Sign in to the Route 53 console.
  2. Select Hosted zones.
  3. Find the public hosted zone that is authoritative for _acme-challenge.private-01.sub1.smortler.com and _acme-challenge.private-02.sub1.smortler.com. Normally this is smortler.com, unless the subdomain is separately delegated.
  4. Check that its nameservers match the ones published in public DNS. Do not select a private hosted zone, a duplicate un-delegated zone, or create a new zone.
  5. Copy its Hosted Zone ID and paste it into the input at the top of this page. The IAM policy will update immediately.

If you cannot find the correct public zone in this AWS account, stop; the user or role must have permissions in the account that owns that zone.

2

Create the least-privilege IAM policy

  1. Open AWS IAM > Policies and select Create policy.
  2. Select JSON and replace its contents with the exact policy below.
  3. After you have entered the Hosted Zone ID at the top, click Copy, paste the policy, choose Next, and name it smortler-Certbot-ExactTXT.
  4. Select Create policy.
AWS IAM policy / ONLY TWO TXT RECORDS
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DiscoverHostedZones",
      "Effect": "Allow",
      "Action": "route53:ListHostedZones",
      "Resource": "*"
    },
    {
      "Sid": "ReadDNSChangeStatus",
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    },
    {
      "Sid": "ChangeOnlyTwoChallengeTXTRecords",
      "Effect": "Allow",
      "Action": "route53:ChangeResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/YOUR_PUBLIC_HOSTED_ZONE_ID",
      "Condition": {
        "ForAllValues:StringEquals": {
          "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
            "_acme-challenge.private-01.sub1.smortler.com",
            "_acme-challenge.private-02.sub1.smortler.com"
          ],
          "route53:ChangeResourceRecordSetsRecordTypes": ["TXT"],
          "route53:ChangeResourceRecordSetsActions": ["CREATE", "UPSERT", "DELETE"]
        },
        "Null": {
          "route53:ChangeResourceRecordSetsNormalizedRecordNames": "false",
          "route53:ChangeResourceRecordSetsRecordTypes": "false",
          "route53:ChangeResourceRecordSetsActions": "false"
        }
      }
    }
  ]
}

Only these two names: _acme-challenge.private-01.sub1.smortler.com and _acme-challenge.private-02.sub1.smortler.com. The policy does not grant record changes to A, AAAA, CNAME, MX, NS, SOA, or any other TXT names. Do not attach broader Route 53 permissions. Listing hosted zone names and reading change status remain read-only.

Do not copy the policy until the correct Hosted Zone ID is entered above. A policy scoped to the wrong zone will not work. A restricted IAM identity can still issue certificates for these two hostnames, so protect its keys.
3

Create one IAM user and attach ONLY this policy

  1. Open IAM > Users and select Create user.
  2. Enter the username smortler-certbot-dns.
  3. Leave AWS Management Console access disabled.
  4. Choose Attach policies directly, select only smortler-Certbot-ExactTXT, and finish creating the user.

For AWS EC2 VMs: an IAM instance role attached to each EC2 instance is preferable to long-lived keys. The same restricted policy can be attached to the appropriate instance role; skip Step 4 and the credentials-file steps below if that is the chosen method.

4

Create an access key and deliver it securely

  1. Open IAM > Users > smortler-certbot-dns > Security credentials.
  2. Select Create access key, choose Application running outside AWS if applicable, and follow the prompts.
  3. Provide the Access Key ID and Secret Access Key securely to Local Server Admin. AWS displays the secret only once.
  4. The AWS Console work is done. There is no need to manually add challenge TXT records.

Never paste the access key, secret key, or passwords into this website, documentation, tickets, or plain-text email. Separate IAM users for the two servers provide additional isolation if desired; this page uses the simpler one-user setup.

PART 2 / FIRST PRIVATE SERVER

Configure Server 01: private-01

Run this entire section as root on private-01 (certificate: private-01.sub1.smortler.com). Assigned administrator: Local Server Admin.

5

Check Certbot, then install the matching Route 53 plugin

Check that the current certificate already exists and its Certificate Name exactly matches private-01.sub1.smortler.com. These commands assume Ubuntu 24.04 with APT-installed Certbot. If Certbot uses Snap instead, stop and use the matching Snap plugin.

Server 01 / check existing Certbot
command -v certbot
certbot --version
certbot certificates

If the certificate is missing or has a different Certificate Name (such as a -0001 suffix), stop and correct the inputs or consult your server administrator. Do not issue a duplicate certificate.

Server 01 / install plugin
apt update
apt install -y python3-certbot-dns-route53
certbot plugins

Confirm the plugin listing includes dns-route53 before continuing.

6

Make a backup of the existing renewal configuration

Keep the existing certificate and Apache configuration unchanged. This is a one-time backup of the renewal settings only.

Server 01 / backup
cp -an /etc/letsencrypt/renewal/private-01.sub1.smortler.com.conf /root/private-01-renewal-before-route53.conf

If the source file is missing or the command fails, stop. The backup will not overwrite an existing backup with the same name.

7

Store the IAM credentials safely (skip if using an EC2 IAM role)

If AWS issued an IAM user access key, check for any existing credentials first. Do not overwrite an existing AWS credentials file that another application uses. AWS secrets must never be entered on this web page.

Server 01 / inspect credentials location
ls -ld /root/.aws 2>/dev/null || true
ls -l /root/.aws/credentials 2>/dev/null || true

If it is safe to create or edit that file:

Server 01 / create or edit credentials file
install -d -m 700 /root/.aws
touch /root/.aws/credentials
chmod 600 /root/.aws/credentials
nano /root/.aws/credentials

Place the access key and secret provided securely by DNS Admin in the file as follows. These are placeholders, not real keys:

Server 01 / file contents (NOT a shell command)
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY

Save Nano with Ctrl+O, Enter, then Ctrl+X. Restrict the file permissions:

Server 01 / protect credentials
chown root:root /root/.aws/credentials
chmod 600 /root/.aws/credentials

EC2 IAM role alternative: if DNS Admin attached the exact-record policy to an instance role assigned to this server, skip all file-creation commands in this step.

8

Convert the existing certificate to automatic DNS validation

reconfigure performs a Let's Encrypt staging test and saves the new authenticator only on success. It does not replace the current live certificate. Do not use certonly or --force-renewal.

Server 01 / save DNS Route 53 renewal method
certbot reconfigure --cert-name private-01.sub1.smortler.com --authenticator dns-route53

Confirm the renewal file now contains authenticator = dns-route53:

Server 01 / verify saved authenticator
grep '^authenticator *=' /etc/letsencrypt/renewal/private-01.sub1.smortler.com.conf

If the test fails, stop here. Do not modify or delete the live certificate. See Troubleshooting below.

9

Reload Apache after renewal, enable the timer, and test

This deploy hook runs only after successful certificate issuance or renewal. It tests the Apache configuration before reloading. Inspect any existing renewal hooks if this server already has customized Certbot automation.

Server 01 / list any existing deploy hooks
ls -l /etc/letsencrypt/renewal-hooks/deploy/ 2>/dev/null || true

Create a separate hook for Apache (does not edit virtual hosts or certificate paths):

Server 01 / create Apache reload hook
install -d -m 755 /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-apache-dns01.sh <<'EOF'
#!/bin/sh
set -eu
/usr/sbin/apache2ctl configtest
/bin/systemctl reload apache2
EOF
chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-apache-dns01.sh

Enable the Certbot systemd timer that comes with the Ubuntu APT package; do not add a cron job as well.

Server 01 / enable scheduled renewals
systemctl enable --now certbot.timer
systemctl is-active certbot.timer
systemctl list-timers --all certbot.timer

Finally, perform a full staging dry run and run the deploy hook against the current live certificate:

Server 01 / final DNS + Apache test
certbot renew --cert-name private-01.sub1.smortler.com --dry-run --run-deploy-hooks
certbot certificates
systemctl is-active apache2

Success means: dry run succeeds, Apache remains active, Certbot's timer is active, and the existing certificate name and paths remain unchanged.

PART 3 / SECOND PRIVATE SERVER

Configure Server 02: private-02

Run this entire section as root on private-02 (certificate: private-02.sub1.smortler.com). Assigned administrator: Local Server Admin. Do not start until Server 01 has passed its dry run.

10

Check Certbot, then install the matching Route 53 plugin

Check that the current certificate already exists and its Certificate Name exactly matches private-02.sub1.smortler.com. These commands assume Ubuntu 24.04 with APT-installed Certbot. If Certbot uses Snap instead, stop and use the matching Snap plugin.

Server 02 / check existing Certbot
command -v certbot
certbot --version
certbot certificates

If the certificate is missing or has a different Certificate Name (such as a -0001 suffix), stop and correct the inputs or consult your server administrator. Do not issue a duplicate certificate.

Server 02 / install plugin
apt update
apt install -y python3-certbot-dns-route53
certbot plugins

Confirm the plugin listing includes dns-route53 before continuing.

11

Make a backup of the existing renewal configuration

Keep the existing certificate and Apache configuration unchanged. This is a one-time backup of the renewal settings only.

Server 02 / backup
cp -an /etc/letsencrypt/renewal/private-02.sub1.smortler.com.conf /root/private-02-renewal-before-route53.conf

If the source file is missing or the command fails, stop. The backup will not overwrite an existing backup with the same name.

12

Store the IAM credentials safely (skip if using an EC2 IAM role)

If AWS issued an IAM user access key, check for any existing credentials first. Do not overwrite an existing AWS credentials file that another application uses. AWS secrets must never be entered on this web page.

Server 02 / inspect credentials location
ls -ld /root/.aws 2>/dev/null || true
ls -l /root/.aws/credentials 2>/dev/null || true

If it is safe to create or edit that file:

Server 02 / create or edit credentials file
install -d -m 700 /root/.aws
touch /root/.aws/credentials
chmod 600 /root/.aws/credentials
nano /root/.aws/credentials

Place the access key and secret provided securely by DNS Admin in the file as follows. These are placeholders, not real keys:

Server 02 / file contents (NOT a shell command)
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY

Save Nano with Ctrl+O, Enter, then Ctrl+X. Restrict the file permissions:

Server 02 / protect credentials
chown root:root /root/.aws/credentials
chmod 600 /root/.aws/credentials

EC2 IAM role alternative: if DNS Admin attached the exact-record policy to an instance role assigned to this server, skip all file-creation commands in this step.

13

Convert the existing certificate to automatic DNS validation

reconfigure performs a Let's Encrypt staging test and saves the new authenticator only on success. It does not replace the current live certificate. Do not use certonly or --force-renewal.

Server 02 / save DNS Route 53 renewal method
certbot reconfigure --cert-name private-02.sub1.smortler.com --authenticator dns-route53

Confirm the renewal file now contains authenticator = dns-route53:

Server 02 / verify saved authenticator
grep '^authenticator *=' /etc/letsencrypt/renewal/private-02.sub1.smortler.com.conf

If the test fails, stop here. Do not modify or delete the live certificate. See Troubleshooting below.

14

Reload Apache after renewal, enable the timer, and test

This deploy hook runs only after successful certificate issuance or renewal. It tests the Apache configuration before reloading. Inspect any existing renewal hooks if this server already has customized Certbot automation.

Server 02 / list any existing deploy hooks
ls -l /etc/letsencrypt/renewal-hooks/deploy/ 2>/dev/null || true

Create a separate hook for Apache (does not edit virtual hosts or certificate paths):

Server 02 / create Apache reload hook
install -d -m 755 /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-apache-dns01.sh <<'EOF'
#!/bin/sh
set -eu
/usr/sbin/apache2ctl configtest
/bin/systemctl reload apache2
EOF
chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-apache-dns01.sh

Enable the Certbot systemd timer that comes with the Ubuntu APT package; do not add a cron job as well.

Server 02 / enable scheduled renewals
systemctl enable --now certbot.timer
systemctl is-active certbot.timer
systemctl list-timers --all certbot.timer

Finally, perform a full staging dry run and run the deploy hook against the current live certificate:

Server 02 / final DNS + Apache test
certbot renew --cert-name private-02.sub1.smortler.com --dry-run --run-deploy-hooks
certbot certificates
systemctl is-active apache2

Success means: dry run succeeds, Apache remains active, Certbot's timer is active, and the existing certificate name and paths remain unchanged.

PART 4 / FINAL VERIFICATION
Violet giving a thumbs-up

Finish and confirm automatic renewal

Complete the steps in order. Use the checkboxes to track progress (they are not saved to the server).

To check renewal status later, run these read-only commands on the appropriate machine (one green block and one blue block):

01

Read-only check on private-01

Server 01 / certificate and timer status
certbot certificates
systemctl list-timers --all certbot.timer
journalctl -u certbot.service -n 60 --no-pager
02

Read-only check on private-02

Server 02 / certificate and timer status
certbot certificates
systemctl list-timers --all certbot.timer
journalctl -u certbot.service -n 60 --no-pager
Finished: The Certbot timer handles renewal attempts; the Route 53 plugin creates and removes temporary challenge TXT records; Apache reloads after a successful renewal. No quarterly DNS changes, no extra cron job, and no public inbound web access are needed. Check logs and certificate expirations periodically.
PART 5 / TROUBLESHOOTING

If anything fails, stop and check

The DNS plugin cannot find the correct zone

Confirm the authoritative PUBLIC hosted zone for sub1.smortler.com and each challenge name is in the same AWS account. If a subdomain has its own NS delegation, the public hosted zone may be the subdomain, not the base domain. A duplicate undelegated zone is not enough. The Certbot plugin discovers hosted zones automatically; it must find the actual authoritative one.

AWS AccessDenied error

Check that the Hosted Zone ID entered above is correct, the identity has the policy attached, and the requested TXT name is exactly _acme-challenge.private-01.sub1.smortler.com or _acme-challenge.private-02.sub1.smortler.com. Do not fix this by giving Route 53 administrator permissions. If using an EC2 role, confirm which role is attached to the actual instance.

Certbot cannot find AWS credentials

Check that the access key is configured as [default] in /root/.aws/credentials, root owns the file, and its permissions are 600. If there are other applications using AWS credentials, review that configuration before changing it.

The certificate name does not match the generated name

Use certbot certificates to discover the real Certificate Name. The name may differ from a DNS hostname. The commands in this guide assume both match. If not, stop and adapt the certificate-name and filename values with the server administrator; do not create a duplicate certificate.

DNS challenge times out

Check outbound HTTPS and working DNS. DNS-01 challenge TXT records must be available in the public authoritative zone. The plugin deletes the temporary challenge record after validation, so an empty query afterward is normal. Review /var/log/letsencrypt/letsencrypt.log.

Apache deploy hook fails

On the failing server, run apache2ctl configtest and systemctl status apache2. Fix Apache configuration first; the hook must not interrupt an otherwise working site. The page intentionally does not rewrite your Apache configuration.

What if reconfigure fails?

Stop and keep using the existing valid certificate. Do not delete certificates, manually edit the renewal file, run certonly for the same name, or force a live renewal until you have diagnosed the problem.

How can I verify public DNS first?

From any Linux machine with dig, query a public resolver with dig @1.1.1.1 for the NS records of the base domain and SOA for the challenge hostname. If the subdomain has separate NS delegation, identify that public zone. Public DNS may differ from internal split DNS.

Official references

SMORTLER HowTo - Return to HowTo Index. No passwords or AWS credentials are accepted. Custom entries are processed only in your browser and reset when you reload.