Issuing Certificates Before DNS Cutover

Problem Statement

You need www.example.com to serve valid TLS from a new origin at 203.0.113.10, but the certificate authority proves you control that hostname by fetching a file from whatever www.example.com currently resolves to — which is the legacy origin. Issue the certificate the usual way and it certifies a server you are about to switch off; skip issuance until after the DNS change and every visitor in the first minutes gets a browser interstitial. This circular dependency is the single most common reason a well-planned cutover produces a TLS outage, and it has a clean solution. This page sits under TLS & Certificate Cutover and covers obtaining and installing a valid certificate on a host that nothing yet resolves to.

When to Use This Approach

  • You are moving a hostname to a new origin, new host, or new CDN and TLS terminates somewhere different afterwards.
  • The hostname must keep serving through the switch with no certificate warning at any point.
  • You control the DNS zone, or can have _acme-challenge TXT records added on request.
  • You need to test the new origin’s TLS end to end before any traffic reaches it.
  • The certificate must cover several hostnames, including some that do not yet exist publicly.

Step-by-Step Instructions

1. Choose the DNS-01 Challenge

An HTTP-01 challenge asks the authority to fetch a token over HTTP at the hostname being certified, which before cutover is answered by the legacy origin. A DNS-01 challenge instead asks you to publish a TXT record at _acme-challenge.<hostname>, which proves control of the name without involving whichever server currently answers for it. That distinction is the whole reason this works.

# certbot with a DNS plugin — no traffic to the new host is required
certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cf.ini \
  -d example.com -d www.example.com -d api.example.com \
  --preferred-challenges dns-01

2. Cover Every Hostname in One Request

List each name the new origin will serve, including any that do not resolve publicly yet. Adding a hostname later means a fresh issuance and a fresh deployment, so the cheapest moment to be thorough is now. Build the list from the DNS zone and the crawl inventory rather than from memory — the names that get missed are asset subdomains referenced only in stylesheets and API hosts called only by a mobile client.

# Enumerate candidate hostnames from the zone before requesting anything
dig AXFR @ns1.provider.com example.com | awk '$4 ~ /^(A|AAAA|CNAME)$/ {print $1}' | sort -u

Watch the rate limits while you iterate. Public authorities cap issuance per registered domain over a rolling window, and a cutover rehearsal that re-requests certificates on every attempt can exhaust that allowance at exactly the wrong moment. Use the staging environment for anything experimental, reserve production issuance for the certificate you intend to deploy, and use --dry-run for renewal testing so the exercise costs nothing.

3. Install and Test by IP, Before Any DNS Change

Deploy the certificate and its full chain on the new origin, then exercise the handshake against that host’s address while sending the real hostname as SNI. This tests certificate, chain, private key and cipher configuration end to end against a server that no visitor is reaching yet, so a failure costs nothing but the time to fix it.

# --resolve pins the connection to the new IP while sending the real SNI
curl -sIv --resolve www.example.com:443:203.0.113.10 \
  https://www.example.com/ 2>&1 | grep -Ei 'subject:|issuer:|SSL certificate verify|^< HTTP'

4. Prove Renewal Before You Depend On It

A certificate obtained by hand to unblock a cutover expires ninety days later with nobody watching. Configure the renewal path on the new host and force a dry run so you know the credentials, the plugin and the post-renewal reload all work — then confirm the reload actually causes the new certificate to be served, which is a separate thing from the renewal succeeding.

# Exercise the whole renewal path without consuming rate limit
certbot renew --dry-run --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cf.ini
systemctl reload nginx && echo "reload hook works"
Why HTTP-01 cannot certify a host that nothing resolves to An HTTP-01 challenge follows DNS to the legacy origin and certifies the wrong server, while a DNS-01 challenge is answered from the zone and proves control of the name independently of which host is currently serving it. The challenge type decides which server gets certified HTTP-01 fetches a token by URL DNS-01 reads a TXT record follows current DNS → the legacy origin reads the zone → no server involved certifies the wrong server usable on the new origin This is also why DNS API access appears in the cutover prerequisites — without it, the only certificate you can obtain is one for the server you are leaving.
Both challenges prove domain control; only one of them does so without asking the server you are migrating away from.

Worked Example

The sequence below is a hosting move where the hostname is unchanged and only the origin behind it differs — the case in which the circular dependency bites hardest. A team is moving www.example.com from a legacy VM to a container platform at 203.0.113.10. Requesting a certificate with the default HTTP-01 flow succeeds — and installs a certificate on the legacy VM, which is not where they need it. They switch to DNS-01 using their provider’s API credentials, issuing one certificate covering example.com, www.example.com and assets.example.com, and install the resulting fullchain.pem on the new platform.

