Knowledgebase › ACME without port 80 — DNS-01 challenges for blocked ports and wildcards

ACME without port 80 — DNS-01 challenges for blocked ports and wildcards

The standard Let's Encrypt flow uses HTTP-01: certbot serves a challenge on port 80, the CA fetches it, cert issued. This fails if port 80 is blocked, busy, or unreachable — and it can't issue wildcard certificates. DNS-01 challenges work through DNS records instead, sidestepping both problems. This article walks the setup.

When DNS-01 is the right choice

  • Port 80 is blocked. Your firewall, your ISP, or your hosting provider doesn't let inbound HTTP through.
  • Port 80 is in use by something that can't be moved. Some PBX or appliance binds to 80 and won't share.
  • You need a wildcard certificate. *.example.com can only be issued via DNS-01.
  • Your service isn't reachable from the public internet. An internal app on a LAN-only IP can still get a real cert via DNS-01.

How DNS-01 works

  1. You request a cert from Let's Encrypt.
  2. The CA gives you a challenge token.
  3. You add a TXT record at _acme-challenge.<your-domain> with the token.
  4. The CA queries DNS for the TXT record.
  5. If it matches, the cert is issued.

The challenge is that step 3 has to be automated for cert renewal to be hands-off — which means certbot needs API access to your DNS provider.

Picking a DNS provider

Whatever DNS host runs your zone, you need a way for certbot to add TXT records programmatically. Most major hosts offer an API:

  • Cloudflare — first-class, well-documented. Most certbot plugins target Cloudflare first. Generate a scoped API token.
  • DigitalOcean, Linode, Hetzner — all have certbot plugins.
  • AWS Route 53 — built-in certbot support.
  • Google Cloud DNS — built-in certbot support.
  • Generic / no API — you can delegate the _acme-challenge subdomain to a separate DNS host that does have API access (acme-dns is a tiny purpose-built DNS server for exactly this).

Install certbot with the DNS plugin

For Cloudflare:

# Debian/Ubuntu
apt install certbot python3-certbot-dns-cloudflare

# AlmaLinux/Rocky
dnf install certbot python3-certbot-dns-cloudflare

Different plugins for different DNS providers — substitute the relevant one.

Configure the Cloudflare credentials

/etc/letsencrypt/cloudflare.ini:

dns_cloudflare_api_token = <your-scoped-cloudflare-token>

Lock it down:

chmod 600 /etc/letsencrypt/cloudflare.ini

The token should have:

  • Zone → DNS → Edit (the only permission needed).
  • Zone Resources → Include → Specific zone → your domain.

Don't use your Global API Key. Use a scoped token.

Request a certificate

certbot certonly \
    --dns-cloudflare \
    --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
    -d example.com \
    -d www.example.com

Wildcard:

certbot certonly \
    --dns-cloudflare \
    --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
    -d example.com \
    -d "*.example.com"

Certbot creates the TXT record, waits for propagation, asks Let's Encrypt to verify, deletes the TXT record. Cert lands at /etc/letsencrypt/live/example.com/.

Renewal

Certbot installs a systemd timer (or cron job on older distros) that checks for renewals twice a day. With the DNS plugin and credentials in place, renewal is automatic.

Test it without actually renewing:

certbot renew --dry-run

The dry-run hits the staging environment so you don't burn your rate limit while testing.

Using the cert

Same as HTTP-01 issued certs — the files are in the same place:

  • fullchain.pem — the certificate plus intermediate.
  • privkey.pem — the private key.

For nginx, Apache, Caddy: point them at these files. For services that don't auto-reload on cert renewal, add a renewal hook:

# /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh
#!/bin/bash
systemctl reload nginx
systemctl reload postfix
# whatever else needs the new cert

Make it executable:

chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh

Delegated _acme-challenge with acme-dns

If your DNS host doesn't have an API, but does support CNAME records, set this up once:

  1. Run acme-dns on a server with a public IP (a small VPS works fine).
  2. Register a subdomain with acme-dns — it gives you a long random token to use as your credential.
  3. In your DNS, CNAME _acme-challenge.example.com to <your-acme-dns-subdomain>.
  4. Use certbot's acme-dns plugin with the credential.

Effectively you delegate the ACME challenge handling to a small DNS server you control, regardless of your main DNS host's API capabilities. acme-dns runs in tiny RAM and is the kind of service that runs for years untouched.

Common issues

  • "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.example.com." Propagation delay — DNS hasn't updated yet. Use the plugin's --dns-cloudflare-propagation-seconds 30 flag to make certbot wait longer.
  • "Unable to authenticate." Token is wrong or doesn't have the right permissions. Generate a fresh scoped token.
  • Wildcard cert issued but doesn't cover the apex. *.example.com doesn't cover example.com itself. Always include both when requesting wildcards.

Also Read

« « Back

Powered by WHMCompleteSolution