Secure Apache with Let’s Encrypt on Ubuntu 26.04, 24.04 and 22.04

Last updated Monday, May 18, 2026 8:59 am Joshua James 8 min read

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 A or AAAA record points to this Ubuntu server.
  • Your Apache port 80 virtual host uses the real domain in ServerName and any extra hostnames in ServerAlias.
  • Ports 80 and 443 are 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 80 and 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 sudo for 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:

OptionWhat it changes
--apacheUses Certbot’s Apache plugin for validation and Apache configuration edits.
--non-interactiveFails with an error instead of waiting for prompts, which keeps the command suitable for servers and scripts.
--agree-tosAccepts the Let’s Encrypt subscriber agreement without an extra prompt.
--no-eff-emailSkips Certbot’s optional email-sharing prompt for the EFF mailing list.
--redirectAdds an HTTP to HTTPS redirect for the authenticated virtual host.
--emailRegisters the certificate contact address for expiry and security notices.
-dLists 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; preload only 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.

Follow LinuxCapable

Want more LinuxCapable guides in Google?

Add LinuxCapable as a preferred source so Google can show more of our fresh Linux tutorials in Top Stories and From your sources when relevant.

Add LinuxCapable as a preferred source on Google
Search LinuxCapable

Need another guide?

Search LinuxCapable for package installs, commands, troubleshooting, and follow-up guides related to what you just read.

Found this guide useful?

Support LinuxCapable to keep tutorials free and up to date.

Buy me a coffeeBuy me a coffee
Before commenting, please review our Comments Policy.
Formatting tips for your comment

You can use basic HTML to format your comment. Useful tags currently allowed in published comments:

You type Result
<code>command</code> command
<strong>bold</strong> bold
<em>italic</em> italic
<blockquote>quote</blockquote> quote block

Got a Question or Feedback?

We read and reply to every comment - let us know how we can help or improve this guide.

Let us know you are human: