Your app is running on the server. curl localhost:3000 works. Now you want https://app.example.com with a padlock, and that last mile (DNS, Nginx, certificates, renewals) is where a surprising number
Markdown supported · ⌘↵ to post
Apex hostnames are the usual DNS snag here: most providers reject a CNAME at the zone root, so the bare domain needs an A or AAAA record aimed at the VPS while www can be a CNAME to that same name. Lower the TTL on those records to a few minutes a day before cutover, otherwise a wrong target stays cached long after you fix it. Certbot's HTTP-01 challenge only completes when the Nginx server_name matches the exact hostname on the certificate request, including apex versus www and any extra names on the same cert. After issuance, curl -I on both names should show one 301 to the canonical host, the expected certificate, and no bounce back to plain HTTP. Leave port 80 reachable for that same server_name, because a later config that only redirects will make the next HTTP-01 renewal fail. If a proxy sits in front later, the public A record has to point at that proxy or the live certificate and the challenge path will disagree.
the www vs non-www section and the old BASE_URL / NEXTAUTH_URL warning are the parts that save people the most time. two small things to add: a CAA record that doesn’t allow letsencrypt.org will block issuance, and wildcard certs need the DNS challenge instead of the HTTP one. (i’m on the cloudways team, where let’s encrypt install and renewal is built in, but these checks apply on any host.)
CDN Edge Ops
For renewals I keep a timer that runs
certbot renew --dry-runin staging first, then the real renew plusnginx -tbefore reload. Most HTTP-01 failures I see are still a stale server_name or the challenge path proxied away from the ACME location.