---
title: "Discussion on \"Custom Domain + Free SSL on a VPS: Nginx and Certbot Done Right (2026)\" | Hashnode"
description: "Discussion on \"Custom Domain + Free SSL on a VPS: Nginx and Certbot Done Right (2026)\". 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"
url: "https://hashnode.com/posts/custom-domain-free-ssl-on-a-vps-nginx-and-certbot-done-right-2026-mw0hqmb6/6ae9aa88ebf1c0e9c06cfcc7"
---

[Feed](https://hashnode.com/feed) /

Discussion

# [Custom Domain + Free SSL on a VPS: Nginx and Certbot Done Right (2026)](https://vishvajeetshukla.hashnode.dev/custom-domain-free-ssl-on-a-vps-nginx-and-certbot-done-right-2026-mw0hqmb6?utm_source=hashnode&utm_medium=feed)

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

Nov 3 · 5 min read · vishvajeetshukla.hashnode.dev  
Read the article

-   [#nginx](https://hashnode.com/tag/nginx)
-   [#ssl](https://hashnode.com/tag/ssl)
-   [#devops](https://hashnode.com/tag/devops)
-   [#webdev](https://hashnode.com/tag/webdev)

## Written by

[Vishvajeet Shukla](https://hashnode.com/@vishvajeetshukla)

@vishvajeetshukla · Jaipur, India

  
[View profile](https://hashnode.com/@vishvajeetshukla)

Full Stack Developer | MERN, Next.js & DevOps

Building Shipown (shipown.com)

## Responses 1

Markdown supported · ⌘↵ to post

[CDN Edge Ops](https://hashnode.com/@cdnedgeops)

[Nov 4](https://hashnode.com/posts/custom-domain-free-ssl-on-a-vps-nginx-and-certbot-done-right-2026-mw0hqmb6/6ae9aa88ebf1c0e9c06cfcc7/comment/6aeba556b80b4586648b9c4b)

Certbot and nginx only stay healthy when server\_name, the HTTP-01 (or TLS-ALPN) challenge path, and the live TLS server block agree on the same hostname. Drift between www and bare, or a challenge location that never hits the issuing vhost, is what makes renew look random while the site still serves an old cert.

I terminate TLS at nginx, set Host and X-Forwarded-Proto for any proxied app, and verify after each renew with a HEAD to the public name checking both the certificate SAN and any redirect Location. Keep port 80 open for the challenge (or use DNS-01 if you must close it), reload only after the new fullchain is on disk, and watch renew timers separately from deploy reloads so a release never races a cert swap. If browsers show NET::ERR\_CERT\_COMMON\_NAME\_INVALID after a domain add, the missing name is almost always in the cert — not in proxy\_pass.
