Let’s Encrypt certificates remove browser “Not secure” warnings and let Apache serve your Debian site over HTTPS. The standard Debian package path uses certbot plus the Apache plugin package python3-certbot-apache, then Certbot edits the Apache virtual host, installs the certificate, and configures renewal. Start with the direct Apache flow, then add safer hardening steps such as HSTS, HTTP/2, security headers, modern TLS settings, and renewal checks.
Install Certbot for Apache
Install Certbot for Apache on Debian 13, 12, and 11 after you have Apache installed on Debian, at least one HTTP virtual host configured, and your domain’s DNS pointing to your server. Certbot needs port 80 reachable for HTTP validation before it can create the HTTPS configuration.
Update Debian Packages
Refresh the package index and apply available Debian updates before installing Certbot so Apache, Python dependencies, and certificate tooling start from the current package state.
sudo apt update
sudo apt upgrade
If your account cannot use
sudo, configure sudo access first with the Debian sudoers guide: add a user to sudoers on Debian.
Install Certbot and Apache Plugin
Install Certbot with the Debian Apache plugin package. The Debian package index lists the plugin as python3-certbot-apache; without it, Certbot can still manage certificates, but it cannot use the --apache installer. The package is architecture-independent and depends on Apache, Certbot, and the Python plugin libraries, so it is the correct package when Certbot should edit Apache virtual hosts.
sudo apt install certbot python3-certbot-apache
Current Debian package sources provide Certbot 4.x on Debian 13, Certbot 2.x on Debian 12, and Certbot 1.x on Debian 11. Debian 11 defaults new certificates to RSA keys, while Debian 12 and 13 default to ECDSA; these Certbot examples use
--key-type ecdsaso the certificate key type is consistent across supported releases.
If a firewall is active, keep port 80 reachable for HTTP-01 validation and port 443 reachable after issuance. On a remote server, allow the current SSH access path before enabling or tightening firewall rules; the Debian SSH setup guide covers the service if it is not already configured. Use the Debian UFW guide for a complete firewall workflow instead of treating certificate issuance as a full firewall policy.
Generate a Let’s Encrypt Certificate for Apache
Generate the Let’s Encrypt certificate from a working HTTP virtual host, then add stricter headers and TLS options after the certificate works.
Generate a Certificate for One Domain
Use the Apache installer when one hostname should receive a certificate and redirect from HTTP to HTTPS. Replace admin@example.com with your email address and example.com with your domain name.
sudo certbot --apache --agree-tos --non-interactive --no-eff-email --redirect --key-type ecdsa --email admin@example.com -d example.com
The main flags do the following:
--apache: Uses the Apache installer plugin to edit the matching virtual host.--agree-tos: Accepts the Let’s Encrypt subscriber agreement for this account.--non-interactive: Keeps the command from stopping for account or installer prompts.--no-eff-email: Declines optional email sharing with the Electronic Frontier Foundation.--redirect: Adds a permanent HTTP to HTTPS redirect in the Apache configuration.--key-type ecdsa: Requests an ECDSA certificate key so Debian 11, 12, and 13 use the same key type.--email: Sets the Let’s Encrypt account email for renewal and security notices.
After a successful run, Certbot generates the certificate, updates the Apache virtual host, and enables the HTTP to HTTPS redirect. Test the site over HTTPS before adding HSTS or preload-related settings.
To confirm the certificate was installed correctly, run:
sudo certbot certificates
The following nonliteral example shows the certificate fields to check; your expiration date and remaining days will differ:
Found the following certs:
Certificate Name: example.com
Domains: example.com
Expiry Date: YYYY-MM-DD HH:MM:SS+00:00 (VALID: XX days)
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
Key Type: ECDSA
The expiration date and days remaining in this output are the source of truth for your certificate’s actual validity period. Renewal automation should be working before you depend on the certificate in production.
ECDSA keys provide equivalent security to RSA with smaller key sizes and faster cryptographic operations. If you need RSA for a legacy client base, replace --key-type ecdsa with --key-type rsa and expect Key Type: RSA in the certificate output.
Securing Multiple Domains with One Certificate
To secure multiple domains or subdomains with one certificate, add another -d flag for each name. This approach is useful for covering both the root domain and the www subdomain, or multiple related hostnames:
sudo certbot --apache --agree-tos --non-interactive --no-eff-email --redirect --key-type ecdsa --email admin@example.com -d example.com -d www.example.com -d blog.example.com
All domains will be included as Subject Alternative Names (SANs) on a single certificate. Each domain must point to your server and be accessible for validation.
Wildcard Certificates with DNS Validation
Wildcard certificates secure all subdomains under a single domain (for example, *.example.com covers blog.example.com, shop.example.com, and any other subdomain). Since wildcard certificates cannot use HTTP validation, you must verify domain ownership through DNS records.
Request a wildcard certificate with a manual DNS challenge. This method remains interactive because Certbot must show the TXT record value before validation continues:
sudo certbot certonly --manual --preferred-challenges=dns --key-type ecdsa --email admin@example.com --agree-tos --no-eff-email -d example.com -d '*.example.com'
Certbot will then prompt you to create a TXT record in your DNS configuration. You will see output similar to:
Please deploy a DNS TXT record under the name: _acme-challenge.example.com with the following value: aBcDeFgHiJkLmNoPqRsTuVwXyZ123456789 Before continuing, verify the TXT record has been deployed.
Add this TXT record through your DNS provider’s control panel, wait for DNS propagation (typically 1-5 minutes), then press Enter to continue. After validation succeeds, configure Apache to use the certificate by updating your virtual host configuration with the certificate paths shown in the Certbot output.
Manual wildcard certificates do not auto-renew without DNS API integration. Packaged DNS plugins vary by Debian release, so verify the provider plugin before building an automated wildcard workflow.
List DNS plugin packages available from your enabled Debian repositories:
apt-cache search --names-only '^python3-certbot-dns-'
Interactive Configuration Method
Certbot can also prompt you for domains, email, and redirect choices interactively. Use this path when you prefer menus over a one-line command:
Start the interactive Apache installer:
sudo certbot --apache
Certbot starts an interactive session. The exact wording varies by Certbot version, but these are the prompts you may encounter:
- Enter email address (used for urgent renewal and security notices): Provide your email address. Let’s Encrypt will use this to communicate about your certificates.
- Agree to the Let’s Encrypt terms of service: You will be asked to agree to the terms of service. Input A to agree.
- Share your email with the Electronic Frontier Foundation for updates on their work: If you want to support the EFF, input Y for yes. Otherwise, input N for no.
- Which names would you like to activate HTTPS for: Certbot will display the domain names it can issue certificates for. Enter the numbers corresponding to your domains, or press Enter to select all.
- Select the appropriate action: If Certbot detects an existing certificate, it may offer to reinstall it or renew and replace it. Choose renewal only when you intentionally need a replacement certificate.
- Choose whether to redirect HTTP traffic to HTTPS: Choose the redirect option for most public sites unless you have a specific reason to keep HTTP and HTTPS separate.
After issuance, Certbot prints the certificate paths and renewal information. Run sudo certbot certificates afterward if you need to confirm the installed certificate name or key type.
Automating SSL Certificate Renewal
Let’s Encrypt certificates are short-lived, so automated renewal is essential to avoid service disruptions. Debian’s Certbot package includes a systemd timer that handles renewal automatically. Verify that timer first, and use the cron fallback only for systems where systemd timers are not available.
Verify Systemd Timer (Recommended)
Debian’s Certbot package includes a systemd timer that runs renewal checks twice daily. Verify that the timer is enabled and active:
systemctl is-enabled certbot.timer
systemctl is-active certbot.timer
Expected output:
enabled active
If either command reports
disabledorinactive, inspect the timer withsystemctl status certbot.timerbefore adding a duplicate cron job.
If the timer is active and enabled, automatic renewal is already configured. You can skip the cron setup below.
Test Renewal with Dry Run
Test renewal manually with a dry run. This simulates the renewal process without replacing your current certificate:
sudo certbot renew --dry-run
When your certificate is already valid and not due for renewal, a successful dry run still simulates renewal without changing the active certificate. Relevant output includes:
Saving debug log to /var/log/letsencrypt/letsencrypt.log Processing /etc/letsencrypt/renewal/example.com.conf Cert not due for renewal, but simulating renewal for dry run Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/example.com/fullchain.pem (success)
If there are errors during the dry run, the output will display them clearly. Common issues include DNS resolution failures or firewall blocks. Address any errors before proceeding to automatic scheduling.
Alternative: Schedule Renewals with Cron
If the systemd timer is unavailable, configure automatic renewal manually with cron. Do not add this cron job when certbot.timer is already active, because duplicate renewal schedules make troubleshooting harder.
Open the root crontab:
sudo crontab -e
Add a daily renewal check at 2:30 AM. Use a low-traffic time for your server and avoid creating a duplicate schedule when certbot.timer already runs:
30 2 * * * /usr/bin/certbot renew --quiet
The --quiet option ensures that the renewal process runs silently in the background without producing any output unless there’s an error.
After adding this line, save and close the file. Cron will check daily for certificates that are due for renewal and renew them when needed.
Enhance Apache SSL Configuration
These Apache SSL settings improve security and performance with HTTP/2, HTTP Strict Transport Security (HSTS), common security headers, and modern TLS protocols. If you used Certbot hardening flags during issuance, check the generated virtual host first so you do not add duplicate directives.
Enable Required Apache Modules
Before modifying the SSL configuration, enable the required Apache modules. Certbot usually enables mod_ssl during certificate setup, but this command also covers redirects, security headers, and HTTP/2. On Debian, enabling ssl also enables socache_shmcb as a dependency when needed. If a module is already enabled, a2enmod reports that state without changing it.
sudo a2enmod ssl rewrite headers http2
Then restart Apache to load any newly enabled modules:
sudo systemctl restart apache2
Edit the Apache Configuration File
To start, you need to access the configuration file for your domain within Apache. If Certbot has already created this file, open it with:
sudo nano /etc/apache2/sites-available/example.com-le-ssl.conf
Certbot automatically creates SSL-enabled virtual host files with the -le-ssl.conf suffix. If this file does not exist, use your normal HTTP virtual host file, such as example.com.conf, instead. Locate the <VirtualHost *:443> block for HTTPS-only directives. For HTTP redirect rules, edit the matching <VirtualHost *:80> block.
Redirect HTTP to HTTPS
If Certbot did not already create a redirect, add this rule inside the <VirtualHost *:80> block. The condition excludes requests to .well-known/acme-challenge/, which Certbot uses for HTTP validation.
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/\.well\-known/acme\-challenge/
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
Enable SSL and Specify Certificates
Within the <VirtualHost *:443> block, enable SSL and specify the paths to your SSL certificate and private key. Let’s Encrypt stores certificates in /etc/letsencrypt/live/example.com/ for this example certificate name:
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
Replace example.com with your actual domain name.
Enable HTTP/2
To improve performance, enable HTTP/2 support. Current Debian 13, 12, and 11 Apache packages include mod_http2. After enabling the module earlier, add this directive to the <VirtualHost *:443> block:
Protocols h2 http/1.1
Before relying on HTTP/2, check the active Apache MPM:
sudo apache2ctl -M | grep mpm
mpm_event_module (shared)
Debian’s default Apache setup normally uses mpm_event, which is the better fit for HTTP/2. If your server uses mpm_prefork, often because libapache2-mod-php is installed, Apache’s HTTP/2 guide notes severe restrictions with prefork. Switch to an event-compatible PHP setup such as PHP-FPM before treating HTTP/2 as a performance improvement.
If you are troubleshooting older Apache 2.4.x references, verify the installed Apache build before changing TLS settings:
sudo apache2ctl -v
Server version: Apache/2.4.x (Debian) Server built: [build date]
If your server reports an older Apache 2.4.x build than the current Debian security package, run sudo apt update and apply available Apache updates before continuing.
Implement HSTS
Add a Strict-Transport-Security header after HTTPS works correctly. Start with a one-year policy for the current hostname:
Header always set Strict-Transport-Security "max-age=31536000"
The HSTS header tells browsers to use HTTPS for future visits to the hostname. A one-year max-age is enough for strong browser enforcement without locking every subdomain into the policy.
Add
includeSubDomains; preloadonly after confirming HTTPS works for every subdomain you control and after reviewing the HSTS preload list requirements. Removing a preloaded domain can take months.
Add Additional Security Headers
Beyond HSTS, several HTTP security headers protect against common web vulnerabilities. Add these headers to your <VirtualHost *:443> block:
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Each header serves a specific security purpose:
X-Content-Type-Options: nosniffprevents browsers from MIME-sniffing responses away from the declared content type, blocking attacks that rely on disguising malicious files.X-Frame-Options: SAMEORIGINprevents your site from being embedded in iframes on other domains, protecting against clickjacking attacks.Referrer-Policy: strict-origin-when-cross-origincontrols what referrer information is sent with requests, balancing functionality with privacy.
Configure SSL Protocols and Ciphers
Use a conservative TLS baseline that disables legacy protocols while keeping TLS 1.2 and TLS 1.3 available:
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite 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
SSLHonorCipherOrder off
SSLSessionTickets off
This configuration explicitly enables only TLSv1.2 and TLSv1.3. The TLSv1.2 cipher suite prioritizes authenticated encryption algorithms that provide forward secrecy, while TLSv1.3 uses its own modern cipher selection through OpenSSL.
Skip OCSP Stapling for Let’s Encrypt Certificates
Because Let’s Encrypt removed OCSP URLs from certificates in 2025, current Let’s Encrypt certificates normally do not advertise an OCSP responder URL. Apache OCSP stapling directives add noise without improving the site, so check the certificate before copying older SSLUseStapling examples:
openssl x509 -noout -ocsp_uri -in /etc/letsencrypt/live/example.com/fullchain.pem
If this command prints no URL, do not add SSLUseStapling or SSLStaplingCache for that Let’s Encrypt certificate. Use OCSP stapling only for another certificate authority that still publishes OCSP responder URLs, and place SSLStaplingCache in global Apache configuration when that CA requires it.
Validate and Apply the Changes
Validate the Apache configuration before reloading so syntax errors do not take the site offline:
sudo apachectl configtest
Successful syntax checks end with:
Syntax OK
If Apache also prints an AH00558 message about the server’s fully qualified domain name, that warning is separate from the SSL syntax check. The configuration is still valid when the final line says Syntax OK.
If there are no issues, reload Apache. A reload applies configuration changes without dropping active connections the way a full restart can:
sudo systemctl reload apache2
Test Your SSL Configuration
Verify your security grade and identify any remaining issues using the SSL Labs Server Test. Enter your domain on the site or visit:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
Replace example.com with your actual domain. A clean certificate chain, current TLS settings, and HSTS usually produce an A-grade result or better.
Next Steps for Server Security
After HTTPS, the Apache hardening checks, and the SSL Labs scan look clean, add the remaining security layers that fit your server:
- Configure UFW firewall on Debian to restrict port access and block unwanted traffic
- Install Fail2ban on Debian to protect against brute-force attacks on SSH and web services
- Implement ModSecurity with Apache for web application firewall protection
- Configure unattended upgrades on Debian to keep your system patched automatically
If you are using Nginx instead of Apache, see securing Nginx with Let’s Encrypt on Debian. For more advanced Certbot options and automation, consult the official Certbot documentation.
Troubleshooting Common Issues
Use these checks for common Apache, Certbot, DNS, and renewal failures.
Certificate Validation Fails
When Certbot cannot validate your domain, you will see an error like:
Failed authorization procedure. example.com (http-01): urn:ietf:params:acme:error:connection :: The server could not connect to the client to verify the domain
This error usually means port 80 is blocked or Apache is not serving the validation file. Check whether Apache is listening on port 80:
sudo ss -tlnp | grep :80
A working HTTP listener returns a line similar to this:
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("apache2",pid=1234,fd=4))
If nothing is listening on port 80, start Apache and repeat the listener check:
sudo systemctl start apache2
sudo ss -tlnp | grep :80
If Apache is listening but validation still fails, check whether UFW is active and blocking HTTP:
sudo ufw status
When UFW is active and blocking ports 80 and 443, allow HTTP and HTTPS traffic:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Apache Fails to Start After SSL Configuration
If Apache fails to start after editing the configuration, inspect the service log without opening a pager:
sudo journalctl -xeu apache2 --no-pager
A common error is a missing Apache module for a directive used in the virtual host:
AH00526: Syntax error on line 42 of /etc/apache2/sites-available/example.com.conf: Invalid command 'SSLEngine', perhaps misspelled or defined by a module not included AH00526: Syntax error on line 18 of /etc/apache2/sites-available/example.com.conf: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included
The SSLEngine error means mod_ssl is not enabled. The RewriteEngine error means mod_rewrite is not enabled. Enable both modules first:
sudo a2enmod ssl rewrite
Retest the Apache configuration, then restart Apache after the syntax check succeeds:
sudo apachectl configtest
Syntax OK
sudo systemctl restart apache2
Certificate Renewal Fails
If automatic renewal fails, you will receive an email notification. Inspect the latest renewal log entries:
sudo tail -n 80 /var/log/letsencrypt/letsencrypt.log
Common causes include changed DNS records, firewall rules blocking validation, or rate limits. Test renewal with a dry run first:
sudo certbot renew --dry-run
Use
--force-renewalonly when you intentionally need to replace a certificate before its normal renewal window. Routine troubleshooting should use--dry-runto avoid consuming production issuance capacity.
Verify the renewal state by checking the certificate expiration date:
sudo certbot certificates
Rate Limit Errors
If you see an error mentioning rate limits, you have exceeded one of Let’s Encrypt’s issuance limits. The common production limits include 50 new certificates per registered domain every 7 days and 5 certificates for the exact same set of identifiers every 7 days. Rate-limit responses include retry timing, and limits refill gradually. Check the official Let’s Encrypt rate limits page when troubleshooting a live limit because these policies can change.
If you hit a rate limit, use the following approach:
- Wait until the retry time shown in the error response instead of repeatedly retrying
- Use the staging environment for testing: add
--stagingto your Certbot commands - Consolidate multiple domains into a single certificate using
-dflags
Revoking a certificate does not reset issuance limits. Normal renewals are designed to avoid these limits, especially when the ACME client and certificate authority can coordinate renewal timing.
DNS Validation Takes Too Long (Wildcard Certificates)
Should DNS validation for wildcard certificates fail or time out, the TXT record may not have propagated yet. The dig command comes from bind9-dnsutils on Debian; install it first if the command is missing:
sudo apt install bind9-dnsutils
Before pressing Enter in Certbot, verify that the TXT record exists:
dig TXT _acme-challenge.example.com +short
If validation succeeds, you should see the challenge string Certbot asked you to add. If the output is empty, wait a few more minutes for DNS propagation. Some DNS providers can take up to 30 minutes to publish TXT record changes globally.
Mixed Content Warnings in Browser
When your website shows a mixed content warning, some resources are still loading over HTTP instead of HTTPS. Check the browser developer console for the exact URLs, then update internal links, images, scripts, and stylesheets to use HTTPS or relative URLs. For WordPress sites, confirm the WordPress Address and Site Address use HTTPS, then use a staging-tested search-and-replace or a maintained mixed-content plugin if old HTTP URLs remain in the database.
Test SSL Certificate Chain
To verify your SSL certificate chain is properly configured and trusted by clients, use OpenSSL to test the connection:
openssl s_client -connect example.com:443 -servername example.com </dev/null
The command displays certificate and connection details, then exits after reading from /dev/null. In the output, look for Verify return code: 0 (ok), which confirms the certificate chain is valid and trusted by your system. Any other return code indicates chain validation failed.
Remove Certbot and Certificates
If you need to remove Certbot and clean up your SSL configuration, start by deciding whether the certificate should be revoked. Revocation is optional, but it is appropriate when you are decommissioning the domain or believe the private key was exposed:
sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem
Remember to replace example.com with your actual domain. If you only want to remove one local certificate lineage without uninstalling Certbot, delete it by certificate name:
sudo certbot delete --cert-name example.com
To uninstall Certbot itself, remove the packages:
sudo apt remove --purge certbot python3-certbot-apache
If APT reports autoremovable dependencies afterward, preview the list before removing them. Reused servers can have old packages unrelated to Certbot in the autoremove set.
sudo apt autoremove --dry-run
Only run the removal command if the preview list contains packages you truly want to remove:
sudo apt autoremove
Remove Certificate Files
The next cleanup command permanently deletes all Let’s Encrypt certificates, private keys, renewal configurations, and account data. Back up anything you may need later, and proceed only if no Certbot-managed certificate on this server should remain.
Preview the certificate-related paths that exist before deleting them:
sudo find /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt -maxdepth 0 -print 2>/dev/null
Remove Certbot-managed certificate data and account information only after reviewing the path list:
sudo rm -rf /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt
After removing Certbot, update your Apache virtual host configurations to remove any SSL directives that reference the deleted certificates. Otherwise, Apache will fail to start due to missing certificate files.
Conclusion
Your Apache site now has a Let’s Encrypt certificate, an HTTPS redirect, renewal checks, and optional hardening for HSTS, HTTP/2, security headers, and modern TLS settings. Keep certbot.timer active, retest renewal after DNS or firewall changes, and rerun SSL Labs after major Apache changes. If you plan to host a CMS, the guide on installing WordPress with Apache on Debian walks through the complete setup.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><a href="https://example.com">link</a><blockquote>quote</blockquote>