ViaThinkSoft CodeLib
This article is in:
CodeLib → How-Tos → Apache
Revision: 27 April 2019
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. Create the following directories:
2. Install and enable the required Apache2 modules by executing following commands:
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".
4. Activate the configuration file by creating a symlink:
5. Add OCSP-Stapling to Apache
Edit /etc/apache2/mods-enabled/ssl.conf and add following at the end:
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:
7. Restarting of Apache2:
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:
9. Create a cronjob for the user root, which renews the certificates each month:
add following line:
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:
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
1. Create the directories /data/ssl/letsencrypt/website1/ and /data/ssl/letsencrypt/website1/old/
2. Create /data/ssl/letsencrypt/website1/openssl.cnf with following contents and insert the proper domain name.
3. Create /data/ssl/letsencrypt/website1/config with following contents and include your email address:
4. Create /data/ssl/letsencrypt/website1/renew.sh and give it execution permissions with chmod +x renew.sh . It should have following contents:
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:
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" 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!
Tested with
- Debian Stretch (amd64)
- Raspberry Pi 3 "Raspbian" (armv7l)
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. Create the following directories:
sudo mkdir /data
sudo mkdir /data/ssl
sudo mkdir /data/ssl/letsencrypt
2. Install and enable the required Apache2 modules by executing following commands:
sudo aptitude update
sudo aptitude install libapache2-mod-macro
sudo a2enmod macro
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod ssl
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>
4. Activate the configuration file by creating a symlink:
cd /etc/apache2/sites-enabled/
ln -s ../sites-available/000--macros.conf
5. Add OCSP-Stapling to Apache
Edit /etc/apache2/mods-enabled/ssl.conf and add following at the end:
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:/var/run/ocsp(128000)
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/
7. Restarting of Apache2:
sudo service apache2 restart
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" )
"$DIR"/website1/renew.sh
"$DIR"/website2/renew.sh
"$DIR"/website3/renew.sh
...
# In case you are using your certificates for other serivces, please un-comment these lines by removing the "#"
#service vsftpd restart
#service postfix restart
#service cyrus-imapd restart
#service icinga2 stop
#service webmin stop
service apache2 stop
service mysql stop
service mysql start
service apache2 start
#service webmin start
#service icinga2 start
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
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
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
1. Create the directories /data/ssl/letsencrypt/website1/ and /data/ssl/letsencrypt/website1/old/
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
...
3. Create /data/ssl/letsencrypt/website1/config with following contents and include your email address:
EMAIL="..."
RSASIZE=4096
SERVER="https://acme-v02.api.letsencrypt.org/directory"
# Note: staging still uses ACMEv1
#SERVER="https://acme-staging.api.letsencrypt.org/directory"
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
DIR=$( dirname "$0" )
cd "$DIR"
if [ ! -f openssl.cnf ]; then
echo "Please run the script in the correct directory." >&2
exit 2
fi
. config
# --- Clean up
rm *_pkcs12.p12 2> /dev/null
rm *_private.key 2> /dev/null
rm *_cert.pem 2> /dev/null
rm *_chain.pem 2> /dev/null
rm *_req.csr 2> /dev/null
rm certbot.log 2> /dev/null
# --- Create private key
openssl genrsa -out 0000_priv.key $RSASIZE
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
$EX certonly \
--authenticator standalone \
--preferred-challenges http-01 --http-01-port 999 \
--server $SERVER \
--text \
--email $EMAIL \
--must-staple \
--staple-ocsp \
--csr 0000_req.csr
if [ $? -ne 0 ]; then
echo "CERTBOT FAILED" >&2
exit 1
fi
# --- Security check: check if certificate and private key are matching
a=$( openssl x509 -noout -modulus -in 0000_cert.pem | openssl sha256 )
b=$( openssl rsa -noout -modulus -in 0000_priv.key | openssl sha256 )
if [ "$a" != "$b" ]
then
echo "Error: Certificate does not match private key!" >&2
exit 1
fi
# --- Create PKCS#12
openssl pkcs12 -export -in 0000_cert.pem -inkey 0000_priv.key -certfile 0000_chain.pem -out 0000_pkcs12.p12 -passout pass:
if [ $? -ne 0 ]
then
echo "FEHLER bei PCKS#12-Erstellung!" >&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: Could not create PKCS#12 file!" >&2
exit 1
fi
chmod 600 0000_pkcs12.p12
# --- 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 0000_req.csr
rm certbot.log 2> /dev/null
rm 0001_chain.pem
# --- Additional security check: X509 Lint
if [ -f "../_x509lint/x509lint" ]; then
../_x509lint/x509lint certificate.pem
fi
# --- Delete expired archived certificates
FILES=old/*.p12
for f in $FILES
do
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"
fi
done
# --- Post create: Restart servers etc.
if [ -f postcreate.sh ]; then
./postcreate.sh
fi
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."
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" 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!
Tested with
- Debian Stretch (amd64)
- Raspberry Pi 3 "Raspbian" (armv7l)
Daniel Marschall
ViaThinkSoft Co-Founder
ViaThinkSoft Co-Founder