Back to Blog
Ssl

DNS vs HTTP Domain Validation: What Actually Happens Behind the Scenes

A practical breakdown of how CAs validate domain ownership, why DNS-01 beats HTTP-01 in most setups, and the gotchas nobody warns you about.

CertGuard Team··7 min read

You picked the wrong validation method

Most people requesting their first SSL certificate just click through the defaults. Whatever the CA offers, whatever the ACME client suggests. And it works. Until it doesn't, and you're staring at a failed renewal at 2 AM because your load balancer ate the challenge token.

Domain validation is the process where a Certificate Authority confirms you actually control the domain you're requesting a cert for. Sounds straightforward. Three main flavors exist: HTTP-01, DNS-01, and TLS-ALPN-01. Each one has sharp edges that the documentation glosses over.

HTTP-01: the path of least resistance (and most breakage)

HTTP-01 is what most people start with. The CA gives you a random token, you stick it at http://yourdomain.com/.well-known/acme-challenge/<TOKEN>, the CA fetches it, done. Certbot and most ACME clients handle this automatically.

Where it falls apart:

  • Your reverse proxy rewrites the path before it hits the webroot. Nginx location blocks are notorious for this.
  • You're behind a CDN like Cloudflare with caching enabled. The CA's validation server might hit a cached 404 from a previous failed attempt.
  • Port 80 is blocked by your hosting provider or firewall rules. HTTP-01 requires port 80, period. No negotiation.
  • You have multiple servers behind a load balancer and the token file only exists on one of them.

That last one bites teams constantly. I've seen a mid-size SaaS company with 12 backend nodes spend an entire afternoon debugging why renewals kept failing. The certbot standalone mode was binding to port 80 on whichever node the cron job happened to run on, but the CA's request hit a different node through the load balancer. Classic.

The fix was switching to DNS-01. Took 20 minutes.

DNS-01: more setup, fewer surprises

With DNS-01, you prove domain ownership by creating a specific TXT record. The CA asks you to set _acme-challenge.yourdomain.com to a particular value, then queries DNS to verify it.

Why this is better for most production setups: it doesn't care about your web server configuration at all. No ports, no paths, no proxies. Just DNS.

But. And this is a big but. You need programmatic access to your DNS provider's API. If you're managing DNS through some ancient control panel that only supports manual record creation, DNS-01 becomes a manual nightmare. Every 60 to 90 days, someone has to log in and update a TXT record. People forget. Certificates expire.

The good news: every major DNS provider has an API now. Cloudflare, Route 53, Google Cloud DNS, DigitalOcean, even GoDaddy (though their API rate limits are painful). Certbot has plugins for most of them:

# Cloudflare example - store your API token in a file, not an env var
# permissions: Zone:DNS:Edit for the specific zone only
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.example.com" \
  -d "example.com"

Notice the wildcard there. HTTP-01 cannot issue wildcard certificates. Full stop. If you need *.yourdomain.com, DNS-01 is your only option with Let's Encrypt.

The propagation trap

DNS propagation is the silent killer of DNS-01 validation. You create the TXT record, the CA queries it within seconds, and... nothing. The record hasn't propagated to the nameservers the CA happens to query.

Let's Encrypt uses multiple vantage points for validation now (they started doing this after some BGP hijacking concerns in 2020). Your TXT record needs to be visible from different geographic locations, not just your local resolver.

Practical advice: add a propagation delay to your ACME client config. Certbot's Cloudflare plugin defaults to 10 seconds, which is usually fine for Cloudflare since their DNS is fast. But if you're on a slower provider, bump it up:

# For slow DNS providers, 120 seconds is safe
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 120 \
  -d "example.com"

Or, better yet, script a check that actually queries public DNS before telling the CA to validate:

#!/bin/bash
# wait-for-dns.sh - poll until the TXT record is visible
DOMAIN="_acme-challenge.example.com"
EXPECTED_VALUE="$1"

for i in $(seq 1 30); do
  RESULT=$(dig +short TXT "$DOMAIN" @8.8.8.8 | tr -d '"')
  if [ "$RESULT" = "$EXPECTED_VALUE" ]; then
    echo "DNS propagated after $((i * 10)) seconds"
    exit 0
  fi
  sleep 10
