HTTPS on Apache: mod_ssl, Let’s Encrypt, mod_md and TLS settings

Updated November 2026

HTTPS on Apache httpd 2.4 needs three things: mod_ssl, a certificate that renews itself, and sensible TLS settings. Let’s Encrypt certificates are free and can be renewed by certbot or by Apache’s own mod_md module. Since 2025 Let’s Encrypt has stopped OCSP and expiry emails, and from February 2027 its certificates get shorter, so a renewal job you set up years ago may need checking.

Before you start

On Debian and Ubuntu, mod_ssl ships with the apache2 package and only needs enabling. The package already contains Listen 443 (in ports.conf), so do not add a second one.

sudo a2enmod ssl headers
sudo apache2ctl configtest
sudo systemctl restart apache2

On RHEL, AlmaLinux, Rocky Linux and Fedora, mod_ssl is a separate package. It installs /etc/httpd/conf.d/ssl.conf with Listen 443 https and a default virtual host that uses a self-signed test certificate.

sudo dnf install mod_ssl
sudo apachectl configtest
sudo systemctl restart httpd

Option 1: certbot

Certbot is the ACME client made by the Electronic Frontier Foundation. Its own instructions recommend the snap package; remove any certbot installed from apt or dnf first so that the right one runs.

sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
sudo certbot --apache

certbot --apache gets a certificate and edits your virtual host for you. If you would rather write the configuration yourself, run sudo certbot certonly --apache; the files then land in /etc/letsencrypt/live/example.com/ as fullchain.pem (certificate plus intermediate) and privkey.pem. The package installs a systemd timer or cron job for renewals. Test it:

sudo certbot renew --dry-run

Certbot 4.1.0 (June 2025) and later ask Let’s Encrypt when to renew through ACME Renewal Information (ARI). For Let’s Encrypt certificates that usually means renewing at about two thirds of the lifetime, and it also lets the CA trigger an early renewal if a certificate has to be revoked. As of November 2026 the current certbot release is 5.9.0.

Option 2: mod_md

mod_md (in httpd since 2.4.30) is an ACME client built into Apache. You list the domain names, and Apache fetches and renews the certificate itself; there is no separate tool or cron job. The module is still marked “Experimental” in the documentation, but it is widely used. It needs mod_watchdog, which Debian compiles into httpd and Fedora’s packages load by default.

# Debian/Ubuntu
sudo a2enmod md ssl
# RHEL/Fedora
sudo dnf install mod_md

A minimal configuration, for example in /etc/apache2/sites-available/example.com.conf or /etc/httpd/conf.d/example.com.conf:

MDCertificateAgreement accepted
MDContactEmail admin@example.com
MDomain example.com www.example.com

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com
    SSLEngine on
    # no SSLCertificateFile lines: mod_md supplies the certificate
    Protocols h2 http/1.1 acme-tls/1
</VirtualHost>

MDCertificateAgreement accepted says you accept the CA’s terms of service. acme-tls/1 in Protocols lets mod_md answer the TLS-ALPN-01 check on port 443. Until the first certificate arrives, requests to that host get a 503 Service Unavailable.

A reload activates the certificate. mod_md fetches the new certificate in the background but only puts it into use at the next restart or graceful reload. Add a daily reload, for example a root cron line 30 4 * * * systemctl reload apache2 (or httpd), or use MDMessageCmd to be told when a certificate was renewed.

Useful mod_md settings, all verified in the httpd 2.4 documentation:

If you redirect HTTP to HTTPS yourself, leave /.well-known/ out of the redirect, or use MDRequireHttps permanent, which handles that for you.

A complete HTTPS virtual host

This example uses the certbot file paths. The redirect keeps /.well-known/acme-challenge/ on plain HTTP, the way the TLSRef configuration generator writes it (it needs mod_rewrite).

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
    RewriteRule ^ https://example.com%{REQUEST_URI} [R=301,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem

    Protocols h2 http/1.1
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
</VirtualHost>

Since httpd 2.4.8, SSLCertificateFile can hold the certificate and the intermediate certificates together, so the old SSLCertificateChainFile directive is deprecated. Point it at fullchain.pem, not cert.pem, or some clients will fail with chain errors.

The Mozilla SSL Configuration Generator (ssl-config.mozilla.org) now redirects to the TLSRef Configurator at configurator.tlsref.org, which carries on the same guidelines. Its “Intermediate” profile (guideline 6.0, as of November 2026) is the right choice for almost every public site. For Apache it produces these server-wide settings, which you can put in /etc/apache2/conf-available/tls.conf (then sudo a2enconf tls) or a file in /etc/httpd/conf.d/:

