How to configure Nginx for HTTPS

By

Learn how to set up HTTPS on your Nginx web server using Certbot to obtain a free Let's Encrypt SSL certificate, then configure Nginx to serve it securely.

~~~

I recently set up a VPS on DigitalOcean using the official Node.js droplet, which installs Ubuntu Linux with Node and Nginx as a reverse proxy, which means it’s a middleman between users and your Node.js apps.

If you want to follow along and don’t have an account, sign up for DigitalOcean with my referral link and you get $200 in credit for 60 days. This is an affiliate link: if you sign up through it and then spend $25, I get $25 in DigitalOcean credit.

By default the droplet is configured to use HTTP, but we want our apps to be served using HTTPS, the secure version of HTTP.

So we need to do a little procedure that involves using Certbot to obtain a SSL certificate through Let’s Encrypt, and configuring Nginx to use it.

By the way, I built a free nginx config generator that writes server blocks with the certbot HTTPS setup already included.

These are the steps we’ll follow:

Check the prerequisites

Let’s Encrypt only gives you a certificate after you prove you control the domain. Certbot’s Nginx plugin does this with the HTTP-01 challenge: Let’s Encrypt connects to your domain on port 80 and asks for a temporary file that Certbot places there.

This means the domain must point at your server. In your DNS provider, create an A record with the server’s IPv4 address (if DNS records are new to you, see my DNS introduction). Then check it from your computer:

dig +short my.domain.com

This must print your server’s IP address. If it prints nothing, or a different IP, fix the record or wait for DNS to update. Certbot will fail until it’s right.

Nginx must also serve the site on port 80 already. Open http://my.domain.com in the browser. You should see your site or the Nginx welcome page, not a timeout.

And ports 80 and 443 must be open. We’ll check that in the firewall step.

Install Certbot

These instructions assume you are using Ubuntu or Debian. The official way to install Certbot there is Snap.

The certbot and python3-certbot-nginx apt packages are the old path. If you installed them in the past, remove them first with sudo apt-get remove certbot, so the certbot command runs the Snap version.

Then install Certbot and link its command:

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

The symlink makes the certbot command available from any shell. The Snap already includes the Nginx plugin, so there’s no other package to install.

Set up Nginx

Certbot looks at the server_name directive to find the right place in your Nginx config. If it’s missing or wrong, Certbot can get the certificate but can’t install it.

Edit /etc/nginx/sites-available/default:

sudo nano /etc/nginx/sites-available/default

Find the server_name line and enter your domain name:

server_name my.domain.com;

If you want the certificate to cover both the bare domain and www, list both names, separated by a space:

server_name my.domain.com www.my.domain.com;

Each name needs its own DNS record pointing at the server.

Before reloading, test the configuration:

sudo nginx -t

You should see syntax is ok and test is successful. Now run:

sudo systemctl reload nginx

to reload Nginx with the updated configuration.

Open the firewall

On Ubuntu the firewall is usually ufw. Check its status:

sudo ufw status

If you see Nginx Full in the list, you’re set. That profile opens both port 80 and port 443.

If you only see Nginx HTTP, swap it for the full profile:

sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'

If the status says inactive, ufw is not blocking anything. If you decide to enable it, allow SSH first, or you’ll lock yourself out of the server:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

Notice that ufw is not the only firewall that can block you. If you added a DigitalOcean Cloud Firewall (or a security group on another provider), it needs inbound rules for 80 and 443 too.

Keep port 80 open even after HTTPS works, because renewals use it.

Generate the SSL certificate using Certbot

Now we can invoke Certbot to generate the certificate. You must run this as root:

sudo certbot --nginx -d my.domain.com --redirect

(of course, change my.domain.com to your domain name)

The --redirect flag tells Certbot to send all HTTP traffic to HTTPS, which is what you want in almost every case. Current Certbot versions already redirect by default, so the flag makes it explicit.

For the bare domain plus www, pass one -d per name, and both end up in the same certificate:

sudo certbot --nginx -d my.domain.com -d www.my.domain.com --redirect

The first time, Certbot asks for an email address and asks you to accept the terms of service. The email is optional, so you can press Enter to skip it. Since June 2025, Let’s Encrypt doesn’t store these addresses with your account anymore.

When it finishes, open https://my.domain.com. You should see the padlock in the browser.

What Certbot changed in your Nginx config

Certbot edits your config file in place. Open /etc/nginx/sites-available/default again and you’ll find lines marked # managed by Certbot.

Inside your server block, it added an HTTPS listener and the certificate paths:

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/my.domain.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/my.domain.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

fullchain.pem is your certificate plus the intermediate certificate, and privkey.pem is the private key. The include line pulls in Certbot’s recommended TLS settings.

Certbot also moved port 80 into a new server block that redirects to HTTPS:

server {
    if ($host = my.domain.com) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

    listen 80;
    server_name my.domain.com;
    return 404; # managed by Certbot
}

So your original server block now answers HTTPS, and this small block handles plain HTTP.

You can keep editing the rest of the file as usual, for example your location blocks. Leave the Certbot lines alone, and run sudo nginx -t after every change.

Renewal

Let’s Encrypt certificates are valid for 90 days. The Snap installs a systemd timer that tries to renew them twice a day, and Certbot renews a certificate when it’s close to expiring.

You can see the timer with:

systemctl list-timers | grep certbot

To simulate and test-drive the renewal process, run:

sudo certbot renew --dry-run

This should give you a successful message.

To list your certificates and their expiry dates:

sudo certbot certificates

Let’s Encrypt stopped sending expiration reminder emails in 2025, so no email will warn you when renewal breaks. Run the dry run again whenever you change your server setup.

Common problems

Certbot can’t find a matching server block

Certbot says something like Could not automatically find a matching server block. No server_name matches the domain you passed with -d, but the certificate may still have been issued.

Fix server_name, reload Nginx, then install the existing certificate without requesting a new one:

sudo certbot install --cert-name my.domain.com

The challenge fails with a timeout or connection error

Let’s Encrypt couldn’t reach your server on port 80. Check the firewall section again, including any cloud firewall, and check that dig +short my.domain.com returns this server’s IP.

The challenge fails with a 404 or unauthorized error

Let’s Encrypt reached a server, but not the right one. Usually a DNS record still points to an old server, or an AAAA record has an IPv6 address that isn’t this server. When an AAAA record exists, Let’s Encrypt tries IPv6 first, so fix or delete that record.

You hit a rate limit

Let’s Encrypt limits how often you can ask. For example, you can get only 5 certificates for the exact same set of names every 7 days. After 5 failed validations for the same name in an hour, it blocks new requests for that name until the limit refills.

While you debug, test with --dry-run. It uses the staging environment, which doesn’t count against the production limits:

sudo certbot certonly --nginx --dry-run -d my.domain.com

Run the real command once the dry run passes.

Renewal worked for months, then failed

Something changed after the first setup. Maybe you closed port 80, moved DNS to a new server, or broke the Nginx config. Run sudo certbot renew --dry-run and read the error, which usually points to one of the problems above.

That’s it, now your Node apps should successfully run on HTTPS with no additional changes on your part.

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about network: