Web servers
How to set up HTTPS on Apache with Let's Encrypt and Certbot
Certbot's Apache plugin gets a free Let's Encrypt certificate for Apache httpd 2.4, writes the SSL virtual host and adds an HTTP-to-HTTPS redirect in one command. Renewal then runs on its own from a systemd timer.
The steps apply to Ubuntu 22.04/24.04, Debian 12/13 and RHEL-compatible systems (Rocky, AlmaLinux 9/10) with Certbot 4.1 or later.
Prerequisites
Let's Encrypt must be able to reach the server over HTTP on port 80.
- A Linux server with systemd, snapd and sudo access.
- Apache httpd 2.4 installed with a working port-80 virtual host whose
ServerName/ServerAliasmatch the domains you want. - DNS
A/AAAArecords forexample.comandwww.example.compointing at the server, for example203.0.113.10. - TCP ports 80 and 443 open in the firewall and any cloud security group, for all source addresses.
dig +short example.com A
sudo ufw allow 'Apache Full' # Debian/Ubuntu with ufw
sudo firewall-cmd --permanent --add-service=http --add-service=https && sudo firewall-cmd --reload # RHEL family
Step 1: Enable mod_ssl and check the virtual host
Certbot needs mod_ssl to create the port-443 virtual host. On Debian and Ubuntu:
sudo a2enmod ssl headers
sudo systemctl reload apache2
On RHEL-based systems install the module package:
sudo dnf install mod_ssl
sudo systemctl reload httpd
Make sure the HTTP virtual host names the domains explicitly:
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
</VirtualHost>
sudo a2ensite example.com.conf
sudo apachectl configtest && sudo systemctl reload apache2
Step 2: Install Certbot
The snap is Certbot's recommended installation method and includes the Apache plugin.
sudo apt-get remove certbot # or: sudo dnf remove certbot
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
Without snap, follow the pip instructions on certbot.eff.org and install certbot certbot-apache into /opt/certbot.
Step 3: Get the certificate and configure Apache
One certbot --apache command obtains the certificate and configures Apache. List every name the certificate should cover:
sudo certbot --apache -d example.com -d www.example.com
Certbot proves control with the HTTP-01 challenge, writes an SSL virtual host, adds an HTTP-to-HTTPS redirect if you accept it, and reloads Apache. The email prompt is optional because Let's Encrypt stopped sending expiry emails on June 4, 2025. To obtain the certificate without touching the configuration, use sudo certbot certonly --apache -d example.com -d www.example.com.
Step 4: Review the SSL virtual host
The virtual host below is equivalent to what Certbot generates; use it if you maintain the configuration yourself:
<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
</VirtualHost>
SSLProtocol -all +TLSv1.2 +TLSv1.3
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
Protocols h2 needs mod_http2 (sudo a2enmod http2). Do not enable SSLUseStapling; Let's Encrypt certificates no longer carry an OCSP URL. Test and reload:
sudo apachectl configtest && sudo systemctl reload apache2
Verify
A working setup shows Let's Encrypt as the issuer, both names in the certificate and a 301 redirect from HTTP. Check it with the SSL checker or from any terminal:
curl ssl.sh/example.com
On the server:
sudo certbot certificates
curl -sI http://example.com | grep -i '^location'
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Automatic renewal
The snap's systemd timer runs certbot renew twice a day; Certbot 4.1 and later follow Let's Encrypt's ARI renewal window, otherwise they renew with one third of the lifetime left. Certificates installed with --apache reload Apache after renewal.
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
For certificates obtained with certonly, add a reload hook (apache2 on Debian/Ubuntu, httpd on RHEL):
sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload apache2"
More hook patterns are in Certbot renewal hooks. The timer handles Let's Encrypt's move to 64-day certificates on February 10, 2027 without changes. Add the site to ssl.sh monitoring (in development) because Let's Encrypt no longer emails before expiry.
Troubleshooting
- Invalid command 'SSLEngine': mod_ssl is not loaded. Run
sudo a2enmod sslor installmod_ssl. - Unable to find a virtual host listening on port 80: create the
*:80vhost from Step 1 with matchingServerName. - Timeout during connect: port 80 is blocked or DNS points elsewhere; see certbot timeout during connect.
- ERR_SSL_PROTOCOL_ERROR in the browser: Apache is answering plain HTTP on port 443, usually because
SSLEngine onis missing from the 443 vhost; see ERR_SSL_PROTOCOL_ERROR. - Certificate works in browsers but not in curl or Java: the chain is incomplete; see missing intermediate certificate.
- Wrong certificate for www: the name is missing from the certificate; see NET::ERR_CERT_COMMON_NAME_INVALID.
FAQ
Do I still need SSLCertificateChainFile?
No. Since Apache httpd 2.4.8, SSLCertificateFile can contain the certificate followed by its intermediates, so point it at fullchain.pem. SSLCertificateChainFile is deprecated and only needed on versions older than 2.4.8.
Where does Certbot put the Apache HTTPS configuration?
On Debian and Ubuntu it creates /etc/apache2/sites-available/example.com-le-ssl.conf (named after your HTTP vhost file) and enables it. The file includes /etc/letsencrypt/options-ssl-apache.conf, which Certbot keeps up to date.
Can I use certbot --apache behind a CDN or load balancer?
Only if HTTP requests for /.well-known/acme-challenge/ reach Apache unchanged. If the CDN terminates TLS or blocks port 80, use the DNS-01 challenge with a DNS plugin instead, for example Certbot with Cloudflare DNS.