SSLProtocol             -all +TLSv1.2 +TLSv1.3
SSLOpenSSLConfCmd       Curves X25519MLKEM768:X25519:prime256v1:secp384r1
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
SSLCipherSuite          TLSv1.3 TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
SSLHonorCipherOrder     off
SSLSessionTickets       off

Check your OpenSSL version first. X25519MLKEM768 is a hybrid post-quantum key exchange that needs OpenSSL 3.5 or later; with an older OpenSSL, httpd refuses to start with that Curves line. Run openssl version. On older systems use Curves X25519:prime256v1:secp384r1. The generator drops the hybrid group automatically when you enter an older OpenSSL version.

Notes: TLS 1.0 and 1.1 are off. The client picks the cipher, because every listed suite is strong. Session tickets are off because, as the mod_ssl documentation says, keeping them on without restarting the server regularly weakens forward secrecy. On RHEL-family systems the packaged ssl.conf uses SSLCipherSuite PROFILE=SYSTEM, which follows the system-wide crypto policy. Keeping that policy is also a valid choice.

HSTS

The Strict-Transport-Security header tells browsers to use only HTTPS for your domain for max-age seconds; 63,072,000 seconds is two years (the generator’s value). Browsers keep that promise even if you later turn HTTPS off, so start with max-age=300 (five minutes), check every subdomain works over HTTPS, then raise it. Add includeSubDomains only if all subdomains have HTTPS. Header always makes sure the header goes out on redirects and error pages too.

Let’s Encrypt changes 2025–2028

Every date below comes from the Let’s Encrypt blog and documentation (checked November 2026).

What changed at Let’s Encrypt and what Apache admins should do
DateChangeWhat to do
June 4, 2025Expiration notification emails ended; stored email addresses were deleted.Set up your own certificate monitoring.
May 7, 2025OCSP URLs removed from new certificates; requests with OCSP Must-Staple fail.Remove MDMustStaple on or --must-staple.
Aug 6, 2025OCSP service turned off. Revocation is published only through CRLs.Nothing to staple for Let’s Encrypt certificates (see below).
Feb 11, 2026Default certificates no longer carry the TLS Client Authentication EKU; the tlsclient profile ended July 8, 2026.Only matters if you used these certificates as client certificates, for example for mutual TLS to a backend.
May 13, 2026The opt-in tlsserver profile issues 45-day certificates; 6-day shortlived certificates have been available to everyone since January 15, 2026.Optional; only if your renewals are fully automated.
Feb 10, 2027Default (classic) certificates drop from 90 to 64 days; authorization reuse falls from 30 to 10 days. Staging switched on October 14, 2026.Renew at about two thirds of the lifetime, or use ARI.
Feb 16, 2028Default certificates drop to 45 days, authorization reuse to 7 hours.Same: no hard-coded renewal dates.

The arithmetic shows why hard-coded schedules break. Two thirds of 90 days is 60 days, so “renew every 60 days” used to work. With 64-day certificates, two thirds is about 43 days; with 45-day certificates it is 30 days. Let’s Encrypt suggests searching cron jobs and scripts for numbers like 60, 80 or 83. Certbot 4.1+ and mod_md with its default 33% window already handle this, and rate limits do not change, because renewals are exempt.

OCSP stapling today

OCSP stapling (SSLUseStapling On in mod_ssl, or MDStapling on in mod_md) makes the server send a signed “this certificate is still valid” answer from the CA during the handshake. Current Let’s Encrypt certificates have no OCSP address, so there is nothing to fetch. Leave stapling off (the default in both modules) for them. If you also use certificates from a CA that still runs OCSP, stapling still works; the httpd SSL How-To lists recommended production values such as SSLStaplingResponderTimeout 4 and SSLStaplingReturnResponderErrors off. To see whether a certificate has an OCSP address:

openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -ocsp_uri

No output means no OCSP address.

Test your setup

After every change, test the configuration and then reload:

sudo apachectl configtest      # Debian: sudo apache2ctl -t
sudo systemctl reload apache2  # RHEL/Fedora: httpd

Check which certificate is served and when it expires:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -dates

The output shows the issuer (it should name Let’s Encrypt) and the notBefore and notAfter dates. Check the redirect and HSTS header with curl -sI http://example.com/ (expect 301 and a Location: https://…) and curl -sI https://example.com/ | grep -i strict. For a quick outside check of the certificate installation (names, expiry date and the intermediate chain), use an online SSL checker; for a full report, the Qualys SSL Labs Server Test grades protocols, ciphers and the chain.

Common mistakes

Next steps: turn on HTTP/2, set up HTTP to HTTPS and www redirects, and work through the hardening checklist.

Sources