| name | configure-dns-tls |
| description | Configures DNS records and TLS for a service — A/AAAA/CNAME/ALIAS/MX/TXT/CAA, zero-downtime cutovers via pre-lowered TTL, automated ACME/Let's Encrypt/cert-manager issuance and auto-renewal, and TLS 1.2+/1.3-only settings with HSTS, OCSP stapling, and 80→443 redirect — eliminating expired-cert and bad-cutover outages. |
| when_to_use | Pointing a domain at a service, enabling HTTPS, automating/rotating certificates (ACME/cert-manager), or migrating DNS. Distinct from configure-reverse-proxy-lb (the proxy/LB that terminates the TLS this issues) and setup-cdn-edge-waf (the CDN/WAF edge in front). |
When to Use
Reach for this skill when the task is names and certificates — getting a domain to resolve to your service and serving valid HTTPS that renews itself:
- "Point
app.example.com at this load balancer / IP without downtime"
- "Enable HTTPS / fix the expired-cert outage / stop the cert from ever expiring again"
- "Automate certs with Let's Encrypt / cert-manager; issue a wildcard"
- "Migrate DNS to a new provider / cut over to a new origin"
- "Lock down SPF/DKIM/DMARC, or CAA so only my CA can issue"
- "Why does SSL Labs give us a B? Harden the TLS config"
NOT this skill:
- Configuring the proxy/LB/Ingress that terminates TLS, virtual hosts, upstream pools, timeouts → configure-reverse-proxy-lb
- The CDN/edge, WAF rules, edge caching, or DDoS layer in front of origin → setup-cdn-edge-waf
- Application-layer auth/authz, token scopes, RBAC → design-authorization-model
- Tamper-evident security event logs (incl. cert-rotation events) → build-audit-logging
This skill owns the record values, the cutover choreography, certificate lifecycle, and the TLS handshake policy. It hands the terminated connection to the proxy.
Steps
-
Pick the record type by what you're pointing at — do not CNAME the apex.
| Need | Record | Notes |
|---|
| Name → IPv4 | A | Bare IP only |
| Name → IPv6 | AAAA | Add alongside A; serve dual-stack |
| Subdomain → another hostname | CNAME | e.g. www → app.example.com; cannot coexist with other records on that name |
Apex (example.com) → hostname | ALIAS/ANAME/flattened-CNAME | Apex can't be a real CNAME (breaks SOA/NS/MX). Use the provider's ALIAS (Route 53 alias, Cloudflare CNAME-flattening, etc.) |
| Mail | MX | Priority + target; target must be an A/AAAA, never a CNAME |
| SPF/DKIM/DMARC/verification | TXT | One SPF per domain; DMARC at _dmarc; DKIM at <sel>._domainkey |
| Who may issue certs | CAA | 0 issue "letsencrypt.org" + 0 issuewild "letsencrypt.org" |
Set CAA before first ACME issuance, or issuance fails with CAA record prevents issuance. Example:
example.com. CAA 0 issue "letsencrypt.org"
example.com. CAA 0 issuewild "letsencrypt.org"
example.com. CAA 0 iodef "mailto:security@example.com"
-
Zero-downtime cutover: lower the TTL BEFORE the change — this is the whole trick. Resolvers cache the old answer for up to its TTL; if you cut over while TTL is 3600, clients hit the dead origin for an hour.
- Drop the record's TTL to
60 (or 30). Wait out the old TTL (e.g. wait the full prior 3600s) so every cache holds the short TTL.
- Run both origins in parallel (old + new healthy) during the switch — never tear down old first.
- Change the record value to the new target.
- Verify the new answer is served (step in Verify) and the new origin takes real traffic.
Common Errors
- CNAME on the apex. Breaks NS/SOA/MX co-existence; many resolvers reject it. Use ALIAS/ANAME/CNAME-flattening for
example.com.
- Cutover without pre-lowering TTL. You switch the record but caches serve the dead origin for the full old TTL (often an hour). Lower TTL and wait out the old TTL first.
- Raising TTL or killing the old origin too early. Do it only after old-origin logs go quiet for > one TTL; otherwise stragglers 502.
- Missing/forbidding CAA. No CAA = any CA may issue (security gap); a CAA that omits your CA = ACME fails with
CAA record prevents issuance. Add the issuing CA explicitly, including issuewild for wildcards.
- HTTP-01 for a wildcard. Impossible — wildcards require DNS-01. Switch the solver.
- Manual cert renewal "we'll remember." You won't. The outage is scheduled for expiry day. Automate or it will lapse.
- Serving only the leaf cert. Browsers cache intermediates and "work";
curl, Java, old Android, and API clients fail chain validation. Always deploy fullchain.pem.
- Burning LE rate limits while debugging. Iterate against
acme-staging-v02 (or certbot --test-cert); only hit prod once issuance succeeds in staging.
includeSubDomains/preload HSTS before all subdomains are HTTPS. Any plain-HTTP subdomain becomes unreachable, and preload is baked into browsers for months. Roll HSTS out short → long → preload.
- DNS-01 with under-scoped API creds. The token can't write
_acme-challenge TXT, so renewal silently fails. Scope the token to DNS-edit on that zone and test it.
- Mixed content after enabling HTTPS. Page loads over HTTPS but pulls
http:// assets → browser blocks them. Rewrite asset URLs to https:// or protocol-relative; verify console is clean.
- Clock skew on the TLS host. A wrong system clock makes a valid cert read as not-yet-valid/expired. Run NTP.
Verify
- Records resolve correctly:
dig +short A app.example.com (and AAAA) returns the new target; dig CAA example.com shows your CA; dig TXT _dmarc.example.com shows the DMARC policy. Query an external resolver (dig @1.1.1.1 …) too, not just the local cache.
- TTL was actually lowered before cutover:
dig app.example.com | grep -E '^app' shows the short TTL before you change the value; confirm the answer flips after, and that it propagated (dig @8.8.8.8 and @1.1.1.1 agree).
- Full chain + protocol scan:
echo | openssl s_client -connect example.com:443 -servername example.com -showcerts shows leaf and intermediate(s), Verify return code: 0 (ok). testssl.sh example.com (or SSL Labs) reports TLS 1.2/1.3 only, no TLS 1.0/1.1, HSTS present, OCSP stapled — target grade A/A+.
- Redirect + HSTS:
curl -sI http://example.com → 301 to https://; curl -sI https://example.com | grep -i strict-transport shows the HSTS header.
- No mixed content: load the page, browser console shows zero "Mixed Content" / blocked-asset warnings; all subresources are
https://.
- Expiry & auto-renew proven:
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -enddate shows a future date; then force a staging renewal — certbot renew --dry-run (VM) or, for k8s, point the issuer at acme-staging-v02, run cmctl renew example-tls, and watch cmctl status certificate example-tls go Ready — and confirm a fresh cert issues without manual steps.
- Mail auth (if MX set): SPF/DKIM/DMARC TXT records validate (e.g. an external mail-tester) — no
softfail/missing-DKIM.
Done = every name resolves to the new target on external resolvers, HTTPS serves the full chain over TLS 1.2/1.3 only with HSTS + stapling + 80→443 redirect and no mixed content (SSL Labs/testssl ≥ A), CAA locks issuance to your CA, and a staging force-renew has proven auto-renewal works before any cert nears expiry.