An HTTP-only Apache site exposes logins, cookies, and form submissions before they reach your Ubuntu server. The fastest way to secure Apache with Let’s Encrypt on Ubuntu is Certbot’s Apache plugin, which requests the certificate, updates the matching virtual host, and keeps renewal under the Ubuntu package manager.
The APT package path works across Ubuntu 26.04, 24.04, and 22.04 with the same package names: certbot and python3-certbot-apache. Start with a working HTTP virtual host, then request the certificate, verify renewal, and add browser-facing hardening only after HTTPS is confirmed.
Secure Apache with Let’s Encrypt on Ubuntu
Certbot needs a domain that already reaches Apache over port 80. Check the server, DNS, and firewall first so the certificate request fails early only when a real prerequisite is missing.
If the site runs Nginx instead of Apache, use the separate workflow to secure Nginx with Let’s Encrypt on Ubuntu because the plugin package, site-file layout, and reload checks differ.
Check Apache and DNS prerequisites on Ubuntu
Make sure these pieces are already in place before you request a certificate:
- Apache is installed and running. If the web server itself is not ready, install Apache on Ubuntu first.
- Your domain’s
AorAAAArecord points to this Ubuntu server. - Your Apache port
80virtual host uses the real domain inServerNameand any extra hostnames inServerAlias. - Ports
80and443are reachable from the internet. If you use UFW, open Apache’s full web profile with the guide to install and configure UFW firewall on Ubuntu. - You control the Apache origin directly. If a CDN, hosting panel, load balancer, or proxy manager terminates TLS before traffic reaches Apache, configure the certificate at that layer instead.
Let’s Encrypt’s HTTP-01 challenge validates the domain over port
80and cannot issue wildcard certificates. Wildcard certificates need DNS-01 validation through a DNS plugin or another automated DNS workflow.
Update Ubuntu and install Certbot for Apache
Refresh package metadata before installing Certbot so APT uses the current Ubuntu package indexes:
sudo apt update
These commands use
sudofor tasks that need root privileges. If your account cannot run sudo commands yet, add a new user to sudoers on Ubuntu before continuing.
Install Certbot, the Apache integration package, and curl for the HTTP checks used later:
sudo apt install certbot python3-certbot-apache curl
The package python3-certbot-apache depends on apache2, so APT can install Apache automatically on a bare system. Still, configure and verify the Apache virtual host before requesting the certificate because Certbot needs a working site to edit.
On Ubuntu 26.04, 24.04, and 22.04, both Certbot packages come from Ubuntu’s universe component. If a minimal or customized server cannot find python3-certbot-apache, enable Universe on Ubuntu, refresh APT, and retry the install command.
Certbot’s official Apache on Linux Snap instructions are useful when you want the upstream Snap channel instead. Keep one Certbot install path per server so certificate files, renewal jobs, and plugin ownership stay predictable.
Confirm that Certbot is installed. On Ubuntu 26.04, current output shows:
certbot --version
certbot 4.0.0
Ubuntu 26.04 currently ships Certbot 4.0.0, Ubuntu 24.04 ships Certbot 2.9.0, and Ubuntu 22.04 ships Certbot 1.21.0. The Apache plugin package is available on all three supported LTS releases.
Verify the Apache site before requesting the certificate
Check Apache syntax and service state before Certbot edits the virtual host:
sudo apache2ctl configtest
systemctl is-active apache2
systemctl is-enabled apache2
Syntax OK active enabled
If the syntax test prints an AH00558 fully qualified domain name warning but still ends with Syntax OK, the warning is separate from the certificate request. Fix real syntax errors, inactive services, or disabled startup before continuing.
Send a local request with the same hostname you plan to pass to Certbot. A default Apache page in the response body usually means the wrong virtual host is answering:
curl -fsS -D - -o /dev/null -H 'Host: example.com' http://127.0.0.1/
HTTP/1.1 200 OK
Request the Let’s Encrypt certificate for Apache
Use the Apache plugin so Certbot can request the certificate and update the matching virtual host in one pass. Replace the email address and hostnames with your real values:
sudo certbot --apache --non-interactive --agree-tos --no-eff-email --redirect --email admin@example.com -d example.com -d www.example.com
Each option controls a specific part of the certificate workflow:
| Option | What it changes |
|---|---|
--apache | Uses Certbot’s Apache plugin for validation and Apache configuration edits. |
--non-interactive | Fails with an error instead of waiting for prompts, which keeps the command suitable for servers and scripts. |
--agree-tos | Accepts the Let’s Encrypt subscriber agreement without an extra prompt. |
--no-eff-email | Skips Certbot’s optional email-sharing prompt for the EFF mailing list. |
--redirect | Adds an HTTP to HTTPS redirect for the authenticated virtual host. |
--email | Registers the certificate contact address for expiry and security notices. |
-d | Lists every hostname that belongs on the same certificate. |
A successful run prints the certificate paths and the Apache deployment target. Relevant output includes:
Successfully received certificate. Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem These files will be updated when the certificate renews. Deploying certificate Successfully deployed certificate for example.com to /etc/apache2/sites-enabled/example.com-le-ssl.conf Successfully deployed certificate for www.example.com to /etc/apache2/sites-enabled/example.com-le-ssl.conf
If you need a certificate for more hostnames, add more -d options in the same command. Every hostname must resolve to the server and answer the HTTP-01 challenge before Let’s Encrypt will issue the certificate. Because the command is non-interactive, fix any virtual host match errors and rerun Certbot instead of waiting for an interactive chooser.
Check the issued certificate and HTTPS redirect
Ask Certbot to list the active certificate set:
sudo certbot certificates
Relevant lines show the certificate name, domain list, and live file paths:
Found the following certs:
Certificate Name: example.com
Domains: example.com www.example.com
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
Then test the redirect with curl:
curl -fsS -D - -o /dev/null http://example.com
HTTP/1.1 301 Moved Permanently Location: https://example.com/
If UFW is active, keep both HTTP and HTTPS open for normal Certbot renewal checks:
sudo ufw allow 'Apache Full'
sudo ufw status
Use Apache Secure only when your certificate workflow does not require HTTP validation, such as an automated DNS-01 setup. Closing port 80 after using the Apache plugin can break future HTTP-01 renewals.
Harden Apache HTTPS after the certificate works
Keep the first Certbot run focused on issuance and the redirect. After the site loads over HTTPS for every hostname, add headers and HTTP/2 deliberately so each change can be tested and rolled back.
Enable Apache modules for headers and HTTP/2
Enable the modules used by the hardening snippets. Certbot usually enables ssl during issuance, but rerunning a2enmod is safe when a module is already enabled:
sudo a2enmod ssl rewrite headers http2
sudo systemctl reload apache2
Confirm Apache loaded the expected modules:
sudo apache2ctl -M | grep -E 'ssl|rewrite|headers|http2'
headers_module (shared) http2_module (shared) rewrite_module (shared) ssl_module (shared)
Add HSTS and common security headers
Open the SSL virtual host file Certbot created for your site. The filename normally follows the original site name with a -le-ssl.conf suffix:
sudo nano /etc/apache2/sites-available/example.com-le-ssl.conf
Add these directives inside the <VirtualHost *:443> block:
Header always set Strict-Transport-Security "max-age=31536000"
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"
Add
includeSubDomains; preloadonly after HTTPS works for every subdomain you control and after reviewing the HSTS preload list requirements. Removing a preloaded domain from browser preload lists can take a long time.
Enable HTTP/2 with a compatible Apache MPM
Check the active Apache multi-processing module before you rely on HTTP/2:
sudo apache2ctl -M | grep mpm
mpm_event_module (shared)
Apache’s HTTP/2 guide notes severe restrictions with mpm_prefork. If your server uses mpm_prefork, often because libapache2-mod-php is installed, move PHP traffic to PHP-FPM before treating HTTP/2 as a performance improvement.
For an event-based Apache setup, add the protocol directive inside the same SSL virtual host:
Protocols h2 http/1.1
Validate and reload Apache HTTPS settings
Test Apache syntax before reloading the service:
sudo apache2ctl configtest
Syntax OK
Reload Apache after the syntax check passes:
sudo systemctl reload apache2
Check that the HTTPS response now includes the headers you added:
curl -fsS -D - -o /dev/null https://example.com | grep -Ei '^(HTTP/|strict-transport-security|x-content-type-options|x-frame-options|referrer-policy)'
HTTP/2 200 Strict-Transport-Security: max-age=31536000 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Referrer-Policy: strict-origin-when-cross-origin
Renew Let’s Encrypt certificates on Ubuntu
Let’s Encrypt certificates are short-lived, so renewal is part of the security setup. Ubuntu’s Certbot package includes a systemd timer; verify it instead of adding a duplicate cron job.
Test Certbot renewal on Ubuntu
Run a dry run after the first certificate exists so you know whether the renewal path works before the real expiry window arrives:
sudo certbot renew --dry-run
Before the first certificate exists, Certbot has nothing to renew and can print:
No simulated renewals were attempted.
After at least one real certificate exists, the same command should simulate renewal and end with a success summary:
Saving debug log to /var/log/letsencrypt/letsencrypt.log Processing /etc/letsencrypt/renewal/example.com.conf Simulating renewal of an existing certificate for example.com and www.example.com Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/example.com/fullchain.pem (success)
Confirm the automatic certbot.timer service on Ubuntu
Check the packaged renewal timer:
systemctl is-enabled certbot.timer
systemctl is-active certbot.timer
enabled active
If either check reports disabled or inactive, restore the timer and rerun the checks:
sudo systemctl enable --now certbot.timer
Update Certbot on Ubuntu
Keep Certbot current through APT so renewal fixes and ACME changes arrive with the rest of your Ubuntu patch cycle:
sudo apt update
sudo apt install --only-upgrade certbot python3-certbot-apache
Troubleshoot Let’s Encrypt and Apache on Ubuntu
Most Apache Certbot failures come from DNS, closed port 80, a virtual host that does not match the requested hostname, or repeated production testing. Start with the exact error Certbot prints, then use the matching checks here.
Fix DNS or HTTP-01 validation failures on Ubuntu
If Let’s Encrypt cannot reach the challenge URL, Certbot can report an unauthorized validation failure:
Challenge failed for domain example.com Detail: Invalid response from http://example.com/.well-known/acme-challenge/...
Check the DNS result first, then verify that the hostname answers over plain HTTP:
getent ahostsv4 example.com
curl -fsS -D - -o /dev/null http://example.com
203.0.113.10 STREAM example.com 203.0.113.10 DGRAM 203.0.113.10 RAW HTTP/1.1 200 OK
If the IP address is wrong, fix DNS and wait for propagation. If HTTP is unreachable, open port 80, confirm the Apache service is running, and make sure the requested hostname reaches the correct virtual host.
Fix Apache virtual host matching problems
The Apache plugin only edits virtual hosts that already declare the requested hostname. Inspect the active virtual host map when Certbot cannot find a match:
sudo apache2ctl -S
*:80 example.com (/etc/apache2/sites-enabled/example.com.conf:1)
alias www.example.com
If your domain is missing, edit the port 80 site file and add the real hostname before trying Certbot again:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
</VirtualHost>
Test and reload Apache after the change:
sudo apache2ctl configtest
sudo systemctl reload apache2
Fix dry-run output that says no simulated renewals were attempted
This message appears when Certbot has no existing renewal file for the server:
sudo certbot certificates
No certificates found.
Issue the first certificate with certbot --apache, then rerun sudo certbot renew --dry-run. Once a renewal file exists under /etc/letsencrypt/renewal/, the dry run simulates a real renewal instead of stopping immediately.
Avoid Let’s Encrypt rate limits while testing
Repeated production issuance attempts can hit Let’s Encrypt limits, especially when the same set of hostnames keeps failing. Use Certbot’s staging mode with certonly while testing so Apache validation runs without installing an untrusted staging certificate into the live virtual host:
sudo certbot certonly --apache --test-cert --non-interactive --agree-tos --no-eff-email --email admin@example.com -d example.com -d www.example.com
Let’s Encrypt’s rate limit policy is generous for normal renewal, but staging is safer during repeated troubleshooting because the test certificate is not browser-trusted and does not spend production issuance capacity.
Remove stale OCSP stapling snippets
New Let’s Encrypt certificates no longer need Apache OCSP stapling directives. If copied hardening snippets produce OCSP-related Apache warnings, search for custom stapling settings:
AH02218: ssl_stapling_init_cert: no OCSP URI in certificate and no SSLStaplingForceURL set
sudo grep -R -E 'SSLUseStapling|SSLStapling' /etc/apache2/ || echo "No custom stapling directives found"
Let’s Encrypt ended OCSP support in 2025 and removed OCSP URLs from certificates. Remove custom SSLUseStapling or SSLStaplingCache directives for Let’s Encrypt certificates unless you use another certificate authority that still publishes OCSP responder URLs.
sudo apache2ctl configtest
sudo systemctl reload apache2
Remove Certbot and Let’s Encrypt configuration from Ubuntu
Remove the certificate first when you are retiring a site or moving to a different ACME client. Then remove the Ubuntu packages and clean leftover certificate data only when no other site on the server uses Certbot.
Delete the certificate from Certbot on Ubuntu
Delete the certificate record before removing the package:
sudo certbot delete --cert-name example.com
If the private key was exposed, revoke the certificate first and then delete it. For a normal migration or site retirement, deletion alone is usually enough.
Remove Certbot packages from Ubuntu
Stop the renewal timer, remove the Certbot packages, then preview orphaned dependency cleanup:
sudo systemctl disable --now certbot.timer
sudo apt remove --purge certbot python3-certbot-apache
sudo apt autoremove --dry-run
If the dry run lists only packages you no longer need, run the real cleanup interactively:
sudo apt autoremove
Check that Certbot packages no longer have installed-state rows:
dpkg-query -W -f='${db:Status-Abbrev} ${binary:Package}\n' certbot python3-certbot-apache 2>/dev/null | grep '^ii' || echo "No Certbot packages are installed"
No Certbot packages are installed
Remove Apache SSL directives if the site stays online
If Apache will keep serving plain HTTP after the certificate is removed, delete the Certbot-created SSL virtual host or remove its SSLCertificateFile, SSLCertificateKeyFile, HTTPS listener, and redirect directives before reloading Apache.
sudo apache2ctl configtest
Syntax OK
Reload Apache after the syntax check passes:
sudo systemctl reload apache2
Remove leftover Let’s Encrypt data if the server is retired
The next commands permanently delete every Let’s Encrypt certificate, private key, renewal file, account file, and log directory on this server. Do not run them if any other Apache site or service still uses Certbot-managed certificates.
Review the shared Certbot directories before deleting them. Keep any path that still contains certificates, renewal files, account data, or logs for another site:
for path in /etc/letsencrypt /var/lib/letsencrypt /var/log/letsencrypt; do
[ -e "$path" ] && sudo find "$path" -maxdepth 2 -print
done
sudo rm -rf /etc/letsencrypt
sudo rm -rf /var/lib/letsencrypt
sudo rm -rf /var/log/letsencrypt
If the host still serves other domains, keep the shared Let’s Encrypt directories and remove only the retired site’s Apache configuration and certificate record.
Conclusion
Apache is serving HTTPS on Ubuntu with a Let’s Encrypt certificate and Certbot renewal timer in place. For the next hardening pass, add request filtering with ModSecurity with Apache on Ubuntu or protect authentication endpoints with Fail2Ban on Ubuntu.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>