Serving an Apache site over HTTPS with a Let's Encrypt certificate protects logins, sessions, and API traffic from interception while removing browser trust warnings that make the site look broken or unsafe.
Let's Encrypt uses the ACME protocol to prove control of each requested name before it issues a short-lived certificate. With the Apache plugin, certbot can request the certificate, update the matching virtual host, store the certificate material under /etc/letsencrypt/live/, and leave renewal to the packaged systemd timer.
Examples below use the Debian and Ubuntu Apache layout with apache2ctl, the apache2 service name, and the distro packages certbot plus python3-certbot-apache. Public DNS, port 80 reachability, and a working /.well-known/acme-challenge/ path all need to line up before issuance, so reverse proxies, CDNs, and broken .AAAA records can block validation even when one local test still succeeds.
Related: How to secure Apache web server
Related: How to enable HSTS in Apache
Steps to configure Let's Encrypt SSL in Apache:
- Confirm that the domain resolves to the server public IP address.
$ getent ahosts host.example.net | head -n 2 203.0.113.10 STREAM host.example.net 203.0.113.10 DGRAM host.example.net
- Confirm that Apache serves the site over HTTP on port 80.
$ curl -I http://host.example.net HTTP/1.1 200 OK Server: Apache/2.4.58 (Ubuntu) ##### snipped #####
The default HTTP-01 validation path uses port 80, so a site that only works through a private address, alternate port, or VPN path will not validate.
- Confirm that the Apache virtual host selection matches the intended site for the domain.
$ sudo apache2ctl -S VirtualHost configuration: *:80 host.example.net (/etc/apache2/sites-enabled/host.example.net.conf:1) ServerRoot: "/etc/apache2" ##### snipped #####
Multiple vhosts with overlapping ServerName or ServerAlias entries can cause certbot to edit the wrong site.
- Install certbot and the Apache plugin on Debian-style systems.
$ sudo apt update Hit:1 http://deb.debian.org/debian bookworm InRelease ##### snipped ##### $ sudo apt install --assume-yes certbot python3-certbot-apache ##### snipped #####
Other packaged install paths, including snap, use different renewal unit names, so match the later timer check to the install method actually used.
- Run certbot with the Apache plugin for the domain.
$ sudo certbot --apache -d host.example.net -d www.host.example.net Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for host.example.net and www.host.example.net ##### snipped ##### Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/host.example.net/fullchain.pem Key is saved at: /etc/letsencrypt/live/host.example.net/privkey.pem Deploying certificate ##### snipped #####
The Apache plugin both authenticates and installs the certificate, and its default validation flow uses HTTP-01 on port 80. Wildcard names require a DNS-based validation plugin instead.
Current certbot install or run mode defaults to HTTP→HTTPS redirects unless --no-redirect is supplied, so use --no-redirect when HTTP must stay reachable during rollout or another layer already handles redirects.
Use --test-cert to rehearse against the Let's Encrypt staging service before requesting a live certificate.
- Validate the Apache configuration syntax after the SSL vhost change.
$ sudo apache2ctl configtest Syntax OK
Reloading with a broken SSL vhost can take HTTPS offline for the entire server.
Related: How to test Apache configuration
- Reload the Apache service to apply the SSL configuration.
$ sudo systemctl reload apache2
- Confirm that the Apache service stayed active after the reload.
$ sudo systemctl is-active apache2 active
If the service does not return active, inspect the journal before retrying the reload because the certificate paths, permissions, or vhost selection may still be wrong.
- Verify HTTPS responses for the site.
$ curl -I https://host.example.net HTTP/2 200 server: Apache/2.4.58 (Ubuntu) ##### snipped #####
- Verify that HTTP redirects to HTTPS when redirect is enabled.
$ curl -I http://host.example.net HTTP/1.1 301 Moved Permanently Location: https://host.example.net/ ##### snipped #####
- Verify the certificate chain and expiry served by the site using openssl.
$ echo | openssl s_client -servername host.example.net -connect host.example.net:443 2>/dev/null | openssl x509 -noout -issuer -subject -dates issuer=C=US,O=Let's Encrypt,CN=R12 subject=CN=host.example.net notBefore=Feb 24 23:33:33 2026 GMT notAfter=May 25 23:33:32 2026 GMT
The intermediate name can differ by chain and key type, but the subject, dates, and issuer organization should match the deployed certificate lineage.
- Validate that the certbot renewal timer is active.
$ sudo systemctl status certbot.timer --no-pager ● certbot.timer - Run certbot twice daily Loaded: loaded (/lib/systemd/system/certbot.timer; enabled; vendor preset: enabled) Active: active (waiting) since Wed 2026-04-08 04:00:00 UTC; 2min ago Trigger: Wed 2026-04-08 10:34:11 UTC ##### snipped #####snap installs use snap-managed unit names instead of certbot.timer, so check the timer or service that matches the packaging method actually in use.
- Perform a renewal dry run to confirm automated renewal works.
$ sudo certbot renew --dry-run Saving debug log to /var/log/letsencrypt/letsencrypt.log ##### snipped ##### Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/host.example.net/fullchain.pem (success)
--dry-run uses the staging service and does not replace the active production certificate.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
