# Nginx Let's Encrypt SSL with Certbot: install, redirect, renew

> Install Certbot from snap, get a free Let's Encrypt certificate for Nginx, redirect HTTP to HTTPS and verify automatic renewal. Ubuntu and Debian.

Source: https://ssl.sh/guides/nginx-lets-encrypt-certbot/
Last updated: 2026-11-10

Install Certbot from snap and run `certbot --nginx`. Certbot obtains a free Let's Encrypt certificate for `example.com` and `www.example.com`, configures the Nginx server block and renews the certificate automatically, and Nginx redirects plain HTTP to HTTPS.

The steps apply to Ubuntu 22.04/24.04 and Debian 12/13 with any supported Nginx release and Certbot 4.1 or later (Certbot 5.x is current as of November 2026).

## Prerequisites

You need a server that Let's Encrypt can reach over HTTP on port 80.

- A Linux server with systemd and sudo access. Snap is preinstalled on Ubuntu; on Debian install it with `sudo apt install snapd`.
- Nginx installed and running, with a server block for your domain.
- DNS `A` (and `AAAA`, if you use IPv6) records for `example.com` and `www.example.com` pointing at the server, for example `203.0.113.10`.
- Inbound TCP ports 80 and 443 open in the host firewall and in any cloud security group. Let's Encrypt validates from several network locations, so do not restrict port 80 to specific source IPs.

Check DNS and the firewall before you start:

```bash
dig +short example.com A
dig +short www.example.com A
sudo ufw allow 'Nginx Full'   # only if you use ufw
```

## Step 1: Make sure Nginx has a server block for the domain

Certbot's Nginx plugin finds the server block by its `server_name`, so the names must match the domains you request. A minimal HTTP-only block looks like this:

```nginx
# /etc/nginx/sites-available/example.com
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

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

Enable it and test the configuration:

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

## Step 2: Install Certbot from snap

The snap package is Certbot's recommended installation method; it bundles the Nginx plugin and a renewal timer. Remove any distribution package first so two copies do not fight over the same files.

```bash
sudo apt-get remove certbot          # ignore "not installed" errors
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
certbot --version
```

If you cannot use snap, Certbot also documents a pip install in a virtual environment (supported on a best-effort basis):

```bash
sudo apt update
sudo apt install python3 python3-dev python3-venv libaugeas-dev gcc
sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-nginx
sudo ln -s /opt/certbot/bin/certbot /usr/local/bin/certbot
```

## Step 3: Get the certificate and let Certbot configure Nginx

One command obtains the certificate with the HTTP-01 challenge, adds the TLS settings to your server block and reloads Nginx.

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

Certbot asks you to accept the Let's Encrypt subscriber agreement and offers to register an email address. The email is optional: Let's Encrypt stopped sending expiry reminder emails on June 4, 2025. For scripts, run it non-interactively:

```bash
sudo certbot --nginx --non-interactive --agree-tos \
  -d example.com -d www.example.com
```

If you prefer to edit Nginx yourself, use `sudo certbot certonly --nginx -d example.com -d www.example.com` to obtain the certificate only. The files land in `/etc/letsencrypt/live/example.com/`.

## Step 4: Review the HTTPS server block

After Certbot finishes, the server block should reference `fullchain.pem` and `privkey.pem` and redirect HTTP to HTTPS. If you write the configuration by hand, this is a sound baseline:

```nginx
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;    # Nginx 1.25.1+; on older versions write "listen 443 ssl http2;" instead
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;

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

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
```

Leave out `ssl_stapling` and `ssl_stapling_verify`: Let's Encrypt certificates no longer contain an OCSP URL. To generate cipher settings for your client base, use the [TLS config generator](https://ssl.sh/tools/tls-config-generator/). Apply the changes:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

## Verify

The live site should present the Let's Encrypt certificate for `example.com` and `www.example.com`, and `curl -I http://example.com` should return `301` with a `Location: https://...` header. Check it from outside the server with the [SSL checker](https://ssl.sh/tools/ssl-checker/), which shows the chain, protocols and expiry date, or from any terminal:

```bash
curl ssl.sh/example.com
```

On the server, confirm what Certbot manages and what Nginx serves:

```bash
sudo certbot certificates
curl -sI https://example.com | head -n 1
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates
```

## Automatic renewal

Renewal is already scheduled: the snap installs the systemd timer `snap.certbot.renew.timer`, which runs `certbot renew` twice a day. Certbot 4.1 and later check ACME Renewal Information (ARI) and renew when Let's Encrypt suggests; otherwise they renew when one third of the lifetime remains. Confirm the timer and run a dry run:

```bash
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
```

Certificates obtained with `--nginx` reload Nginx automatically after renewal. If you used `certonly` with `--webroot` or `--standalone`, add a deploy hook so Nginx picks up the new files:

```bash
sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload nginx"
```

The pip install has no timer; add the cron line from the Certbot instructions. See [Certbot renewal hooks](https://ssl.sh/guides/certbot-renewal-hooks/) for pre, post and deploy hooks. Nothing else needs to change when Let's Encrypt shortens its default lifetime to 64 days on February 10, 2027 and 45 days on February 16, 2028, as long as the timer keeps running. Because Let's Encrypt no longer emails you before expiry, add the domain to [ssl.sh monitoring](https://ssl.sh/monitor/) (in development) as a safety net.

## Troubleshooting

Most failures come from DNS, the firewall or a mismatched server block.

- **Timeout during connect** or **Connection refused**: port 80 is blocked or DNS points elsewhere. See [certbot timeout during connect](https://ssl.sh/errors/certbot-timeout-during-connect/).
- **Could not automatically find a matching server block**: add the exact names to `server_name` and run Certbot again.
- **too many certificates already issued**: you hit a rate limit while testing. Use `--dry-run` or `--test-cert` (staging), and read [Let's Encrypt rate limits](https://ssl.sh/errors/lets-encrypt-rate-limit/).
- **CAA record prevents issuance**: your DNS has a CAA record that does not list `letsencrypt.org`. See [CAA record prevents issuance](https://ssl.sh/errors/caa-record-prevents-issuance/).
- **key values mismatch** when reloading Nginx: the certificate and key files do not belong together. See [nginx key values mismatch](https://ssl.sh/errors/nginx-key-values-mismatch/).
- **NET::ERR_CERT_COMMON_NAME_INVALID** for `www`: the certificate does not cover that name. Run Certbot again with every `-d` name and answer "Expand". See [NET::ERR_CERT_COMMON_NAME_INVALID](https://ssl.sh/errors/net-err-cert-common-name-invalid/).
- Works in the browser but **curl or apps fail**: Nginx is serving `cert.pem`. See [missing intermediate certificate](https://ssl.sh/errors/missing-intermediate-certificate/).
