ViaThinkSoft CodeLib
This article is in:
CodeLib → How-Tos → Apache
Revision: 20 November 2024
This example shows, how you can implement a custom automatic which renews Let's Encrypt certificates for your website. The custom automatic may be useful in case you don't trust an 100% automatic and are concerned that complex configurations might break, or in case you just want to have more control about the renewals.
With this custom automatic, it is easy to let your other services (MySQL, FTP, IMAP, SMTP etc) use the same certificates as your HTTP service.
This tutorial requires that you have an already configured Apache installation and have some basic knowledge about SSL. This tutorial especially is for webmasters who want to change to Let's Encrypt or begin using HTTPS.
In our example, we want to save our SSL relevant data in /data/ssl/letsencrypt . All directory names in this tutorial are only examples, of course and should be adapted to your individual machine configuration.
Step 1 (only required once): Installing of Certbot, Apache and the Cronjob
1.1. Create the following directories:
1.2. Install and enable the required Apache2 modules by executing following commands:
1.3. Now we are creating some macros in Apache. Please create /etc/apache2/sites-available/000--macros.conf
Attention: The file name contains two hyphens, because "000--macros.conf" must be loaded/sorted before "000-default.conf".
1.4. Activate the configuration file by creating a symlink:
1.5. Add OCSP-Stapling to Apache
Edit /etc/apache2/mods-enabled/ssl.conf and add following at the end:
1.6. Add the following line to each <VirtualHost> block in your website configuration files (/etc/apache2/sites-available/*.conf) :
In case the domain validation fails in the later procedure, the reason might be a Rewrite-Rule. In this case, you have to add following line to the Rewrite-block:
1.7. Restarting of Apache2:
1.8. Create the script /data/ssl/letsencrypt/renew-all.sh and give execution-permissions via chmod +x renew-all.sh . The contents of the file should be:
1.9. Create a cronjob for the user root, which renews the certificates each month:
add following line:
1.10. Installing of Certbot:
Please execute following commands:
In case the package "certbot" is not available in your Linux distribution, you can execute following commands:
1.11. Setup of a Linux user:
Step 2 (perform for each of your websites): Creation of the scripts for your new website:
In this example, we will call the website "website1" with the domains domain1.com and domain2.com
2.1. Create the directories /data/ssl/letsencrypt/website1/ and /data/ssl/letsencrypt/website1/old/
2.2. Create /data/ssl/letsencrypt/website1/openssl.cnf with following contents and insert the proper domain name.
2.3. Create /data/ssl/letsencrypt/website1/config with following contents and include your email address:
2.4. Create /data/ssl/letsencrypt/website1/renew.sh and give it execution permissions with chmod +x renew.sh . It should have following contents:
2.5. (Optional step) Create the following script /data/ssl/letsencrypt/website1/recover_cert.sh which can be used in emergency to recover a certificate together with its private key. Give it execution permissions with chmod +x recover_cert.sh and add the following content:
2.6. Edit the configurations in /etc/apache2/sites-available/website1.conf .
In case you only have one <VirtualHost> block (with port 80), duplicate the block so you have one block for port 80 (HTTP) and one block for port 443 (HTTPS).
In the HTTPS block, insert following line to activate the Let's Encrypt certificates (in case you have more than one port 443 block, add the line to the other port 443 blocks as well):
Step 3: Testing
Execute /data/ssl/letsencrypt/renew-all.sh the first time (this time only manual) and follow the instructions. Also note if there are any error messages.
Usually, you need to accept the rules once, and you will be asked if you want to be added to the EFF mailing list.
To receive certificates by a Test Certificate Authority first (not trusted in browsers!), change "acme-v02" to "acme-staging-v02" in your config files. This will prevent creation of unnecessary certificates.
Troubleshooting
In case you receive a timeout during domain validation, although your website is reachable from outside, there might be the case that your domain has an IPv6 record (AAAA) but your server does not accept IPv6. Note that the Let's Encrypt bot prefers IPv6 connections if there is an AAAA DNS record!
If Apache does not start, you can try one of these commands to find the error message:
This example shows, how you can implement a custom automatic which renews Let's Encrypt certificates for your website. The custom automatic may be useful in case you don't trust an 100% automatic and are concerned that complex configurations might break, or in case you just want to have more control about the renewals.
With this custom automatic, it is easy to let your other services (MySQL, FTP, IMAP, SMTP etc) use the same certificates as your HTTP service.
This tutorial requires that you have an already configured Apache installation and have some basic knowledge about SSL. This tutorial especially is for webmasters who want to change to Let's Encrypt or begin using HTTPS.
In our example, we want to save our SSL relevant data in /data/ssl/letsencrypt . All directory names in this tutorial are only examples, of course and should be adapted to your individual machine configuration.
Step 1 (only required once): Installing of Certbot, Apache and the Cronjob
1.1. Create the following directories:
sudo mkdir /data
sudo mkdir /data/ssl
sudo mkdir /data/ssl/letsencrypt
1.2. Install and enable the required Apache2 modules by executing following commands:
sudo a2enmod macro
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod ssl
1.3. Now we are creating some macros in Apache. Please create /etc/apache2/sites-available/000--macros.conf
Attention: The file name contains two hyphens, because "000--macros.conf" must be loaded/sorted before "000-default.conf".
<Macro LetsEncryptProxy>
<IfModule mod_proxy.c>
ProxyPass "/.well-known/acme-challenge/" "http://127.0.0.1:999/.well-known/acme-challenge/" retry=1
ProxyPassReverse "/.well-known/acme-challenge/" "http://127.0.0.1:999/.well-known/acme-challenge/"
<Location "/.well-known/acme-challenge/">
ProxyPreserveHost On
Order allow,deny
Allow from all
Require all granted
</Location>
</IfModule>
</Macro>
<Macro LetsEncryptSSL $sitedirname $ssl_log>
SSLEngine on
SSLCertificateFile "/data/ssl/letsencrypt/$sitedirname/certificate.pem"
SSLCertificateKeyFile "/data/ssl/letsencrypt/$sitedirname/private.key"
SSLCertificateChainFile "/data/ssl/letsencrypt/$sitedirname/intermediate_ca.pem"
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
CustomLog "$ssl_log" "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</Macro>
1.4. Activate the configuration file by creating a symlink:
cd /etc/apache2/sites-enabled/
ln -s ../sites-available/000--macros.conf
1.5. Add OCSP-Stapling to Apache
Edit /etc/apache2/mods-enabled/ssl.conf and add following at the end:
SSLUseStapling on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
1.6. Add the following line to each <VirtualHost> block in your website configuration files (/etc/apache2/sites-available/*.conf) :
Use LetsEncryptProxy
In case the domain validation fails in the later procedure, the reason might be a Rewrite-Rule. In this case, you have to add following line to the Rewrite-block:
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/
1.7. Restarting of Apache2:
sudo service apache2 restart
1.8. Create the script /data/ssl/letsencrypt/renew-all.sh and give execution-permissions via chmod +x renew-all.sh . The contents of the file should be:
#!/bin/bash
DIR=$( dirname "$0" )
TOTALRES=0
for subdir in "$DIR"/*/; do
if [ -f "${subdir}renew.sh" ]; then
"${subdir}renew.sh"
if [ $? -ne 0 ]; then
TOTALRES=1
fi
sleep 1
fi
done
service apache2 restart
# In case you are using your certificates for other services, please un-comment these lines by removing the "#"
#service vsftpd restart
#service postfix restart
#service cyrus-imapd restart
#service mysql restart
# ...
exit $TOTALRES
1.9. Create a cronjob for the user root, which renews the certificates each month:
sudo crontab -e
add following line:
0 0 1 * * /data/ssl/letsencrypt/renew-all.sh
1.10. Installing of Certbot:
Please execute following commands:
sudo aptitude update
sudo aptitude install certbot
In case the package "certbot" is not available in your Linux distribution, you can execute following commands:
sudo aptitude update
sudo aptitude install git
cd /data/ssl/letsencrypt/
git clone https://github.com/letsencrypt/letsencrypt
mv letsencrypt _certbot
1.11. Setup of a Linux user:
groupadd ssl
usermod -a -G ssl www-data
chown -R root:ssl /data/ssl/
Step 2 (perform for each of your websites): Creation of the scripts for your new website:
In this example, we will call the website "website1" with the domains domain1.com and domain2.com
2.1. Create the directories /data/ssl/letsencrypt/website1/ and /data/ssl/letsencrypt/website1/old/
2.2. Create /data/ssl/letsencrypt/website1/openssl.cnf with following contents and insert the proper domain name.
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
CN = www.domain1.com
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
# Extention "Must Staple"
# Remove this line if you want to use the certificate with services that do not support OCSP-Must-Staple (e.g. Postfix)
1.3.6.1.5.5.7.1.24 = DER:30:03:02:01:05
[alt_names]
DNS.1 = domain1.com
DNS.2 = www.domain2.com
DNS.3 = domain2.com
...
2.3. Create /data/ssl/letsencrypt/website1/config with following contents and include your email address:
EMAIL="..."
ECCURVE=secp384r1
#RSASIZE=4096
SERVER="https://acme-v02.api.letsencrypt.org/directory"
#SERVER="https://acme-staging-v02.api.letsencrypt.org/directory"
## If WILDCARD_APITOKEN is missing, reverse proxy method will be used, otherwise DNS method.
## Wildcard DNS method requires the scripts wildcard_authenticator.sh and wildcard_cleanup.sh
## which must be prepared for your domain provider
#WILDCARD_APITOKEN=""
2.4. Create /data/ssl/letsencrypt/website1/renew.sh and give it execution permissions with chmod +x renew.sh . It should have following contents:
#!/bin/bash
# --- Initialization
SELF_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P)
DIR=$( dirname "$0" )
cd "$DIR"
if [ ! -f openssl.cnf ]; then
echo "Please run the script in the correct directory." >&2
exit 2
fi
if [ ! -d "old/" ]; then
mkdir old
fi
. config
# --- Clean up
rm -f *_pkcs12.p12 2> /dev/null
rm -f *_private.key 2> /dev/null
rm -f *_cert.pem 2> /dev/null
rm -f *_chain.pem 2> /dev/null
rm -f *_req.csr 2> /dev/null
rm -f certbot.log 2> /dev/null
rm -f letsencrypt.log 2> /dev/null
# --- Create private key
if [ "$ECCURVE" != "" ]; then
openssl ecparam -name "$ECCURVE" -genkey -out 0000_priv.key
else
openssl genrsa -out 0000_priv.key $RSASIZE
fi
if [ $? -ne 0 ]; then
echo "FAILED TO CREATE PRIVATE KEY" >&2
exit 1
fi
chown root:ssl 0000_priv.key
chmod 640 0000_priv.key
# --- Create certificate request
openssl req -new -batch -sha256 \
-key 0000_priv.key \
-config openssl.cnf \
-out 0000_req.csr
if [ $? -ne 0 ]; then
echo "FAILED TO CREATE CERTIFICATE REQUEST" >&2
exit 1
fi
# --- Ask server to sign the certificate
#if [ -f ../_certbot/certbot-auto ]; then
# EX="../_certbot/certbot-auto"
#else
EX="certbot"
#fi
if [ "$WILDCARD_APITOKEN" == "" ]; then
$EX certonly \
--authenticator standalone \
--preferred-challenges http-01 --http-01-port 999 \
--server $SERVER \
--text \
--email $EMAIL \
--agree-tos \
--must-staple \
--staple-ocsp \
--csr 0000_req.csr
RES=$?
else
$EX certonly \
--manual --manual-auth-hook "$SELF_PATH/wildcard_authenticator.sh" --manual-cleanup-hook "$SELF_PATH/wildcard_cleanup.sh" \
--preferred-challenges dns \
--server $SERVER \
--text \
--email $EMAIL \
--agree-tos \
--must-staple \
--staple-ocsp \
--csr 0000_req.csr
RES=$?
fi
if [ -f /var/log/letsencrypt/letsencrypt.log ]; then
cp /var/log/letsencrypt/letsencrypt.log letsencrypt.log
fi
if [ $RES -ne 0 ]; then
echo "CERTBOT FAILED WITH EXIT CODE $RES ($DIR)" >&2
exit 1
fi
# --- Security check: check if certificate and private key are matching
if [ "$ECCURVE" != "" ]; then
# Extract the public key from the certificate
a=$( openssl x509 -in 0000_cert.pem -pubkey -noout 2>/dev/null | openssl ec -pubin -outform der 2>/dev/null | openssl dgst -sha256 )
# Derive the public key from the private key
b=$( openssl ec -in 0000_priv.key -pubout -outform der 2>/dev/null | openssl dgst -sha256 )
else
a=$( openssl x509 -noout -modulus -in 0000_cert.pem | openssl sha256 )
b=$( openssl rsa -noout -modulus -in 0000_priv.key | openssl sha256 )
fi
if [ "$a" != "$b" ]
then
echo "ERROR: certificate public key does not match private key!" >&2
exit 1
fi
# --- PKCS#12 erstellen
# TODO: add $PREVDIR to the name
openssl pkcs12 -export -in 0000_cert.pem -inkey 0000_priv.key -certfile 0000_chain.pem -name "Server Certificate" -out 0000_pkcs12.p12 -passout pass:
if [ $? -ne 0 ]
then
echo "ERROR while PCKS#12 creation!" >&2
if [ -f 0000_pkcs12.p12 ]
then
chmod 600 0000_pkcs12.p12
rm 0000_pkcs12.p12
fi
exit 1
fi
if [ ! -f 0000_pkcs12.p12 ]
then
echo "ERROR! PCKS#12 could not be created!" >&2
exit 1
fi
chmod 600 0000_pkcs12.p12
if [ -f precreate.sh ]; then
./precreate.sh
fi
# --- Activate certs
# Files created by certbot:
# 0000_cert.pem = cert.pem (i.e., the server certificate)
# 0000_chain.pem = chain.pem (i.e., the intermediate certificate)
# 0001_chain.pem = fullchain.pem (i.e., a concatenation of cert.pem + chain.pem in one file).
mv -f 0000_pkcs12.p12 "old/$(date +%s).p12"
mv -f 0000_priv.key private.key
mv -f 0000_cert.pem certificate.pem
mv -f 0000_chain.pem intermediate_ca.pem
rm -f 0000_req.csr
rm -f certbot.log 2> /dev/null
rm -f 0001_chain.pem
# --- Delete expired archived certificates
FILES=old/*.p12
for f in $FILES
do
# TODO: das ist nicht sauber, denn wenn ein anderer fehler im zertifikat vorliegt, dann würde auch $?=1 sein
openssl pkcs12 -in "$f" -clcerts -nokeys -passin pass: | openssl x509 -noout -checkend 0 > /dev/null
if [ $? -eq 1 ]; then
echo "$f has expired. Deleting."
rm -f "$f"
fi
done
# --- Post create: Restart servers etc.
if [ -f postcreate.sh ]; then
./postcreate.sh
fi
2.5. (Optional step) Create the following script /data/ssl/letsencrypt/website1/recover_cert.sh which can be used in emergency to recover a certificate together with its private key. Give it execution permissions with chmod +x recover_cert.sh and add the following content:
#!/bin/bash
DIR=$( dirname "$0" )
cd "$DIR"
if [ "$1" == "--help" ]; then
echo "Syntax: $0 <p12file>"
exit 2
fi
if [ ! -f "$1" ]; then
echo "ERROR: File '$1' does not exist" >&2
exit 1
fi
openssl pkcs12 -in "$1" -nocerts -out tmp_priv.key -passin pass: -nodes
if [ $? -ne 0 ]; then
echo "ERROR recovering the private key" >&2
rm tmp_priv.key 2> /dev/null
rm tmp_cert.pem 2> /dev/null
rm tmp_ca.pem 2> /dev/null
exit 1
fi
openssl pkcs12 -in "$1" -clcerts -nokeys -out tmp_cert.pem -passin pass:
if [ $? -ne 0 ]; then
echo "ERROR recovering the certificate" >&2
rm tmp_priv.key 2> /dev/null
rm tmp_cert.pem 2> /dev/null
rm tmp_ca.pem 2> /dev/null
exit 1
fi
openssl pkcs12 -in "$1" -cacerts -nokeys -out tmp_ca.pem -passin pass:
if [ $? -ne 0 ]; then
echo "ERROR recovering the intermediate certificate" >&2
rm tmp_priv.key 2> /dev/null
rm tmp_cert.pem 2> /dev/null
rm tmp_ca.pem 2> /dev/null
exit 1
fi
mv -f tmp_priv.key private.key
if [ $? -ne 0 ]; then
echo "ERROR moving the private key" >&2
exit 1
fi
mv -f tmp_cert.pem certificate.pem
if [ $? -ne 0 ]; then
echo "ERROR moving the certificate" >&2
exit 1
fi
mv -f tmp_ca.pem intermediate_ca.pem
if [ $? -ne 0 ]; then
echo "ERROR moving the intermediate certificate" >&2
exit 1
fi
echo "Certificate $1 recovered."
2.6. Edit the configurations in /etc/apache2/sites-available/website1.conf .
In case you only have one <VirtualHost> block (with port 80), duplicate the block so you have one block for port 80 (HTTP) and one block for port 443 (HTTPS).
In the HTTPS block, insert following line to activate the Let's Encrypt certificates (in case you have more than one port 443 block, add the line to the other port 443 blocks as well):
Use LetsEncryptSSL website1 /var/log/.../website1/ssl_request.log
Step 3: Testing
Execute /data/ssl/letsencrypt/renew-all.sh the first time (this time only manual) and follow the instructions. Also note if there are any error messages.
Usually, you need to accept the rules once, and you will be asked if you want to be added to the EFF mailing list.
To receive certificates by a Test Certificate Authority first (not trusted in browsers!), change "acme-v02" to "acme-staging-v02" in your config files. This will prevent creation of unnecessary certificates.
Troubleshooting
In case you receive a timeout during domain validation, although your website is reachable from outside, there might be the case that your domain has an IPv6 record (AAAA) but your server does not accept IPv6. Note that the Let's Encrypt bot prefers IPv6 connections if there is an AAAA DNS record!
If Apache does not start, you can try one of these commands to find the error message:
- apachectl configtest
- journalctl -u apache2.service
- systemctl status apache2.service
- journalctl -xe
Daniel Marschall
ViaThinkSoft Co-Founder
ViaThinkSoft Co-Founder