How to Secure Nginx with Let’s Encrypt on Debian

Securing Nginx with Let’s Encrypt on Debian provides free, automated SSL/TLS certificates that encrypt traffic between your server and visitors. Whether you’re hosting a WordPress site, running a web application, or serving static content, HTTPS encryption protects sensitive data and improves search engine rankings. As a result, by the end of this guide, you’ll have a fully configured SSL certificate with automatic renewal, HTTP-to-HTTPS redirection, and optimized security settings.

Prerequisites

Before generating SSL certificates, ensure you have the following in place:

  • A Debian server with root or sudo access
  • Nginx installed and running (see how to install Nginx on Debian if needed)
  • A registered domain name with DNS A/AAAA records pointing to your server’s IP address
  • An existing Nginx server block configured for your domain in /etc/nginx/sites-available/
  • Ports 80 (HTTP) and 443 (HTTPS) open in your firewall (see how to configure UFW on Debian for firewall setup)

Let’s Encrypt uses HTTP-01 validation to verify domain ownership. Certbot places a temporary file on your server, then Let’s Encrypt requests it over HTTP (port 80). This requires your domain’s DNS A record to point to your server and port 80 to be accessible from the internet.

Update System Packages

Before installing Certbot, update your package repositories and upgrade existing packages to ensure you install the latest versions with all security patches:

sudo apt update && sudo apt upgrade

Confirm any prompts to proceed with the upgrade. Once complete, your system is ready for Certbot installation.

Install Certbot and Nginx Plugin

Certbot is the official Let’s Encrypt client that handles certificate generation, installation, and renewal. Additionally, the Nginx plugin enables Certbot to automatically configure your web server and manage certificates without manual intervention.

First, install both packages:

sudo apt install certbot python3-certbot-nginx

Next, verify the installation by checking the Certbot version:

certbot --version
certbot 4.0.0

Note that the version number may vary depending on your Debian release. Debian 11 includes Certbot 1.12, Debian 12 includes 2.1, and Debian 13 includes 4.0. All versions support the commands in this guide.

Generate SSL Certificate with Certbot

With Certbot installed, you can generate an SSL certificate for your domain. The following command uses recommended security options that enable HTTPS redirection, HSTS (HTTP Strict Transport Security) headers, and OCSP (Online Certificate Status Protocol) stapling in a single step.

Replace you@example.com with your email address and yourdomain.com with your actual domain name:

sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d yourdomain.com

To include both the apex domain and www subdomain in a single certificate, add multiple -d flags:

sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d yourdomain.com -d www.yourdomain.com

The command options configure the following:

OptionPurpose
--nginxUses the Nginx plugin to automatically configure your server block
--agree-tosAccepts the Let’s Encrypt terms of service
--redirectSets up a permanent 301 redirect from HTTP to HTTPS
--hstsAdds Strict-Transport-Security header to enforce HTTPS
--staple-ocspEnables OCSP stapling for faster SSL verification
--emailEmail address for renewal reminders and security notices
-dDomain name(s) to include in the certificate

Upon successful completion, Certbot displays the certificate location and expiration date:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/yourdomain.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/yourdomain.com/privkey.pem
This certificate expires on 2026-03-02.
These files will be updated when the certificate renews.

Verify the Certificate

List all certificates managed by Certbot to confirm successful installation:

sudo certbot certificates
Found the following certs:
  Certificate Name: yourdomain.com
    Domains: yourdomain.com
    Expiry Date: 2026-03-02 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/yourdomain.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/yourdomain.com/privkey.pem

Alternatively, you can verify the certificate by visiting your domain in a browser. Look for the padlock icon in the address bar indicating a secure HTTPS connection.

Interactive Certificate Generation

If you prefer a guided approach instead, Certbot provides an interactive mode that prompts for each configuration option. This method is particularly helpful when securing multiple domains or when you want to review each setting before applying it.

sudo certbot --nginx

Certbot will prompt you through the following steps:

  1. Email address: Enter your email for renewal reminders and security notices
  2. Terms of service: Input A to agree
  3. EFF newsletter: Input Y or N for Electronic Frontier Foundation updates
  4. Domain selection: Enter the numbers for domains to secure, or leave blank for all detected domains
  5. HTTPS redirect: Select 2 to redirect all HTTP traffic to HTTPS (recommended)

Finally, after completing the prompts, Certbot generates the certificate and updates your Nginx configuration automatically.

Test Certificate Renewal

