You set up Nginx and your site is live, but the browser shows “Not Secure” in the address bar. Visitors are warned before they even see your content. Forms, logins, and any data the user submits travel unencrypted across the internet. You need HTTPS.
The good news: free, trusted SSL certificates from Let’s Encrypt are available to everyone. Certbot is the official tool that requests those certificates, installs them into Nginx, and keeps them renewed automatically.
In this tutorial, you will install Certbot on Ubuntu, obtain a Let’s Encrypt certificate for your domain, configure Nginx to serve your site over HTTPS, and set up automatic renewal so the certificate never expires. You will also learn how to redirect all HTTP traffic to HTTPS.
What Is TLS/SSL, and Why Does It Matter?
When someone visits your site over plain HTTP, every byte of data travels as plain readable text across every router between the user’s browser and your server. Anyone on the same network can intercept it.
TLS (Transport Layer Security, often still called SSL by habit) encrypts the connection so that data is unreadable to anyone who intercepts it in transit. HTTPS is simply HTTP running over a TLS connection.
Beyond security, HTTPS has practical consequences:
- Search ranking: Google has used HTTPS as a ranking signal since 2014.
- Browser warnings: Modern browsers show a “Not Secure” warning on HTTP sites, which kills user trust.
- HTTP/2: Most browsers only support HTTP/2 over HTTPS, which means better performance is gated behind HTTPS.
What Is Let’s Encrypt?
Let’s Encrypt is a nonprofit Certificate Authority (CA) that issues free, trusted TLS certificates. Before Let’s Encrypt existed, you had to pay a CA for a certificate. Now you can get one in seconds, and it is trusted by every major browser.
Let’s Encrypt certificates are valid for 90 days and must be renewed. This sounds short, but Certbot handles renewal automatically.
What Is Certbot?
Certbot is the official Let’s Encrypt client. It speaks the ACME protocol, a standard that lets software automatically prove domain ownership and obtain a certificate without any human interaction. Certbot has a plugin for Nginx that:
- Requests the certificate from Let’s Encrypt
- Edits your Nginx config to point to the certificate files
- Adds the HTTPS redirect automatically (if you ask it to)
Prerequisites
- Ubuntu 22.04 or 24.04
- Nginx installed and running. If you need to install it:
sudo apt install nginx - A domain name (e.g.,
example.com) with its A record pointing to your server’s public IP. Let’s Encrypt will not issue a certificate if the domain does not resolve to the server running Certbot. - Ports 80 and 443 open in your firewall. If you use UFW:
sudo ufw allow 'Nginx Full' - A non-root user with
sudoaccess
Replace example.com and www.example.com with your actual domain throughout this tutorial.
Step 1: Install Certbot
There are two ways to install Certbot on Ubuntu: via snap or via apt. Both work, but they differ in how up to date the package is.
- Snap, always installs the latest upstream Certbot release. The Certbot project officially recommends this method because the ACME protocol evolves and outdated clients can fail.
- apt, installs the version packaged for your Ubuntu release. It is simpler and does not require snapd, but it may lag behind the latest release.
Choose one method and follow only that section.
Option A: Install via snap (recommended)
First, remove any old certbot package installed via apt to avoid conflicts:
sudo apt remove certbot
Install snapd (it is usually pre-installed on Ubuntu, but this ensures it is up to date):
sudo snap install core
sudo snap refresh core
Install Certbot:
sudo snap install --classic certbot
Make certbot available as a system command:
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Option B: Install via apt
sudo apt update
sudo apt install certbot python3-certbot-nginx
python3-certbot-nginx is the Nginx plugin that lets Certbot edit your Nginx configuration automatically. If you skip it, Certbot cannot modify your Nginx config and you will have to configure SSL manually.
Verify the installation
Whichever method you used, confirm Certbot is installed:
certbot --version
You should see output like certbot 2.x.x.
Step 2: Set Up a Basic Nginx Server Block
Certbot needs to verify that you control the domain. It does this by placing a temporary file in your web root or by temporarily modifying your Nginx config. For this to work, Nginx must have a server_name directive matching your domain.
Check if you already have a server block for your domain:
sudo cat /etc/nginx/sites-available/example.com
If the file does not exist, create one:
sudo nano /etc/nginx/sites-available/example.com
Add this minimal configuration:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Create the web root and a simple index file:
sudo mkdir -p /var/www/example.com
echo "<h1>It works</h1>" | sudo tee /var/www/example.com/index.html
Enable the site by symlinking it to sites-enabled:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
Test the configuration and reload Nginx:
sudo nginx -t
sudo systemctl reload nginx
Open http://example.com in your browser to confirm Nginx is serving the page before proceeding.
Step 3: Obtain the SSL Certificate
Now run Certbot with the --nginx plugin. Pass both the bare domain and the www subdomain so the certificate covers both:
sudo certbot --nginx -d example.com -d www.example.com
Certbot will ask a few questions:
- Email address, used by Let’s Encrypt to notify you about expiring certificates if auto-renewal fails. Enter a real address.
- Terms of Service, type
Yto agree. - Newsletter, optional, answer as you like.
- HTTP to HTTPS redirect, choose option
2to redirect all HTTP traffic to HTTPS automatically. This is almost always what you want.
If everything succeeds, you will see:
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem
Certbot also edits your Nginx config to add the SSL directives and the redirect block. Open the file to see what changed:
sudo cat /etc/nginx/sites-available/example.com
You will see Certbot added listen 443 ssl, ssl_certificate, ssl_certificate_key, and a redirect server block at port 80. It also added a server block that handles the ACME challenge for future renewals.
Step 4: Verify HTTPS Is Working
Open your site in the browser:
https://example.com
The padlock icon should appear. Click it to inspect the certificate. You should see it is issued by Let’s Encrypt and is valid for your domain.
You can also test from the command line using curl:
curl -I https://example.com
Look for HTTP/2 200 or HTTP/1.1 200 OK in the output. If you see 301 Moved Permanently when you hit the HTTP address, the redirect is working correctly:
curl -I http://example.com
Expected output:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Step 5: Test and Enable Automatic Renewal
Let’s Encrypt certificates expire after 90 days. Certbot installs a systemd timer (or cron job on older systems) that renews certificates automatically when they are within 30 days of expiry.
Check that the timer exists. The timer name depends on how you installed Certbot:
If you installed via snap:
sudo systemctl status snap.certbot.renew.timer
If you installed via apt:
sudo systemctl status certbot.timer
Test the renewal process without actually renewing anything:
sudo certbot renew --dry-run
If the output ends with Congratulations, all simulated renewals succeeded, your auto-renewal is configured correctly. You do not need to do anything else. Certbot handles renewals silently in the background and reloads Nginx after each renewal so your server picks up the new certificate without downtime.
Step 6: Check Certificate Details
To see the certificates Certbot is managing on this machine:
sudo certbot certificates
Output example:
Found the following certs:
Certificate Name: example.com
Domains: example.com www.example.com
Expiry Date: 2026-08-12 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
The certificate files live in /etc/letsencrypt/live/example.com/. The important files are:
fullchain.pem: the certificate plus the intermediate chain. This is what Nginx uses forssl_certificate.privkey.pem: the private key. Keep this file private and never expose it.
Common Mistakes and Troubleshooting
Error: Domain control validation failed
This means Certbot could not reach your server on port 80 to verify domain ownership. Check two things:
- Does the domain’s A record point to this server’s IP?
dig +short example.com - Is port 80 open?
sudo ufw status. If Nginx is not listed, runsudo ufw allow 'Nginx Full'.
Error: too many certificates already issued for exact set of domains
Let’s Encrypt rate-limits certificate issuance. You can request a maximum of 5 certificates for the same set of domains per week. If you hit this during testing, wait a week or use the Let’s Encrypt staging environment to test:
sudo certbot --nginx --staging -d example.com -d www.example.com
Staging certificates are not trusted by browsers, but they let you verify that your setup works without consuming rate-limit quota.
Error: nginx: configuration file test failed
If sudo nginx -t fails after Certbot modifies your config, check whether your original server_name had a trailing semicolon and whether the Certbot-added SSL lines are syntactically correct. Run sudo nginx -t to see the specific line causing the issue.
Certificate renewed but Nginx still serves the old one
This should not happen with the Nginx plugin, but if it does, manually reload Nginx:
sudo systemctl reload nginx
My domain has www but Certbot only secured the bare domain
Always pass both forms to Certbot with -d:
sudo certbot --nginx -d example.com -d www.example.com
Best Practices
Always redirect HTTP to HTTPS. If you did not choose the redirect option during certbot setup, you can add it manually. Add this block above your HTTPS server block:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Add HTTP Strict Transport Security (HSTS). HSTS tells browsers to always use HTTPS for your domain, even if the user types plain HTTP. Add this to your HTTPS server block:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Start with a short max-age (e.g., 3600) while testing. Once you are confident HTTPS is stable, increase it to 31536000 (one year).
Do not touch the files in /etc/letsencrypt/live/ directly. Always use certbot commands. Certbot manages symlinks in that directory and manual edits can break renewal.
Test with SSL Labs. After setup, visit https://www.ssllabs.com/ssltest/ and enter your domain. Certbot’s default configuration typically scores an A or A+ out of the box thanks to Let’s Encrypt’s strong cipher suite recommendations.
Monitor expiry even with auto-renewal. Auto-renewal works reliably, but it can fail if your server is offline during the renewal window or if DNS changes. Set up a simple monitoring alert for certificate expiry (many uptime monitoring services include certificate checks).
If you are running multiple virtual hosts, run certbot once per domain:
sudo certbot --nginx -d api.example.com
sudo certbot --nginx -d admin.example.com
Or issue a single certificate covering multiple domains:
sudo certbot --nginx -d example.com -d www.example.com -d api.example.com
Conclusion
You now have a fully working HTTPS setup on Nginx backed by a free Let’s Encrypt certificate that renews itself automatically. Here is what you did:
- Installed Certbot (via snap or apt)
- Set up an Nginx server block with the correct
server_name - Obtained a certificate covering both the bare domain and
www - Verified the certificate and confirmed HTTPS is working
- Tested that automatic renewal is configured
From here, if you want to take things further, you can look into:
- Configuring Nginx as a Layer 4 load balancer with SSL for terminating TLS before traffic hits your backend servers.
- Setting up Nginx as a Layer 7 load balancer behind HTTPS for application-level routing.
- Exploring high availability with Nginx and Keepalived to prevent your server from being a single point of failure.