The short version: install Certbot, let its Nginx plugin get the certificate and update your config, then check that renewal works.
shell
1sudo snap install --classic certbot2sudo ln -s /snap/bin/certbot /usr/local/bin/certbot3sudo certbot --nginx -d example.com -d www.example.com4sudo certbot renew --dry-runReplace example.com with your domain. The rest of this guide explains each step, shows what Certbot changes in your Nginx config, and gives a manual alternative for people who want to write the config themselves. All commands were tested on Ubuntu 22.04, 24.04 and 26.04 against a test ACME server.
Requirements
- An Ubuntu 22.04, 24.04 or 26.04 server with SSH access and a user that can run
sudo. - A domain name with DNS records pointing to the server's public IP. In this guide, that's
example.comandwww.example.com. If the domain has an AAAA (IPv6) record, Nginx has to answer on that address too, because Let's Encrypt may check over IPv6. Otherwise, remove the record. - Ports 80 and 443 open to the internet. Port 80 has to stay open even after you switch to HTTPS, because Let's Encrypt's HTTP-01 check only works on port 80.
- Basic comfort with the Linux console. The whole setup takes about 15 minutes.
If Nginx isn't installed yet, install it and open the firewall. Nginx Full is a ufw profile that comes with the Ubuntu nginx package and opens both ports:
shell
1sudo apt update2sudo apt install nginx3sudo ufw allow 'Nginx Full'On a cloud server, also check the provider's firewall or security group. That's the most common reason the certificate request fails.
Get a certificate with the Nginx plugin
This is the easiest path. Certbot proves that you control the domain, gets the certificate, adds it to your Nginx config, and sets up the redirect from HTTP to HTTPS.
Step 1: Create an Nginx server block for your domain
Certbot looks for a server block whose server_name matches the domain you ask for. Create one in /etc/nginx/sites-available/example.com:
nginx
1server {2 listen 80;3 listen [::]:80;4 server_name example.com www.example.com;5
6 root /var/www/example.com;7 index index.html;8
9 location / {10 try_files $uri $uri/ =404;11 }12}Create the web root, enable the site, then test and reload Nginx:
shell
1sudo mkdir -p /var/www/example.com2sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/3sudo nginx -t4sudo systemctl reload nginxYou should see:
shell
1nginx: the configuration file /etc/nginx/nginx.conf syntax is ok2nginx: configuration file /etc/nginx/nginx.conf test is successfulAlways run nginx -t before a reload. If the test fails, Nginx keeps running with the old config and tells you which line is wrong.
Step 2: Install Certbot
The Certbot team recommends the snap package, and that's what I use. It updates itself, so you always run the current Certbot. Snap comes preinstalled on standard Ubuntu Server installs:
shell
1sudo snap install --classic certbot2sudo ln -s /snap/bin/certbot /usr/local/bin/certbotIf an older Certbot from apt is on the server, remove it first with sudo apt remove certbot, so the two don't fight over the certbot command.
If you can't use snap, the Certbot from Ubuntu's repositories works too:
shell
1sudo apt install certbot python3-certbot-nginxKeep in mind that the apt version is much older: Certbot 1.21 on Ubuntu 22.04, 2.9 on 24.04 and 4.0 on 26.04. It gets the job done for a regular domain certificate, but it doesn't support ACME Renewal Information (Certbot 4.1+) or IP address certificates (Certbot 5.4+ for webroot). Both are covered below.
Many tutorials still tell you to add the ppa:certbot/certbot repository or to install a letsencrypt package. Skip that: the PPA is deprecated, and Ubuntu 22.04 and newer have no letsencrypt package or command.
Step 3: Request the certificate
Run Certbot with the Nginx plugin and list every name that should be in the certificate:
shell
1sudo certbot --nginx -d example.com -d www.example.comOn the first run, Certbot asks for an email address and for you to accept the Let's Encrypt terms of service. When it's done, you'll see something like this:
shell
1Successfully received certificate.2Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem3Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem4These files will be updated when the certificate renews.5Certbot has set up a scheduled task to automatically renew this certificate in the background.6
7Deploying certificate8Successfully deployed certificate for example.com to /etc/nginx/sites-enabled/example.com9Successfully deployed certificate for www.example.com to /etc/nginx/sites-enabled/example.com10Congratulations! You have successfully enabled HTTPS on https://example.com and https://www.example.comTo run it from a script without questions, add -m you@example.com --agree-tos --no-eff-email -n.
Step 4: Check the redirect and turn on HTTP/2
Open /etc/nginx/sites-available/example.comto see what Certbot changed. Your original block now listens on 443 with the certificate, and Certbot added a second block that redirects HTTP to HTTPS:
nginx
1server {2 server_name example.com www.example.com;3
4 root /var/www/example.com;5 index index.html;6
7 location / {8 try_files $uri $uri/ =404;9 }10
11 listen [::]:443 ssl ipv6only=on; # managed by Certbot12 listen 443 ssl; # managed by Certbot13 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot14 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot15 include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot16 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot17}18
19server {20 if ($host = www.example.com) {21 return 301 https://$host$request_uri;22 } # managed by Certbot23
24 if ($host = example.com) {25 return 301 https://$host$request_uri;26 } # managed by Certbot27
28 listen 80;29 listen [::]:80;30 server_name example.com www.example.com;31 return 404; # managed by Certbot32}The included options-ssl-nginx.conf sets the TLS protocols (TLS 1.2 and 1.3), ciphers, and session cache, so you don't need to write those yourself.
Certbot doesn't enable HTTP/2. How you turn it on depends on your Nginx version, so check it first:
shell
1nginx -vOn Nginx 1.25.1 and newer (Ubuntu 26.04 ships 1.28), add one line inside the 443 server block:
nginx
1http2 on;On older Nginx (1.18 on Ubuntu 22.04, 1.24 on Ubuntu 24.04), there's no http2 directive, and nginx -t fails with unknown directive "http2". Add http2 to the listen lines instead:
nginx
1listen 443 ssl http2; # managed by Certbot2listen [::]:443 ssl http2 ipv6only=on; # managed by CertbotThen test and reload:
shell
1sudo nginx -t2sudo systemctl reload nginxNow check both protocols with curl. HTTP should answer with a 301 to the HTTPS address, and HTTPS should answer over HTTP/2:
shell
1curl -I http://www.example.com/2curl -I https://example.com/shell
1HTTP/1.1 301 Moved Permanently2Location: https://www.example.com/3
4HTTP/2 200Set up automatic renewal
Let's Encrypt certificates are valid for 90 days, so renewal has to be automatic. Good news: Certbot already took care of it during installation. You don't need your own cron job.
Check the renewal timer
The snap installs a systemd timer called snap.certbot.renew.timer, and the apt package installs certbot.timer. Both run twice a day and renew only the certificates that are close to expiry:
shell
1systemctl list-timers | grep certbotThe apt package also adds /etc/cron.d/certbot, but it doesn't run on systems with systemd, so there's no double renewal.
Test renewal with a dry run
shell
1sudo certbot renew --dry-runThe dry run goes through the whole renewal against the Let's Encrypt staging server and doesn't touch your real certificate. You want to see this at the end:
shell
1Congratulations, all simulated renewals succeeded:2 /etc/letsencrypt/live/example.com/fullchain.pem (success)Reload Nginx after renewal
Nginx keeps the old certificate in memory until it's reloaded. If you got the certificate with --nginx, Certbot reloads Nginx itself after every renewal, and there's nothing to add.
If you use certonly (see the next section), add a deploy hook. A deploy hook runs only after a certificate is actually renewed. The easiest way is a script in Certbot's hooks directory, which works for all your certificates:
shell
1sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'2#!/bin/sh3systemctl reload nginx4EOF5sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shOr add --deploy-hook "systemctl reload nginx" when you request the certificate. Certbot saves it in /etc/letsencrypt/renewal/example.com.conf and uses it on every renewal.
The dry run doesn't run deploy hooks by default. To test the hook too, use sudo certbot renew --dry-run --run-deploy-hooks. The snap version has this flag; Certbot 1.21 from Ubuntu 22.04's apt doesn't.
Alternative: certonly with webroot and your own config
If you manage Nginx configs yourself (or with Ansible and similar tools), you may not want Certbot to edit them. In that case, use certonly --webroot: Certbot only gets the certificate, and you write the config.
Step 1: Catch ACME challenges on port 80
Here's a small trick that saves time when a server hosts many sites. One default server on port 80 serves the Let's Encrypt challenge files for every domain from one directory and redirects everything else to HTTPS. Put this in /etc/nginx/sites-available/acme-catchall:
nginx
1server {2 listen 80 default_server;3 listen [::]:80 default_server;4 server_name _;5
6 location ^~ /.well-known/acme-challenge/ {7 root /var/www/letsencrypt;8 default_type text/plain;9 }10
11 location / {12 return 301 https://$host$request_uri;13 }14}Only one server block can be default_server on a port, so disable Ubuntu's default site, enable this one, create the challenge directory, and reload:
shell
1sudo rm /etc/nginx/sites-enabled/default2sudo ln -s /etc/nginx/sites-available/acme-catchall /etc/nginx/sites-enabled/3sudo mkdir -p /var/www/letsencrypt4sudo nginx -t5sudo systemctl reload nginxWith this in place, no site needs its own port 80 block.
Step 2: Request the certificate
shell
1sudo certbot certonly --webroot -w /var/www/letsencrypt -d example.com -d www.example.com --deploy-hook "systemctl reload nginx"--webroot -w /var/www/letsencrypt: Certbot puts the challenge file in this directory, and the catch-all server above serves it.-d example.com -d www.example.com: the names in the certificate. The first one becomes the certificate name and the folder name in/etc/letsencrypt/live/.--deploy-hook "systemctl reload nginx": reload Nginx after each renewal.
Step 3: Configure the HTTPS server block
Point Nginx at the new files. Note that server_name must match the names in the certificate, or browsers will show a name mismatch error. Here's /etc/nginx/sites-available/example.com for Ubuntu 22.04 and 24.04:
nginx
1server {2 listen 443 ssl http2;3 listen [::]:443 ssl http2;4 server_name example.com www.example.com;5
6 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;7 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;8
9 root /var/www/example.com;10 index index.html;11}On Nginx 1.25.1 and newer (Ubuntu 26.04), use listen 443 ssl; and listen [::]:443 ssl; and add http2 on; on its own line.
There's no ssl on; here. That directive was deprecated in Nginx 1.15.0 and removed in 1.25.1: older versions print a warning, Nginx 1.28 on Ubuntu 26.04 fails with unknown directive "ssl". The ssl parameter of listen does the job.
Enable the site, test, and reload:
shell
1sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/2sudo nginx -t3sudo systemctl reload nginxTest your SSL configuration
Start with curl. It shows the certificate details and the result of the check:
shell
1curl -vI https://example.comIn the output, look for these lines:
subjectAltName: host "example.com" matched cert's "example.com": the name matches.issuer:withO=Let's Encrypt: the certificate comes from Let's Encrypt. Their current intermediate certificates are listed on letsencrypt.org/certificates.expire date:no more than 90 days from today.SSL certificate verify ok.: the chain is valid.
To see every certificate Certbot manages, with its domains and days left, run:
shell
1sudo certbot certificatesYou can also open the site in a browser, click the icon to the left of the address, and check that the connection is secure and the certificate was issued by Let's Encrypt.
Let's Encrypt changes to know about
Let's Encrypt has changed a few things in 2025 and 2026 that older guides don't mention:
- No more expiration emails. Let's Encrypt stopped sending expiration reminders on June 4, 2025. If renewal breaks, nobody will email you. Set up your own monitoring, for example with one of the services Let's Encrypt lists.
- No more OCSP. Let's Encrypt turned off its OCSP responders on August 6, 2025, and new certificates don't have an OCSP URL. If your config has
ssl_stapling on;, Nginx now prints"ssl_stapling" ignored, no OCSP responder URL in the certificate. Removessl_staplingandssl_stapling_verify. - Shorter certificates. Let's Encrypt is moving to 45-day certificates. The default goes down to 64 days on February 10, 2027, and to 45 days on February 16, 2028. Don't renew on a fixed schedule like "every 60 days". Let Certbot's timer decide. The snap version also uses ACME Renewal Information, so Let's Encrypt can tell it when to renew.
Can you get a Let's Encrypt certificate for an IP address?
Yes, you can now get one. Let's Encrypt made IP address certificates generally available in January 2026, for IPv4 and IPv6. There are a few conditions:
- They're short-lived: 160 hours, just over six days. You request them with the
shortlivedprofile. - Validation works with HTTP-01 on port 80 (or TLS-ALPN-01), not with DNS.
- You need Certbot 5.4 or newer for the webroot plugin, which means the snap. The Nginx plugin doesn't support IP addresses, so use
certonly.
With the catch-all server from the alternative section above, the request looks like this (use your server's public IP):
shell
1sudo certbot certonly --preferred-profile shortlived --webroot -w /var/www/letsencrypt --ip-address 203.0.113.10 --deploy-hook "systemctl reload nginx"The files land in /etc/letsencrypt/live/203.0.113.10/, and you point ssl_certificate and ssl_certificate_key at them the same way as for a domain. With a six-day lifetime, automatic renewal and the deploy hook aren't optional.
Troubleshooting
- The challenge fails (Certbot reports a timeout or an unauthorized response). Check that the DNS records point to this server, that port 80 is open in ufw and in your cloud firewall, and that nothing else answers on port 80.
unknown directive "http2": your Nginx is older than 1.25.1. Uselisten 443 ssl http2;instead ofhttp2 on;.the "listen ... http2" directive is deprecated: the opposite case, Nginx 1.25.1 or newer. Movehttp2out oflistenintohttp2 on;.unknown directive "ssl"orthe "ssl" directive is deprecated: removessl on;and keepsslin thelistenline.- The browser still shows the old certificate after renewal: Nginx wasn't reloaded. Add the deploy hook from the renewal section.
Wrapping up
Now you know how to install Let's Encrypt on Ubuntu 22.04, 24.04 and 26.04 to secure your site with Nginx. It's free, it renews itself, and once the dry run passes, there's not much left to do. The only thing I'd add is monitoring of certificate expiry, since Let's Encrypt no longer emails you about it.
If you'd rather not look after certificates, servers, and the rest of your infrastructure yourself, our team can do it for you. Take a look at our cloud engineering services.
