TLS Certificates with Certbot on an Ubuntu VPS 2026

There is no longer any reason to serve a website over plain HTTP. A trusted TLS certificate costs nothing, browsers flag sites without one as "Not Secure," and search engines treat HTTPS as a ranking signal. The piece that used to be painful — issuing, installing, and renewing certificates every ninety days — is now fully automated by Certbot, the EFF's Let's Encrypt client. This guide takes an Ubuntu VPS running Nginx from no certificate to a valid, auto-renewing one, and verifies that the renewal will actually fire long before the certificate expires.

Quick Verdict
Install Certbot with the Nginx plugin, run certbot --nginx, and it obtains a certificate and edits your Nginx config to use it in one step. Renewal is already automated by a systemd timer the moment the package installs — your only job is to prove it works with certbot renew --dry-run and add a reload hook so Nginx picks up each new certificate.

Prerequisites

Before you start, two things must already be true. First, a DNS A record (and AAAA for IPv6) for your domain must point at the server's public IP — Let's Encrypt validates that you control the domain by reaching it over the network, so a misconfigured record is the most common failure. Second, Nginx must be serving the domain on port 80 with a server_name that matches, and the firewall must allow HTTP and HTTPS:

1sudo ufw allow 'Nginx Full'

Confirm the site answers on plain HTTP before involving Certbot — if it does not, the certificate request will fail validation.


Install Certbot

The EFF recommends installing Certbot via snap so you always run the current release, but the Ubuntu archive package is simpler on a single server and is what most VPS setups use. Install Certbot together with its Nginx plugin:

1sudo apt update
2sudo apt install certbot python3-certbot-nginx

The python3-certbot-nginx plugin is what lets Certbot read and edit your Nginx configuration automatically. Without it you can still issue certificates, but you would have to wire them into Nginx by hand.


Obtain and Install the Certificate

This is the whole issuance process in one command:

1sudo certbot --nginx

Certbot lists the server_name values it found in your Nginx config and asks which to secure. It then completes an HTTP-01 challenge with Let's Encrypt — placing a temporary token your server serves back to prove control — obtains the certificate, and rewrites your Nginx server block to reference the new certificate and key. You will be prompted once for an email (used for expiry warnings) and to agree to the terms.

When it finishes, Certbot reloads Nginx and your site is live on HTTPS. If you prefer to issue the certificate without touching the Nginx config yourself, --nginx is the option that does both; use certonly --nginx if you want the certificate but intend to wire it in manually.

Verify the result in a browser, or from the command line:

1curl -sI https://example.com | head -n 1

A HTTP/2 200 (or HTTP/1.1 200 OK) over HTTPS confirms the certificate is installed and trusted.


Understand Where Things Live

Certbot stores everything under /etc/letsencrypt/. Two paths matter:

  • /etc/letsencrypt/live/example.com/ holds the current fullchain.pem and privkey.pem your Nginx config points at. These are symlinks that always track the newest certificate, which is why renewal does not require editing Nginx.
  • /etc/letsencrypt/renewal/example.com.conf records how this certificate was issued, so certbot renew can repeat the process unattended.

Never edit the files under live/ by hand or copy them elsewhere — point Nginx at the symlinks and let Certbot manage what they resolve to.


Confirm Renewal Is Automated

Let's Encrypt certificates are valid for ninety days, and Certbot is designed to renew them when they have thirty days left. On a package install, this is already wired up by a systemd timer — there is nothing to schedule yourself. Confirm the timer exists and is active:

1systemctl list-timers | grep certbot

You should see certbot.timer listed with a next-run time. The timer runs certbot renew twice a day; the command is a no-op until a certificate is inside its renewal window, so running often is harmless and means a renewal is never missed by much.


Test the Renewal Before You Depend On It

A renewal that silently fails leaves you with an expired certificate and a site full of browser warnings — discovered at the worst possible time. Test the entire renewal path now, against Let's Encrypt's staging server, without touching your real certificate:

1sudo certbot renew --dry-run

This performs a full practice renewal — issuing a throwaway staging certificate and discarding it — so a success here means the real renewal in two months will also succeed. If it fails, the output tells you why (almost always DNS or a port-80 block), and you fix it now while the live certificate is still valid, not after it lapses.

It is also worth knowing how the validation actually reaches you. The default HTTP-01 challenge needs port 80 open to the world for the few seconds the challenge runs — if a firewall rule or a security group closes it, renewal fails even though your site works fine on 443. For wildcard certificates or servers with no inbound HTTP at all, the DNS-01 challenge is the alternative: Certbot proves control by creating a temporary TXT record instead, which requires a DNS provider plugin but removes the port-80 dependency entirely. For a single domain on a standard VPS, HTTP-01 is simpler and is what certbot --nginx uses by default.


Reload Nginx on Each Renewal

Renewing the certificate updates the files under live/, but the running Nginx process keeps using the certificate it loaded at start-up until it is reloaded. Certbot's Nginx plugin normally handles this, but make it explicit with a deploy hook — a script that runs only after a successful renewal. Drop it in the renewal-hooks directory:

1sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh <<'EOF'
2#!/bin/sh
3systemctl reload nginx
4EOF
5sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Every script in /etc/letsencrypt/renewal-hooks/deploy/ runs after each certificate that renews successfully, and reload applies the new certificate with no dropped connections. A deploy hook runs only on success — unlike a post-hook, it never fires after a failed attempt — so Nginx is never told to reload a certificate that did not actually change.


Set It and Verify It

The reason TLS is no longer a chore is that the hard parts are automated end to end: issuance with one command, renewal on a timer you did not have to write, and a reload hook that swaps the certificate in without downtime. The one piece of work that remains yours is verification — running certbot renew --dry-run so you know the automation works before you rely on it, rather than discovering a broken renewal from a "Not Secure" warning ninety days from now.

Install the plugin, run certbot --nginx, prove the renewal with a dry-run, and add the deploy hook. After that, your VPS keeps itself encrypted indefinitely with no further attention.

References

Posts in this series