How to Secure Apache with Let’s Encrypt on Debian 13, 12 and 11

Secure Apache with Let's Encrypt SSL certificates on Debian 13, 12 and 11. Includes HTTPS redirect, HSTS, and auto-renewal.

Last updatedAuthorJoshua JamesRead time14 minGuide typeDebian

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 ecdsa so 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 disabled or inactive, inspect the timer with systemctl status certbot.timer before 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; preload only 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: nosniff prevents browsers from MIME-sniffing responses away from the declared content type, blocking attacks that rely on disguising malicious files.
  • X-Frame-Options: SAMEORIGIN prevents your site from being embedded in iframes on other domains, protecting against clickjacking attacks.
  • Referrer-Policy: strict-origin-when-cross-origin controls 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:

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-renewal only when you intentionally need to replace a certificate before its normal renewal window. Routine troubleshooting should use --dry-run to 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 --staging to your Certbot commands
  • Consolidate multiple domains into a single certificate using -d flags

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.

Share this guide

Help another Linux user troubleshoot faster

Share this guide with someone troubleshooting Linux systems or saving it for later.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show our tutorials more often in Top Stories and mark them as preferred in AI Mode and AI Overviews when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<a href="https://example.com">link</a> link
<blockquote>quote</blockquote> quote block

Add to the discussion

Questions, fixes, command output, and version notes help keep this guide current.

Verify before posting: