# Apache + Let's Encrypt with Certbot: enable HTTPS step by step

> Get a free Let's Encrypt certificate for Apache httpd 2.4 with Certbot, configure the SSL virtual host, redirect HTTP to HTTPS and automate renewal.

Source: https://ssl.sh/guides/apache-lets-encrypt-certbot/
Last updated: 2026-11-10

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`/`ServerAlias` match the domains you want.
- DNS `A`/`AAAA` records for `example.com` and `www.example.com` pointing at the server, for example `203.0.113.10`.
- TCP ports 80 and 443 open in the firewall and any cloud security group, for all source addresses.

```bash
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:

```bash
sudo a2enmod ssl headers
sudo systemctl reload apache2
```

On RHEL-based systems install the module package:

```bash
sudo dnf install mod_ssl
sudo systemctl reload httpd
```

Make sure the HTTP virtual host names the domains explicitly:

```apache
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com
</VirtualHost>
```

```bash
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.

```bash
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:

```bash
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:

```apache
<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:

```bash
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](https://ssl.sh/tools/ssl-checker/) or from any terminal:

```bash
curl ssl.sh/example.com
```

On the server:

```bash
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.

```bash
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):

```bash
sudo certbot reconfigure --cert-name example.com --deploy-hook "systemctl reload apache2"
```

More hook patterns are in [Certbot renewal hooks](https://ssl.sh/guides/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](https://ssl.sh/monitor/) (in development) because Let's Encrypt no longer emails before expiry.

## Troubleshooting

- **Invalid command 'SSLEngine'**: mod_ssl is not loaded. Run `sudo a2enmod ssl` or install `mod_ssl`.
- **Unable to find a virtual host listening on port 80**: create the `*:80` vhost from Step 1 with matching `ServerName`.
- **Timeout during connect**: port 80 is blocked or DNS points elsewhere; see [certbot timeout during connect](https://ssl.sh/errors/certbot-timeout-during-connect/).
- **ERR_SSL_PROTOCOL_ERROR** in the browser: Apache is answering plain HTTP on port 443, usually because `SSLEngine on` is missing from the 443 vhost; see [ERR_SSL_PROTOCOL_ERROR](https://ssl.sh/errors/err-ssl-protocol-error/).
- **Certificate works in browsers but not in curl or Java**: the chain is incomplete; see [missing intermediate certificate](https://ssl.sh/errors/missing-intermediate-certificate/).
- **Wrong certificate for www**: the name is missing from the certificate; see [NET::ERR_CERT_COMMON_NAME_INVALID](https://ssl.sh/errors/net-err-cert-common-name-invalid/).
