Serve a site with nginx and HTTPS

Install nginx, point a domain at your server, and get a free Let's Encrypt certificate that renews itself, on Ubuntu 24.04 or Debian 12.

By the end of this, example.com serves a page over HTTPS with a free certificate that renews itself. Swap in your own domain, and your server's addresses for 203.0.113.42 and 2001:db8:9::2.

Point the domain at the server

DNS first, because it can take a few minutes to show up and certbot needs it later. At your DNS provider, add:

text
example.com.      A      203.0.113.42
example.com.      AAAA   2001:db8:9::2
www.example.com.  A      203.0.113.42
www.example.com.  AAAA   2001:db8:9::2

Only add the AAAA records once IPv6 actually works on the server (Check that IPv6 works shows how). A broken AAAA record is the most common reason certbot fails later.

Check from your computer:

bash
dig +short example.com A
dig +short example.com AAAA

Both should print your server's addresses.

Install nginx

bash
apt update
apt install -y nginx

If you use ufw, open HTTP and HTTPS. The Nginx Full profile covers 80 and 443:

bash
ufw allow 'Nginx Full'

Browse to http://203.0.113.42 and you should see the nginx welcome page.

Add your site

Make a folder and a test page:

bash
mkdir -p /var/www/example.com
echo '<h1>It works</h1>' > /var/www/example.com/index.html

Then create /etc/nginx/sites-available/example.com:

nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Switch it on, test the config, reload:

bash
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

http://example.com should say "It works".

Get the certificate

Install certbot and its nginx plugin from the distribution:

bash
apt install -y certbot python3-certbot-nginx

Then ask for the certificate:

bash
certbot --nginx -d example.com -d www.example.com

It asks for an email address (for expiry warnings) and for you to agree to the Let's Encrypt terms. Then it proves you control the domain over port 80, saves the certificate, and edits your nginx file to serve HTTPS and redirect plain HTTP. All of that takes about a minute.

Check it:

bash
curl -I https://example.com

The first line should be HTTP/2 200 or HTTP/1.1 200 OK.

Renewals

Certificates last 90 days, and the certbot package installs a systemd timer that renews them for you. It's worth confirming that once rather than finding out in three months:

bash
systemctl list-timers certbot.timer
certbot renew --dry-run

The dry run should finish with Congratulations, all simulated renewals succeeded.

If it fails

  • nginx: [emerg] from nginx -t: the message names the file and line. Nine times out of ten it's a missing ;.
  • Certbot says Timeout during connect or Connection refused: Let's Encrypt can't reach port 80. Check ufw status allows Nginx Full, and that the A record points here.
  • It fails only over IPv6: the AAAA record points at an address that doesn't answer. Fix IPv6, or remove the AAAA record and try again.
  • "Too many failed authorizations": Let's Encrypt rate-limits failures. Practise against staging with certbot certonly --nginx --dry-run -d example.com -d www.example.com until it passes, then run the real command.

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.