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
$ getent ahosts host.example.net | head -n 2 203.0.113.10 STREAM host.example.net 203.0.113.10 DGRAM host.example.net
$ 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.
$ 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.
$ 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.
$ 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.
$ 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
$ sudo systemctl reload apache2
$ 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.
$ curl -I https://host.example.net HTTP/2 200 server: Apache/2.4.58 (Ubuntu) ##### snipped #####
$ curl -I http://host.example.net HTTP/1.1 301 Moved Permanently Location: https://host.example.net/ ##### snipped #####
$ 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.
$ 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.
$ 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.