Every site served over plain HTTP leaks login credentials, session cookies, and form data to anyone listening on the wire. The fastest way to secure Nginx with Let’s Encrypt on Debian is Certbot, which generates a free SSL/TLS certificate, rewrites your server block for HTTPS, and sets up automatic renewal so nothing expires silently. You get a working HTTPS redirect, HSTS headers, and OCSP stapling in a single command, with optional cipher hardening on top.
Prerequisites for Certbot and Nginx on Debian
Certbot needs a few things already working before it can issue a certificate. These prerequisites apply to Debian 13 (Trixie), Debian 12 (Bookworm), and Debian 11 (Bullseye):
- A Debian server with root or sudo access
- Nginx installed and running (see how to install Nginx on Debian, or set up a full LEMP stack on Debian if you also need PHP and MariaDB)
- 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 Debian System Packages
Update your package index and apply any pending upgrades before adding new packages:
sudo apt update && sudo apt upgrade
If your account does not have sudo privileges, see how to add a user to sudoers on Debian before continuing.
Install Certbot and the Nginx Plugin on Debian
Certbot is the official Let’s Encrypt client that handles certificate generation, installation, and renewal. The Nginx plugin lets Certbot automatically configure your web server and manage certificates without manual edits.
Install both packages from the default Debian repositories. On Debian 12, this pulls certbot 2.1 and the python3-certbot-nginx plugin; Debian 13 provides Certbot 4.0 with the same package names:
sudo apt install certbot python3-certbot-nginx
Verify the installation:
certbot --version
certbot 4.0.0
The version number varies by Debian release. Debian 12 (Bookworm) includes Certbot 2.1, Debian 13 (Trixie) ships Certbot 4.0, and Debian 11 (Bullseye) includes 1.12. All versions support the commands and instructions in this guide.
Generate an SSL Certificate with Certbot on Debian
One command handles certificate generation, HTTPS redirection, HSTS (HTTP Strict Transport Security) headers, and OCSP (Online Certificate Status Protocol) stapling all at once.
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. For www-to-non-www or non-www-to-www redirection rules, see how to redirect www in Nginx.
sudo certbot --nginx --agree-tos --redirect --hsts --staple-ocsp --email you@example.com -d yourdomain.com -d www.yourdomain.com
Here is what each flag does:
| Option | Purpose |
|---|---|
--nginx | Uses the Nginx plugin to automatically configure your server block |
--agree-tos | Accepts the Let’s Encrypt terms of service |
--redirect | Sets up a permanent 301 redirect from HTTP to HTTPS |
--hsts | Adds Strict-Transport-Security header to enforce HTTPS |
--staple-ocsp | Enables OCSP stapling for faster SSL verification |
--email | Email address for renewal reminders and security notices |
-d | Domain name(s) to include in the certificate |
Certbot confirms success with the certificate path 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 Let’s Encrypt Certificate
Confirm the certificate is in place:
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
You can also visit your domain in a browser and check for the padlock icon in the address bar.
Interactive Certbot Certificate Generation on Debian
Certbot also provides an interactive mode that prompts for each configuration option. This approach works well when securing multiple domains or reviewing each setting before applying it.
sudo certbot --nginx
The interactive prompts walk through these decisions:
- Email address: Enter your email for renewal reminders and security notices
- Terms of service: Input
Ato agree - EFF newsletter: Input
YorNfor Electronic Frontier Foundation updates - Domain selection: Enter the numbers for domains to secure, or leave blank for all detected domains
- HTTPS redirect: Select
2to redirect all HTTP traffic to HTTPS (recommended)
After completing the prompts, Certbot generates the certificate and updates your Nginx configuration automatically.
Test Certbot Certificate Renewal on Debian
Let’s Encrypt certificates expire after 90 days, so automatic renewal is essential. 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)
If the dry run fails, check that port 80 is accessible and your DNS records point to the correct server IP.
Automatic Certbot Renewal on Debian
The Certbot package on Debian includes a systemd timer that automatically attempts certificate renewal twice daily. Debian enables this timer during installation, so no manual cron configuration is needed.
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
If you previously disabled the timer, re-enable it with
sudo systemctl enable --now certbot.timer.
The timer runs at midnight and noon each day with a randomized delay to distribute load across Let’s Encrypt servers. Certificates are only renewed when they are within 30 days of expiration. To keep the Certbot package itself patched automatically, configure unattended upgrades on Debian.
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 Nginx SSL Configuration on Debian (Optional)
Certbot’s automatic configuration is secure enough for most sites, but Mozilla’s SSL Configuration Generator recommends tighter session handling, stronger key exchange, and explicit HSTS/OCSP directives for production servers. The tweaks below follow Mozilla’s “Intermediate” profile. You may also want to enable gzip compression in Nginx and configure Upgrade-Insecure-Requests to handle mixed content after SSL is in place.
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 SSL Configuration File
Open your domain’s Nginx server block configuration:
sudo nano /etc/nginx/sites-available/yourdomain.com
Certbot has already added SSL directives and an include line pointing to /etc/letsencrypt/options-ssl-nginx.conf, which contains its default cipher and protocol settings. The tweaks below override some of those defaults for tighter security 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. 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. 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
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;
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. 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 (ssl_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. You can substitute Google DNS (8.8.8.8 8.8.4.4) or your ISP’s resolvers if preferred.
Test and Apply the Nginx 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
Using reload instead of restart applies configuration changes without dropping active connections.
Troubleshoot Certbot and Nginx Issues on Debian
Certificate generation and renewal can fail for a handful of reasons. Each scenario below shows the error, the cause, and how to fix it.
Certbot 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))
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'.
Once DNS and firewall are correct, rerun the Certbot command.
Nginx SSL Module Missing
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 SSL module loaded, rerun Certbot.
OCSP Stapling Fails with “No Resolver Defined”
If Nginx logs show this warning after enabling ssl_stapling:
nginx: [warn] "ssl_stapling" ignored, no resolver defined to resolve ocsp.int-x3.letsencrypt.org
The resolver directive is missing from your server block. Add a DNS resolver inside the server { } block that handles port 443:
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;
Reload Nginx and verify the warning is gone:
sudo nginx -t && sudo systemctl reload nginx
Let’s Encrypt 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
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
Staging certificates are not trusted by browsers but do not count against rate limits.
Remove Certbot and Let’s Encrypt Certificates from Debian
Removing Certbot cleanly means revoking or deleting certificates first, then uninstalling the packages and cleaning up leftover directories.
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 Let’s Encrypt Configuration Files
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
Update the package cache and verify Certbot is fully removed:
sudo apt update
apt-cache policy certbot
certbot:
Installed: (none)
Candidate: x.x.x-x
Version table:
x.x.x-x 500
500 http://deb.debian.org/debian <codename>/main amd64 Packages
The Installed: (none) line confirms successful removal.
Frequently Asked Questions
Yes. The certbot package on Debian installs a systemd timer that runs twice daily and renews any certificate within 30 days of expiration. No manual cron job or additional configuration is needed.
python3-certbot-nginx is the Certbot plugin that integrates with Nginx. It automatically modifies your Nginx server block to add SSL directives, configure redirects, and enable HTTPS without manual file editing.
Yes, but wildcard certificates require DNS-01 validation instead of HTTP-01. You need a DNS plugin for your provider (such as certbot-dns-cloudflare) and must create a TXT record to prove domain ownership. The --nginx plugin alone cannot issue wildcard certificates.
The process is identical across Debian 13, 12, and 11. Install certbot and python3-certbot-nginx from the default repositories, then run certbot --nginx with your domain. Debian 13 ships Certbot 4.0, which supports the same flags and workflow as earlier versions.
No. Certbot is the official Let’s Encrypt client, so installing the certbot and python3-certbot-nginx packages from the Debian 12 repositories is all you need. There is no separate “Let’s Encrypt” package to install.
Conclusion
Nginx on Debian is now serving all traffic over a valid Let’s Encrypt certificate that renews itself before expiration. HTTPS redirect, HSTS, and OCSP stapling are in place, so connections are both encrypted and fast. To harden the server further, Fail2ban on Debian blocks brute-force attempts and Nginx rate limiting throttles abusive traffic. If you are ready to put the server to use, WordPress with Nginx on Debian is a natural next step.
Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>