Testing with --resolve against the new IP reveals a second problem before any visitor could hit it: the platform was configured with cert.pem rather than fullchain.pem, so the handshake presented a leaf certificate with no intermediate. Their browsers accepted it — the intermediate was cached from other sites — and curl did not:

$ curl -sIv --resolve www.example.com:443:203.0.113.10 https://www.example.com/
* SSL certificate problem: unable to get local issuer certificate

They repoint the configuration at the full chain, re-test, and get a clean HTTP/2 200. Only then do they lower the TTL per How to Lower DNS TTL Before Domain Migration and schedule the record change. At cutover the handshake simply works, because it has already been working for two days against an origin nobody was using.

Certificate file choices and what each one serves The three files an ACME client produces compared by what each contains and which clients accept a handshake presenting it, showing that only the full chain satisfies strict clients. Pointing at the wrong file is the usual chain defect File Contains Browsers Strict clients cert.pem leaf only usually fine reject chain.pem intermediates only fail fail fullchain.pem leaf + intermediates accept accept The first row is the dangerous one: it looks correct to everyone testing in a browser and fails for apps, payment gateways and server-to-server calls. Point the server at fullchain.pem and count the certificates served to confirm.
An ACME client writes three files and only one of them is the right thing to serve — which is why the count check below is worth running every time.

Keep the private key on the new host and never move it around casually. Certificate deployment tends to involve copying files between machines under time pressure, which is how keys end up in a shared drive, in a chat message, or committed to a repository. Generate the key where it will be used, transfer only the certificate, and if a key genuinely must move, treat it as a credential — encrypted channel, restrictive permissions on arrival, and a note to rotate it once the migration settles.

Verification

Prove the new origin is TLS-ready while the hostname still points elsewhere.

# 1. Handshake succeeds against the new IP with the real SNI
curl -sI --resolve www.example.com:443:203.0.113.10 https://www.example.com/ | head -1

# 2. At least two certificates are presented (leaf + intermediate)
openssl s_client -connect 203.0.113.10:443 -servername www.example.com \
  -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERTIFICATE'

# 3. Every intended hostname appears in the SANs
openssl s_client -connect 203.0.113.10:443 -servername www.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

A clean result is an HTTP/2 200 from the first command, a count of two or more from the second, and every hostname from your enumeration present in the third. Run all three again through the public hostname after the record changes.

Sequencing certificate issuance against the DNS change Certificate issuance, installation and verification all completing while the hostname still resolves to the legacy origin, with the TTL reduction and record swap following afterwards. Everything TLS finishes before anything DNS starts TLS work DNS work DNS-01 issue install chain verify by IP lower the TTL swap the record The TLS row completes entirely against a host no visitor is reaching, so every failure in it is free to fix. Reversing this order is what turns a certificate misconfiguration into a customer-visible interstitial.
The overlap in the middle is deliberate — TTL reduction can begin once the certificate is verified, but the record swap waits for both.

FAQ

Can I use HTTP-01 if I temporarily point the hostname at the new origin? You can, and it defeats the purpose: pointing the hostname at a server with no valid certificate is exactly the outage you are trying to avoid, even briefly. The one variation that works is issuing against a different hostname that already points at the new origin and using a certificate covering both — but that requires the certificate to be issued for a name you can already validate, which brings you back to needing DNS-01 for the name you actually care about.

Does the _acme-challenge TXT record need to stay in place? No. It is consumed during validation and can be removed afterwards, and most DNS plugins clean it up automatically. What does need to persist is the credential the plugin uses, because renewal repeats the same challenge — so the token is transient and the API access is not. Store that credential as a secret with an owner rather than in a personal account.

What if DNS is managed by another team and API access is not available? Ask for a delegated _acme-challenge CNAME pointing at a zone you do control. This is the standard workaround: the other team makes one static change, and every subsequent validation and renewal happens in your zone without further involvement from them. It is far easier to get approved than ongoing API credentials, and it removes a recurring dependency from your renewal path.

How early should the certificate be issued? Early enough to test properly, which usually means several days before the window rather than the night before. Certificates are valid from issuance, so there is no cost to obtaining one early beyond starting the expiry clock — and the value of having the new origin’s TLS proven for days before cutover is that any problem surfaces while it is still cheap. Just confirm the expiry leaves comfortable room after the migration.

Related

← Back to TLS & Certificate Cutover