done

echo "DNS propagation timed out after 300 seconds"
exit 1

TLS-ALPN-01: the forgotten third option

Almost nobody uses this one. TLS-ALPN-01 validates through a TLS handshake on port 443 using a special ALPN protocol identifier. It was designed as a replacement for the deprecated TLS-SNI challenges.

When would you actually use it? Basically only when port 80 is blocked AND you can't modify DNS. Some locked-down enterprise environments fit this description. The Caddy web server supports it natively, which is one of the reasons Caddy's automatic HTTPS "just works" in situations where Nginx plus Certbot struggles.

For everyone else, pretend this method doesn't exist.

Validation from the CA's perspective

Something most engineers never think about: the CA doesn't just check once. Let's Encrypt validates from multiple network perspectives to prevent BGP hijacking attacks. If an attacker can reroute traffic from one vantage point, they still can't satisfy validation from all of them simultaneously.

Google Trust Services (their CA) does something similar. And the CAB Forum has been pushing multi-perspective validation as a baseline requirement. After the research papers demonstrating practical BGP attacks against domain validation in 2018 and 2019, the industry took it seriously.

What this means for you: if your DNS or web server is only accessible from certain regions (geo-blocking, for instance), validation might fail from vantage points that can't reach you. Temporarily relaxing geo-restrictions during certificate issuance is an annoying but sometimes necessary workaround.

DV, OV, EV: does the validation level matter anymore?

Quick tangent. Domain Validation (DV) is the automated stuff we've been discussing. Organization Validation (OV) and Extended Validation (EV) require the CA to verify your legal entity, physical address, phone number, the works. EV used to give you that green bar in browsers.

Chrome killed the green bar in 2019. Firefox followed. The visual distinction between DV, OV, and EV certificates is now essentially zero for end users.

Does that make OV and EV useless? Not entirely. Some regulated industries (finance, healthcare) require OV or EV for compliance reasons. And there's an argument that OV/EV certificates are harder for phishers to obtain, since you can't just spin up a domain and get one automatically.

But for 95% of websites? DV with Let's Encrypt is the correct choice. Free, automated, 90-day certificates that force you to have working automation. The short lifetime is a feature, not a bug.

Common validation failures and how to fix them fast

A quick reference for when things break at the worst possible time:

CAA record blocking issuance. DNS CAA records specify which CAs are allowed to issue certificates for your domain. If you have a CAA record that only allows DigiCert but you're trying to use Let's Encrypt, validation will fail with a cryptic error. Check with dig CAA yourdomain.com. Add 0 issue "letsencrypt.org" if needed.

DNSSEC validation failure. If your domain has DNSSEC enabled and the signatures are expired or misconfigured, the CA will refuse to trust the DNS response. DNSSEC issues are particularly brutal because everything might look fine from your local resolver (which might not validate DNSSEC) while the CA's resolver rejects it.

Rate limits. Let's Encrypt has rate limits. 50 certificates per registered domain per week. 5 failed validations per account per hostname per hour. If you're testing with production domains, you'll hit these. Use the staging environment: --staging flag in Certbot, https://acme-staging-v02.api.letsencrypt.org/directory for other clients.

# always test with staging first
sudo certbot certonly --staging \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "test.example.com"

# staging certs aren't trusted by browsers, but they prove your setup works
# once it passes, remove --staging and run for real

Pick the right method for your setup

Single server, direct internet access, no CDN? HTTP-01 is fine. Keep it simple.

Load balanced, behind CDN, or need wildcards? DNS-01, every time. Invest the 30 minutes to set up API credentials for your DNS provider. Future you will be grateful at 2 AM when renewals just work.

Locked-down enterprise with no DNS control and no port 80? TLS-ALPN-01 with Caddy. Or, honestly, just buy a commercial cert with a longer validity and skip the ACME dance entirely.

Whatever you pick, test the renewal process before you need it. Run certbot renew --dry-run after initial setup. Set up monitoring that alerts you at 30, 14, and 7 days before expiry. Because validation method doesn't matter much if you never notice the renewal failed.