How to Set Up Free SSL/TLS Certificates with Let’s Encrypt and Certbot on Nginx

Why Bother with SSL?

Let me be blunt: if your site is still serving plain HTTP in 2026, you’re doing it wrong. Not just because browsers flag you as “Not Secure” in big scary red text, but because HTTPS is the baseline expectation for everything on the web — from search rankings to API integrations to your visitors’ trust.

The good news? SSL certificates are completely free thanks to Let’s Encrypt, a nonprofit certificate authority that has issued over three billion certificates. No credit card, no sales call, no “enterprise tier” nonsense.

In this tutorial, we’ll set up Certbot to automatically obtain and renew Let’s Encrypt certificates for your Nginx server. By the end, your site will be serving HTTPS with an A+ rating on SSL Labs — and it’ll stay that way with zero ongoing maintenance.

Prerequisites

  • A Linux server (Ubuntu 22.04+ or Debian 11+) with root or sudo access
  • Nginx installed and running
  • A domain name pointing to your server’s public IP (A record)
  • Ports 80 and 443 open in your firewall
  • Basic comfort with the command line

We’ll use example.com throughout this guide. Replace it with your actual domain everywhere.

Step 1: Install Certbot

Certbot is the official Let’s Encrypt client. It handles certificate requests, domain validation, installation, and automatic renewal — all from the command line.

# Update your package index
sudo apt update

# Install Certbot with the Nginx plugin
sudo apt install certbot python3-certbot-nginx -y

The python3-certbot-nginx plugin is key — it lets Certbot automatically configure Nginx to use your new certificate, without you having to manually edit server blocks.

Step 2: Verify Your Nginx Configuration

Before requesting a certificate, make sure Nginx is properly configured for your domain. Certbot needs to be able to find a server block that matches your domain name.

# Check that your domain resolves to this server
nslookup example.com

# Verify Nginx config is valid
sudo nginx -t

# Check that your server block includes the server_name directive
cat /etc/nginx/sites-available/example.com

Your server block should look something like this:

server {
    listen 80;
    server_name example.com www.example.com;

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

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

Important: The server_name directive must match the domain you want a certificate for. If it’s missing or set to _ (catch-all), Certbot won’t know which block to modify.

Step 3: Obtain Your Certificate

Now for the magic. Run Certbot with the Nginx plugin:

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

Certbot will:

  1. Contact the Let’s Encrypt servers
  2. Verify that you control the domain (via an HTTP-01 challenge on port 80)
  3. Generate a certificate and private key
  4. Automatically update your Nginx configuration to use HTTPS

You’ll be prompted for:

  • Email address — for renewal notices and account recovery. Use a real one; Let’s Encrypt will email you when certificates are about to expire (though auto-renewal should handle it).
  • Terms of Service — agree to them.
  • HTTP→HTTPS redirect — I strongly recommend choosing “2: Redirect” so all HTTP traffic is automatically upgraded to HTTPS.

You should see output like:

Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your certificate will expire on 2026-09-06.

Step 4: Verify HTTPS Is Working

Open your browser and navigate to https://example.com. You should see the padlock icon in the address bar.

For a deeper check, test from the command line:

# Check the certificate details
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -subject

# Verify the full chain
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | grep -E "(subject|issuer|Verify)"

You can also run your domain through SSL Labs’ SSL Test for a comprehensive analysis. Aim for an A+ rating.

Step 5: Set Up Auto-Renewal

Let’s Encrypt certificates are valid for 90 days — intentionally short to minimize the impact of compromised keys. But don’t worry: Certbot handles renewal automatically.

Certbot installs a systemd timer that runs twice daily and renews any certificate that’s within 30 days of expiry. Verify it’s active:

# Check the timer status
sudo systemctl status certbot.timer

# List all timers to confirm it's enabled
sudo systemctl list-timers | grep certbot

You should see output like:

certbot.timer    certbot.service    n/a    n/a    5h left    19h left

Do a dry run to make sure renewal works without actually issuing a new certificate:

sudo certbot renew --dry-run

If you see Congratulations, all simulated renewals succeeded, you're golden. Certbot will silently keep your certificates fresh forever.

Step 6: Harden Your SSL Configuration (Optional but Recommended)

Certbot's default SSL settings are decent, but we can do better. Create a shared SSL configuration snippet:

sudo nano /etc/nginx/snippets/ssl-params.conf

Add the following:

# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Then include this snippet in your Nginx server block:

server {
    listen 443 ssl http2;
    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;
    include snippets/ssl-params.conf;

    # ... rest of your config
}

Test and reload:

sudo nginx -t && sudo systemctl reload nginx

Common Pitfalls and Troubleshooting

"Unauthorized" or "Connection Refused" during validation

Let's Encrypt needs to reach your server on port 80 to verify domain ownership. Check:

  • Your firewall allows inbound HTTP: sudo ufw allow 80/tcp
  • Your domain's DNS A record points to the correct IP: dig example.com +short
  • You're not behind a CDN that blocks the ACME challenge (Cloudflare users: temporarily set DNS records to "DNS only" / grey cloud during setup)

"Too many certificates already issued"

Let's Encrypt has rate limits: 50 certificates per registered domain per week. If you're testing repeatedly, use the staging server:

sudo certbot --nginx --staging -d example.com

This gives you a fake certificate for testing without hitting production limits.

Certificate renewal fails silently

Check the renewal logs:

sudo journalctl -u certbot --since "7 days ago"

# Or check the renewal config directly
sudo cat /etc/letsencrypt/renewal/example.com.conf

Common cause: your Nginx config was manually edited after Certbot modified it, breaking the SSL directives. Let Certbot manage the SSL lines, or use the certonly mode and configure Nginx yourself.

Mixed content warnings

Your site loads over HTTPS, but some resources (images, scripts, stylesheets) are still fetched over HTTP. The browser will warn about this. Fix it by:

  • Updating all internal links to use https://
  • Using protocol-relative URLs (//example.com/image.png) or relative paths
  • Adding a Content-Security-Policy header: add_header Content-Security-Policy "upgrade-insecure-requests" always;

Going Further

Once you've got the basics down, here are some next steps:

  • Wildcard certificates: sudo certbot certonly --manual --preferred-challenges dns -d '*.example.com' — requires adding a TXT record to your DNS
  • Multiple domains on one cert: Just add more -d flags: sudo certbot --nginx -d example.com -d api.example.com -d blog.example.com
  • Dockerized Certbot: Run Certbot in a container with a shared volume for /etc/letsencrypt — great for containerized Nginx setups
  • Monitoring: Set up a cron job or health check that alerts you if a certificate is within 14 days of expiry, as a backup to auto-renewal

Wrapping Up

That's it. In about ten minutes, you've gone from an insecure HTTP site to a fully HTTPS-enabled, auto-renewing, A+ rated secure website — for free. No recurring costs, no manual certificate management, no excuses.

The web is slowly but surely becoming encrypted by default, and tools like Let's Encrypt and Certbot are the reason why. Every site you secure is a small win for privacy and security on the internet.

Now go enable HTTPS on everything you own. Your future self (and your visitors) will thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *