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
:443configuration. - 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
