SSL Setup on Amazon Linux 2023 (Apache + Let’s Encrypt)

This guide shows how to issue and automatically renew a Let’s Encrypt SSL certificate for an Apache website running directly on an EC2 instance with Amazon Linux 2023, without a load balancer.

Example domain used in this walkthrough: example.com


Prerequisites

Before starting, confirm the following:

  • The EC2 security group allows inbound traffic on ports 80 and 443.
  • The domain’s DNS A record points to the instance’s public IP.
  • An Elastic IP is in use. If the public IP changes after a stop/start cycle, DNS resolution and certificate renewal can both fail.

Check the operating system:

cat /etc/os-release

Step 1: Install Apache

Install Apache and the SSL module, then enable and start the service:

sudo dnf install httpd mod_ssl -y
sudo systemctl enable --now httpd

Step 2: Install Certbot with Python venv

Amazon Linux 2023 does not provide Certbot as a native package, so the cleanest approach is to install it in a Python virtual environment.

Install the required packages:

sudo dnf install python3 python3-pip augeas-libs -y
sudo dnf install python3-devel gcc libxml2-devel augeas-devel pkgconf-pkg-config -y

Create the virtual environment and install Certbot:

sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-apache

Create a symlink so certbot is available on the system path:

sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot
certbot --version

Step 3: Configure Apache for HTTP only

Before requesting the certificate, create only the port 80 virtual host.

Do not manually create a *:443 SSL virtual host yet. If Apache sees SSLEngine on without valid certificate paths, it can fail to start with errors such as AH02572: Failed to configure at least one certificate and key.

Create the vhost file:

/etc/httpd/conf.d/example.com.conf

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/html
</VirtualHost>

Validate the configuration and restart Apache:

sudo apachectl configtest
sudo systemctl restart httpd
sudo systemctl status httpd

Place a basic index file in the document root so Apache has content to serve:

echo "Welcome to example.com" > /var/www/html/index.html
sudo chown -R apache:apache /var/www/html

Step 4: Request the certificate

Run Certbot with the Apache plugin:

sudo certbot --apache -d example.com -d www.example.com

To run it non-interactively:

sudo certbot --apache -d example.com -d www.example.com \
  --agree-tos -m webmaster@example.com -n

On Amazon Linux 2023 and other RPM-based distributions, Certbot typically creates a separate SSL configuration file instead of rewriting the original vhost in place:

/etc/httpd/conf.d/example.com-le-ssl.conf

That file contains the :443 virtual host and points to the issued certificate files under /etc/letsencrypt/live/example.com/.

Review it if needed, then restart Apache:

cat /etc/httpd/conf.d/example.com-le-ssl.conf
sudo systemctl restart httpd
sudo systemctl status httpd

Step 5: Disable unused default configs (optional)

This step is not required, but it helps reduce clutter and removes default Apache configurations that may not be needed.

cd /etc/httpd/conf.d/
sudo mv userdir.conf userdir.conf.disabled
sudo mv welcome.conf welcome.conf.disabled
sudo mv autoindex.conf autoindex.conf.disabled
sudo apachectl configtest
sudo systemctl reload httpd

Renaming works because Apache only loads files matching conf.d/*.conf.


Step 6: Verify certificate and renewal settings

List the installed certificates:

certbot certificates

Inspect the renewal configuration:

cat /etc/letsencrypt/renewal/example.com.conf

Look for this line:

installer = apache

If installer = apache is present, Apache should be reloaded automatically after renewal. If it shows installer = None, add a deploy hook to reload Apache as part of the renewal process.


Step 7: Configure automatic renewal with systemd

A pip-based Certbot install does not include a prebuilt renewal timer, so create one manually.

Create the service file:

/etc/systemd/system/certbot-renew.service

[Unit]
Description=Certbot Renewal

[Service]
Type=oneshot
ExecStart=/opt/certbot/bin/certbot renew --quiet

Create the timer file:

/etc/systemd/system/certbot-renew.timer

[Unit]
Description=Run certbot renew twice daily

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target

Enable the timer:

sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer

Verify that it is scheduled and active:

sudo systemctl list-timers | grep certbot
sudo systemctl status certbot-renew.timer

Test the renewal process without issuing a real renewal:

sudo /opt/certbot/bin/certbot renew --dry-run

Common pitfalls

  • Do not pre-create the SSL virtual host. Let Certbot generate the :443 configuration.
  • Without an Elastic IP, a changed public IP can break both DNS and future certificate renewals.

Command summary

sudo dnf install httpd mod_ssl -y
sudo systemctl enable --now httpd

sudo dnf install python3 python3-pip augeas-libs -y
sudo dnf install python3-devel gcc libxml2-devel augeas-devel pkgconf-pkg-config -y

sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-apache

sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot

# Create /etc/httpd/conf.d/example.com.conf with port 80 only
sudo apachectl configtest
sudo systemctl restart httpd

# Add test content
echo "Welcome to example.com" > /var/www/html/index.html
sudo chown -R apache:apache /var/www/html

# Request certificate
sudo certbot --apache -d example.com -d www.example.com

# Optional cleanup
sudo mv userdir.conf userdir.conf.disabled
sudo mv welcome.conf welcome.conf.disabled
sudo mv autoindex.conf autoindex.conf.disabled
sudo apachectl configtest
sudo systemctl reload httpd

# Create certbot-renew.service and certbot-renew.timer
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer
sudo /opt/certbot/bin/certbot renew --dry-run