Let’s Encrypt certificates expire after 90 days, so automatic renewal is essential. Therefore, before relying on the automatic process, verify that renewal works correctly with a dry run:

sudo certbot renew --dry-run

A successful test displays:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/yourdomain.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for yourdomain.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/yourdomain.com/fullchain.pem (success)

Conversely, if the dry run fails, check that port 80 is accessible and your DNS records point to the correct server IP.

Enable Automatic Certificate Renewal

The Certbot package on Debian includes a systemd timer that automatically attempts certificate renewal twice daily. Consequently, this timer is the recommended method for automatic renewal, so no manual cron configuration is needed.

To begin, enable and start the timer:

sudo systemctl enable --now certbot.timer

Then, verify the timer is active:

sudo systemctl status certbot.timer
● certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; preset: enabled)
     Active: active (waiting) since Mon 2025-12-02 10:00:00 UTC; 1min ago
    Trigger: Mon 2025-12-02 12:00:00 UTC; 1h 59min left
   Triggers: ● certbot.service

The timer runs at midnight and noon each day with a randomized delay to distribute load across Let’s Encrypt servers. Furthermore, certificates are only renewed when they are within 30 days of expiration.

To view the timer schedule:

systemctl list-timers certbot.timer
NEXT                        LEFT          LAST                        PASSED       UNIT           ACTIVATES
Mon 2025-12-02 12:00:00 UTC 1h 30min left Mon 2025-12-02 00:00:00 UTC 10h ago      certbot.timer  certbot.service

Optimize SSL Configuration (Optional)

Certbot’s automatic configuration provides a secure baseline, but you can further optimize SSL settings for improved security and performance. Specifically, this section covers advanced hardening based on Mozilla’s SSL Configuration Generator recommendations.

These optimizations are optional. Certbot’s default configuration is secure for most use cases. Only proceed if you understand the implications of each setting.

Edit the Nginx Configuration File

Open your domain’s Nginx server block configuration:

sudo nano /etc/nginx/sites-available/yourdomain.com

Certbot has already added SSL directives. You can enhance these settings within the server block that listens on port 443.

Configure SSL Certificate Paths

Certbot automatically sets these paths, but for reference, Let’s Encrypt certificates are stored at:

ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

Configure SSL Session Parameters

SSL session caching reduces the overhead of SSL handshakes for returning visitors:

ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;

The shared cache stores approximately 40,000 sessions in 10MB of memory. Moreover, disabling session tickets improves forward secrecy.

Generate Diffie-Hellman Parameters (TLS 1.2 Only)

Diffie-Hellman parameters strengthen key exchange for TLS 1.2 connections. However, TLS 1.3 uses ECDHE exclusively and does not require DH parameters, so this step is only necessary if you support TLS 1.2 clients.

Generating DH parameters can take several minutes depending on your server’s CPU. A 2048-bit key is sufficient for most use cases; 4096-bit provides additional security at the cost of longer generation time and slightly slower connections.

Generate a 2048-bit DH parameter file:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Alternatively, for environments requiring stronger security, generate a 4096-bit key instead:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096

Afterward, add the DH parameter path to your Nginx configuration:

ssl_dhparam /etc/ssl/certs/dhparam.pem;

Configure TLS Protocols and Cipher Suites

Modern browsers support TLS 1.2 and 1.3. The following configuration uses Mozilla’s “Intermediate” cipher suite, which balances security with broad compatibility:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;

In particular, setting ssl_prefer_server_ciphers off allows modern clients to negotiate the most efficient cipher, typically resulting in better performance.

Enable HSTS Header

HTTP Strict Transport Security (HSTS) instructs browsers to only connect via HTTPS, preventing protocol downgrade attacks:

add_header Strict-Transport-Security "max-age=63072000" always;

The max-age value of 63072000 seconds equals two years. As a result, once set, browsers will refuse HTTP connections to your domain for that duration. For additional protection, see how to configure security headers in Nginx to add headers like X-Frame-Options and Content-Security-Policy.

Configure OCSP Stapling

OCSP stapling allows your server to provide certificate revocation status directly, improving connection speed and visitor privacy:

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;

The ssl_trusted_certificate directive points to the Let’s Encrypt intermediate certificate chain, which Nginx uses to verify OCSP responses.

Set DNS Resolver for OCSP

Nginx requires a DNS resolver to fetch OCSP responses. Configure a reliable public DNS server:

resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;

