Let's Encrypt with Certbot and Nginx on Ubuntu
Issue a Let's Encrypt certificate with certbot and nginx on Ubuntu, complete HTTP-01, and wire the live cert into a working TLS server block.
Netbay Engineering
Netbay Engineering
On this page
Certbot plus nginx is still the most boring way to get a trusted certificate on a single Ubuntu VPS. Boring is the goal. You install the packages, serve port 80 for the HTTP-01 challenge, let certbot write the leaf and chain, and point nginx at live/fullchain.pem and live/privkey.pem. This post is that path with the failure points that actually happen: A record not pointing here, port 80 filtered, a default server catching the challenge, and a TLS block that still serves the snakeoil cert.
Assume Ubuntu 24.04, nginx from the distro, and a domain whose A record already matches the public IPv4 of the VPS. If DNS is wrong, stop and fix DNS first. Certbot cannot negotiate with a name that hits another machine.
Install nginx and certbot, open port 80
Install nginx, certbot, and the nginx plugin. Enable the unit. Confirm something answers on port 80 from the public IP. A host firewall that allows 80 only from your laptop will pass your curl and fail Let's Encrypt, which checks from multiple vantage points.
apt update
apt install -y nginx certbot python3-certbot-nginx
systemctl enable --now nginx
ss -lntp | grep -E ":80|:443"
ufw allow 80/tcp
ufw allow 443/tcp
dig +short A example.com
curl -4 -I --max-time 5 http://example.com/The A record must equal this VPS. If curl shows another server's default page, do not run certbot yet. L3/L4 DDoS filtering on the VPS does not replace an open port 80; it only drops junk. You still need the challenge path reachable.
A port 80 server block the plugin can find
Certbot's nginx plugin looks for a server_name that matches the requested hostname. If the only server is default_server with no name, the plugin may guess wrong or inject into the wrong file. Put a dedicated HTTP block in /etc/nginx/sites-available/example.com and enable it.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example;
location /.well-known/acme-challenge/ {
default_type text/plain;
}
location / {
return 200 "http-ok
";
}
}nginx -t && systemctl reload nginx. Visit http://example.com/ and http://www.example.com/. Both names should be in DNS if you will request both on the certificate. Requesting www without an A or CNAME is a failed authorization, not a warning.
Issue with the nginx plugin
The plugin can obtain the cert and write ssl_* directives for you. Read the diff it wants to apply. On a first box, this is the usual command:
certbot --nginx -d example.com -d www.example.com --redirect
That runs HTTP-01, stores files under /etc/letsencrypt/live/example.com/, and rewrites the server block to listen 443 ssl with a 80-to-443 redirect. Use --dry-run first if you have already hit rate limits this week. Staging (--staging) issues untrusted certs; useful for tests, not for users.
If you prefer not to let certbot edit nginx, use certbot certonly --webroot -w /var/www/example -d example.com -d www.example.com and then set ssl_certificate and ssl_certificate_key yourself. That is more typing and easier to review in git.
After success, the live directory is a set of symlinks. Always point nginx at live/, not at archive/ with a dated filename. archive/ is the history; live/ is the current pair that renewal will retarget.
Confirm TLS and the chain nginx actually serves
Reload nginx and inspect what the client sees, not just what the file contains.
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
You want a leaf for example.com (and www if requested) plus the Let's Encrypt intermediate in the same handshake. ssl_certificate should be fullchain.pem, not cert.pem alone. Serving cert.pem without the intermediate is the "works in some browsers, fails in others" bug. ssl_certificate_key must be privkey.pem. Permissions: private keys stay 0600 root, and the nginx worker reads them at start or reload as root.
If the handshake still presents a default self-signed cert, you have two server blocks on 443 and the default_server is not the one you edited. nginx -T | grep -E "listen|server_name|ssl_certificate" is the debug tool.
Certbot plus nginx is a short path: DNS A matches, port 80 open, server_name matches, plugin or webroot issues, nginx uses fullchain and privkey, then reload. Rate limits punish loops; dry-run and staging exist so you do not burn production issuances. You can stand up Ubuntu 24.04 on Netbay in Lucknow (DC01) in under 60 seconds and run this sequence on a real public IP — netbayhosts.in.
Keep reading
Follow along on a real VPS
Deploy Linux in under 60 seconds
These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.
Deploy an instance