Key points
- A Bash script that installs Apache and Certbot and gets a wildcard Let’s Encrypt certificate through the DNS-01 challenge.
- A certificate obtained with
certbot --manualcannot renew by itself, and Let’s Encrypt no longer sends expiry emails. - For real automatic renewal use a Certbot DNS plugin for your DNS provider, or the Apache plugin if you do not need a wildcard.
Automate Apache with Let’s Encrypt SSL in Minutes
Updated October 2026: One correction first: a Let’s Encrypt SSL certificate obtained with certbot --manual, as this script does, cannot renew by itself. The cron job runs certbot renew, but renewing a manual DNS-01 certificate needs a --manual-auth-hook script, so without one the wildcard certificate simply expires. Let’s Encrypt also stopped sending expiry reminder emails in June 2025, so nothing warns you.
For real automatic renewal, use a Certbot DNS plugin for your DNS provider (Cloudflare below; Debian packages plugins for several providers), or, if you do not need a wildcard, the Apache plugin with the HTTP challenge. The Debian certbot package already installs a certbot.timer that runs the renewals, so the extra cron job is not needed. The random TXT value the script prints is only a propagation test; the value Let’s Encrypt checks is the one Certbot shows during the run.
# wildcard certificate that renews by itself (Cloudflare DNS as the example)
# /root/.secrets/cloudflare.ini holds one line: dns_cloudflare_api_token = YOUR_TOKEN (chmod 600)
sudo apt install python3-certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare --dns-cloudflare-credentials /root/.secrets/cloudflare.ini -d example.com -d '*.example.com'
# no wildcard needed: HTTP challenge, Apache is configured for you
sudo certbot --apache -d example.com -d www.example.com
# check that renewal works
sudo certbot renew --dry-run
Securing your website with SSL certificates is essential to protect user data and enhance credibility. This Bash script simplifies the process of setting up Let’s Encrypt SSL certificates for Apache servers on Debian-based systems. Automate the setup, from installing Apache and Certbot to the first wildcard certificate, and read the renewal note above so that it stays valid.
What Is Let’s Encrypt?
Let’s Encrypt is a free, automated Certificate Authority (CA) that enables HTTPS by issuing SSL/TLS certificates. It eliminates manual configuration and ensures your website remains secure with automatic renewals.
Key Features of the Script
- Automates Apache Setup: Installs and configures Apache if it’s missing.
- Installs Certbot: Handles the Let’s Encrypt client setup for certificate management.
- Wildcard SSL Support: Secures your domain and all its subdomains.
- DNS Validation: Uses the DNS-01 challenge; Certbot shows the TXT record to add during the run.
- Renewal Job: Adds a cron job and an Apache reload hook (a manual DNS certificate still needs an auth hook or a DNS plugin to renew; see the note at the top).
How It Works
- Checks Apache and Certbot:
- Ensures Apache is installed and running.
- Installs Certbot for Let’s Encrypt integration.
- Configures Apache:
- Sets up virtual hosts for both HTTP and HTTPS.
- Redirects traffic from HTTP to HTTPS when certificates are available.
- Secures Your Domain:
- Guides you through DNS validation for wildcard SSL certificates.
- Uses Certbot to obtain and install the certificates.
- Sets Up the Renewal Job:
- Creates a cron job that runs
certbot renew(it succeeds only with a DNS plugin or an auth hook; see the note at the top). - Reloads Apache to apply renewed certificates.
- Creates a cron job that runs
Quick Start
- Save and Make Executable: copy the full script at the end of this post into a file named
lets-encrypt-setup.sh, setDOMAINandEMAILat its top, then run:
chmod +x lets-encrypt-setup.sh
- Run the Script:
sudo ./lets-encrypt-setup.sh
- Follow Prompts:
- Add the DNS TXT record that Certbot shows for validation.
- Let the script configure Apache and install certificates automatically.
- Enjoy HTTPS:
- Your domain and subdomains are now secured.
- Set up renewal with a DNS plugin as described at the top, so that the certificate does not expire.
Benefits
- Time-Saving: Automates tedious SSL setup tasks.
- Enhanced Security: Serves the site over HTTPS and redirects HTTP to HTTPS.
- Repeatable: Run it once per domain by changing
DOMAIN. - Reliable: Reduces errors with a streamlined, automated approach.
Conclusion
This Bash script makes securing your website with Let’s Encrypt SSL certificates a breeze. It handles setup and configuration, leaving you with more time to focus on what matters most. Secure your site today and enjoy the benefits of effortless automation. Once HTTPS works, you can put a dashboard such as my Netdata setup behind it.
#!/bin/bash
# =========================================
# Let's Encrypt Setup Script
# =========================================
#
# Version: 1.0.0
# Author: Warith AL Maawali
#
# Contact:
# - Discord: https://discord.gg/KEFErEx
# - Twitter: https://x.com/warith2020
# - LinkedIn: https://www.linkedin.com/in/warith1977
# - Website: https://www.digi77.com
#
# Copyright (c) 2024 Warith AL Maawali
#
# Description:
# This script automates the setup and configuration of Let's Encrypt SSL certificates
# for Apache web servers on Kodachi VPS instances. It performs the following tasks:
# - Installs and configures Apache web server
# - Installs Certbot and its dependencies
# - Obtains and configures SSL certificates
# - Sets up automatic certificate renewal
# - Configures Apache virtual hosts
#
# License:
# Dual-licensed under:
# 1. Apache License 2.0 (personal, non-commercial use)
# 2. Commercial license (for corporate/organizational use)
# Contact author for commercial licensing details.
#
# Usage:
# sudo ./lets-encrypt-setup.sh
#
# Requirements:
# - Debian-based Linux distribution
# - Root/sudo privileges
# - Active internet connection
# - Domain name with DNS access
# - Apache web server
# =========================================
# Define script variables
DOMAIN="yourdomain.com" # Domain name to secure
EMAIL="youremail@gmail.com" # Contact email for Let's Encrypt
CLOUDFLARE_DNS="1.1.1.1" # Primary DNS resolver
GOOGLE_DNS="8.8.8.8" # Backup DNS resolver
CERTBOT_PATH="/usr/bin/certbot" # Path to certbot executable
APACHE_CONFIG="/etc/apache2/sites-available/${DOMAIN}.conf" # Apache vhost config location
RENEW_HOOK="/etc/letsencrypt/renewal-hooks/deploy/reload-apache.sh" # Renewal hook script path
CRON_JOB="/etc/cron.d/letsencrypt-renew" # Auto-renewal cron job path
CERT_PATH="/etc/letsencrypt/live/$DOMAIN" # SSL certificate storage path
VALIDATION_STRING="" # DNS validation string
# Display current DNS TXT records for domain validation
echo "Current TXT records for _acme-challenge.$DOMAIN:"
# Try Cloudflare DNS first, fallback to Google DNS if it fails
dig +short -t TXT "_acme-challenge.$DOMAIN" @$CLOUDFLARE_DNS ||
dig +short -t TXT "_acme-challenge.$DOMAIN" @$GOOGLE_DNS ||
echo "No TXT records found"
echo "----------------------------------------"
# Function to verify and install Apache if needed
check_apache() {
if ! [ -x "$(command -v apache2)" ]; then
echo "Apache is not installed. Installing it now..."
apt update
apt install -y apache2
systemctl enable apache2
systemctl start apache2
else
echo "Apache is already installed."
fi
}
# Function to install Certbot and its Apache plugin
install_certbot() {
echo "Installing Certbot and dependencies..."
apt update
apt install -y certbot python3-certbot-apache
if [ $? -eq 0 ]; then
echo "Certbot successfully installed."
else
echo "Error installing Certbot. Please check your system configuration."
exit 1
fi
}
# Function to verify Certbot installation
check_certbot_installed() {
if ! [ -x "$(command -v $CERTBOT_PATH)" ]; then
echo "Certbot is not installed. Installing it now..."
install_certbot
else
echo "Certbot is already installed."
fi
}
# Function to configure Apache virtual host with SSL support
configure_apache() {
# Check if SSL certificates exist
if [ -d "$CERT_PATH" ] && [ -f "$CERT_PATH/fullchain.pem" ] && [ -f "$CERT_PATH/privkey.pem" ]; then
# Configure HTTPS with SSL certificates
echo "SSL certificates found, configuring Apache with HTTPS..."
cat >"$APACHE_CONFIG" <<EOF
<VirtualHost *:80>
ServerName $DOMAIN
ServerAlias *.$DOMAIN
DocumentRoot /var/www/html
ErrorLog \${APACHE_LOG_DIR}/error.log
CustomLog \${APACHE_LOG_DIR}/access.log combined
# Redirect all HTTP traffic to HTTPS
Redirect permanent / https://$DOMAIN/
</VirtualHost>
<VirtualHost *:443>
ServerName $DOMAIN
ServerAlias *.$DOMAIN
DocumentRoot /var/www/html
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/$DOMAIN/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/$DOMAIN/privkey.pem
# Logging Configuration
ErrorLog \${APACHE_LOG_DIR}/error.log
CustomLog \${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
EOF
a2ensite "${DOMAIN}.conf"
a2enmod ssl
else
# Configure HTTP-only virtual host
echo "SSL certificates not found, configuring Apache for HTTP only..."
cat >"$APACHE_CONFIG" <<EOF
<VirtualHost *:80>
ServerName $DOMAIN
ServerAlias *.$DOMAIN
DocumentRoot /var/www/html
ErrorLog \${APACHE_LOG_DIR}/error.log
CustomLog \${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
EOF
a2ensite "${DOMAIN}.conf"
fi
# Validate and reload Apache configuration
if apachectl configtest; then
systemctl reload apache2
echo "Apache configuration updated successfully"
else
echo "Error in Apache configuration. Please check the syntax."
exit 1
fi
}
# Function to generate DNS validation string
generate_validation() {
VALIDATION_STRING=$(openssl rand -hex 32)
echo "Please add this DNS TXT record:"
echo "Record Name: _acme-challenge.$DOMAIN"
echo "Record Value: $VALIDATION_STRING"
}
# Function to verify DNS record propagation
verify_dns_record() {
local attempt=1
local max_attempts=12 # 2 minute timeout (12 attempts * 10 seconds)
while [ $attempt -le $max_attempts ]; do
# Check both Cloudflare and Google DNS for record propagation
if dig +short -t TXT "_acme-challenge.$DOMAIN" @$CLOUDFLARE_DNS | grep -q "$VALIDATION_STRING" ||
dig +short -t TXT "_acme-challenge.$DOMAIN" @$GOOGLE_DNS | grep -q "$VALIDATION_STRING"; then
echo "DNS record verified successfully!"
return 0
fi
echo "Attempt $attempt of $max_attempts: DNS record not found yet. Waiting 10 seconds..."
echo "Found TXT record (Cloudflare): $(dig +short -t TXT "_acme-challenge.$DOMAIN" @$CLOUDFLARE_DNS)"
echo "Found TXT record (Google): $(dig +short -t TXT "_acme-challenge.$DOMAIN" @$GOOGLE_DNS)"
echo "Expected TXT record: $VALIDATION_STRING"
sleep 10
attempt=$((attempt + 1))
done
echo "Could not verify DNS record after $max_attempts attempts."
return 1
}
# Function to obtain SSL certificate using DNS validation
obtain_certificate() {
echo "Starting DNS validation process for $DOMAIN..."
# Generate and verify DNS record
generate_validation
# Wait for user to add DNS record and verify
while true; do
read -p "Have you added the DNS TXT record? (yes/no): " response
if [ "$response" = "yes" ]; then
if verify_dns_record; then
break
fi
elif [ "$response" = "no" ]; then
echo "Please add the DNS record and try again."
else
echo "Please answer 'yes' or 'no'"
fi
done
# Request certificate from Let's Encrypt
echo "DNS verified! Running certbot to obtain certificate..."
$CERTBOT_PATH certonly \
--manual \
--preferred-challenges dns-01 \
--agree-tos \
--email "$EMAIL" \
--domains "$DOMAIN,*.${DOMAIN}" \
--manual-public-ip-logging-ok \
--force-interactive
# Verify certificate creation and configure Apache
if [ -d "$CERT_PATH" ]; then
echo "Wildcard certificate successfully obtained."
configure_apache
else
echo "Error obtaining certificate. Check logs for details."
exit 1
fi
}
# Function to configure automatic certificate renewal
setup_auto_renew() {
echo "Setting up auto-renewal..."
# Create renewal hook directory and script
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat >"$RENEW_HOOK" <<EOF
#!/bin/bash
systemctl reload apache2
EOF
chmod +x "$RENEW_HOOK"
# Configure cron job for automatic renewal
echo "0 3 * * * root $CERTBOT_PATH renew --deploy-hook $RENEW_HOOK" >$CRON_JOB
chmod 644 $CRON_JOB
echo "Auto-renewal configured."
}
# Main script execution
echo "Starting Let's Encrypt wildcard certificate setup for $DOMAIN..."
check_apache
check_certbot_installed
configure_apache
echo "Obtaining Let's Encrypt wildcard SSL certificate for $DOMAIN..."
echo "Saving debug log to /var/log/letsencrypt/letsencrypt.log"
echo "Requesting a certificate for $DOMAIN and *.$DOMAIN"
obtain_certificate
setup_auto_renew
# Display completion message and verify renewal configuration
echo "Setup complete! Your domain $DOMAIN and all subdomains are now secured with Let's Encrypt SSL."
echo "Wildcard certificate will auto-renew before expiration."
echo -e "\nVerifying auto-renewal configuration:"
if [ -f "$CRON_JOB" ]; then
echo "Auto-renewal cron job is configured as follows:"
cat "$CRON_JOB"
else
echo "Warning: Auto-renewal cron job file not found at $CRON_JOB"
fi
ShellScript