This configuration uses Cloudflare’s DNS servers. Of course, you can substitute Google DNS (8.8.8.8 8.8.4.4) or your ISP’s resolvers if preferred.

Test and Apply Configuration

After making changes, validate the Nginx configuration syntax:

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If the test passes, reload Nginx to apply the changes:

sudo systemctl reload nginx

Notably, using reload instead of restart applies configuration changes without dropping active connections.

Troubleshoot Common Issues

If certificate generation or renewal fails, use these diagnostic steps to identify the problem.

Domain Validation Fails

If Certbot reports a validation error, the HTTP-01 challenge cannot reach your server:

Challenge failed for domain yourdomain.com
IMPORTANT NOTES:
 - The following errors were reported by the server:
   Domain: yourdomain.com
   Type:   unauthorized
   Detail: Invalid response from http://yourdomain.com/.well-known/acme-challenge/...

First, check that your DNS A record points to your server:

getent hosts yourdomain.com
203.0.113.10    yourdomain.com

Next, verify port 80 is open and Nginx is running:

sudo ss -tlnp | grep ':80'
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*    users:(("nginx",pid=1234,fd=6))

Similarly, if using UFW, ensure HTTP traffic is allowed:

sudo ufw status | grep -E '80|Nginx'
80/tcp                     ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere

If you see no output, HTTP traffic is blocked. Enable it with sudo ufw allow 80/tcp or sudo ufw allow 'Nginx Full'.

After correcting DNS or firewall issues, retry the Certbot command to generate your certificate.

Nginx Configuration Errors

If nginx -t reports syntax errors after Certbot runs:

nginx: [emerg] unknown directive "ssl_certificate" in /etc/nginx/sites-enabled/yourdomain.com:15

This typically means the SSL module is not loaded. To confirm, check your Nginx installation:

nginx -V 2>&1 | grep -o 'with-http_ssl_module'

If the module is missing, reinstall Nginx with full modules:

sudo apt install --reinstall nginx-full

Verify the SSL module is now available:

nginx -V 2>&1 | grep -o 'with-http_ssl_module'
with-http_ssl_module

With the module present, retry the Certbot command.

Rate Limit Errors

Let’s Encrypt enforces rate limits to prevent abuse. If you see:

Error creating new order :: too many certificates already issued for exact set of domains

In this case, wait one week before requesting another certificate for the same domain, or use the staging environment for testing:

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

Importantly, staging certificates are not trusted by browsers but do not count against rate limits.

Remove Certbot and Certificates

If you need to remove Let’s Encrypt certificates and Certbot from your system, follow these steps.

Revoke and Delete Certificates

Revoking a certificate notifies browsers that it should no longer be trusted. Only revoke if the private key was compromised or you’re permanently decommissioning the domain.

To revoke and then delete a certificate:

sudo certbot revoke --cert-name yourdomain.com
sudo certbot delete --cert-name yourdomain.com

Alternatively, if you only want to remove the certificate without revoking:

sudo certbot delete --cert-name yourdomain.com

Remove Certbot Packages

Disable the renewal timer and uninstall Certbot:

sudo systemctl disable --now certbot.timer
sudo apt remove --purge certbot python3-certbot-nginx
sudo apt autoremove

Clean Up Configuration

The following commands permanently delete all Let’s Encrypt certificates, renewal configurations, and logs. If you have other domains using Certbot certificates on this server, do not run these commands. Instead, remove only the specific certificate with certbot delete --cert-name.

Remove Let’s Encrypt directories and any remaining configuration:

sudo rm -rf /etc/letsencrypt
sudo rm -rf /var/lib/letsencrypt
sudo rm -rf /var/log/letsencrypt

Edit your Nginx server block to remove SSL directives added by Certbot, then test and reload:

sudo nginx -t && sudo systemctl reload nginx

Verify Certbot is fully removed:

apt-cache policy certbot
certbot:
  Installed: (none)
  Candidate: 2.1.0-4
  Version table:
     2.1.0-4 500
        500 http://deb.debian.org/debian bookworm/main amd64 Packages

The Installed: (none) line confirms successful removal.

Conclusion

You now have Nginx secured with a free Let’s Encrypt SSL certificate on Debian. The configuration includes automatic renewal via systemd timer, HTTP-to-HTTPS redirection, and optional advanced SSL hardening with HSTS and OCSP stapling. For additional security, consider setting up Fail2ban on Debian to protect against brute-force attacks, or explore deploying WordPress with Nginx on Debian to put your secure server to use.

Leave a Comment