Zertifikate von Let’s Encrypt verwenden

Categories: administration

Installing acme-tiny

My favorite Let's Encrypt clients is acme-tiny. It is just a very short Python script, which makes a code review easy. Som preparations must be done with OpenSSL. acme-tiny does not require root privileges. It just works. The user lets has to exist on an OpenBSD system with an nginx web server for the following description.

Creating the directoy structure

Create the following directories in the home directory of lets.

mkdir bin
mkdir src
mkdir reqs
mkdir certs

Cloning the repository

There may be updates of the script in the future. To facilitate this we are cloning the Github repository into the src directory.

cd src
git clone https://github.com/diafygi/acme-tiny.git

Now you should always review the source code of the script. If the code review is showing no issue, we are copying the script into the bin directory.

cp src/acme-tiny/acme_tiny.py bin
chmod +x bin/acme_tiny.py

Creating a private key for the Let's Encrypt account

You will need a key pair for the account. The following command will generate a private and a public key for your account. The private key should only be readable by the user lets.

openssl genrsa 4096 > account.key
chmod 400 account.key

Creating a certificate signing request (CSR)

You must create a a domain key pair and certificate signing request for each domain. The certificate signing request contains the public key for the domain. It will be send to Let's Encrypt for signing.

openssl genrsa 4096 > example.key
openssl req -new -sha256 -key example.key -subj "/CN=example.com" > ./requests/example.csr

Only the server process will require the private key from now on. I move it as user root (sudo/doas) into the /etc/ssl/private directory, where only root can access it.

It is also possible to request one certificate for several domains, but I do not use this at the moment.

Configuring the challenges directory

Let's Encrypt must verify the you have control over the requested domain. This is done by checking a special file that the acme-tiny client puts on the web server at http://domain/.well-known/acme-challenge/....

server {
    listen 80;
    server_name example.com;

    location /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        try_files $uri =404;


This step kept me for quite some time. The reverse proxy did search in the wrong directory, but the configuration seemed to be quite in order. I had to restructure my nginx configuration files. Now it is working like a charm. The lesson learned is: Do not overoptimize configuration files.

Now we have completed the preparations for the certificate requests.

Requesting a certificate

The following command request the certificate for the example domain.

python acme_tiny.py --account-key ./account.key --csr ~/requests/example.csr --acme-dir /var/www/challenges/ > ~/certs/example.crt

Normally a server requires a certificate chain. The certificate chain is a file with the concatenation of the domain and the intermediate and root certificate of the certification authority. The following steps create this file.

curl -fsS https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem -o ~/certs/intermediate.pem
cat ~/certs/example.crt intermediate.pem > /etc/ssl/lets/example-chained.pem

This shows the trick to separate the privileges. The domain's private key (example.key), which is only required during creation of the certificate request and aftewards by the server, is stored in a directory only accessible by root. The directory with the certificates (/etc/ssl/lets/) is owned by lets who has only normal user privileges. Everybody has readonly access.

Certificate renewal

Since the certificate is only valid for 90 days, you must request a new one before expiration. You may use the same call of acme-tiny.py like shown above. Since we are running OpenBSD it is advisable to write a script and run it using cron. The following script works with OpenBSD for other unix like operating systems you have to adapt the script. Let's call it renew_cert.sh.

# Renew certificates with Let's Encrypt

alias acme_tiny="$base/bin/acme_tiny.py"


echo "Downloading intermediate certificate..."
# get intermediate certificate with curl
# -f: fail without writing html garbage to output file
# -s: silent mode without progress meter
# -S: output error message if download fails
curl -fsS $intermediate_cert_url -o $intermediate_cert && echo "Success."

# The script continues even if the download of the intermediate
# certificate fails. The old intermediate certificate should still
# exist and be useable.

for csr in $reqsdir/*.csr; do
  domain=`basename $csr .csr`
  echo "Requesting certificate for $domain..."
  acme_tiny --account-key $accountkey --csr $csr --acme-dir $challengedir > $certsdir/$domain.crt || (echo "Failed!" ; exit)
  echo "Success."
  cat $certsdir/$domain.crt $intermediate_cert >$chaineddir/$domain-chained.pem

echo "Reloading web server configuration..."
doas /usr/sbin/rcctl reload nginx || (echo "Failed!"; exit)
echo "Success."

After renewal the web server must load its configurtion again. The user lets should do this after renewing the certificate, but he needs the privileges for this. The can be done with a simple line in the doas configuration file /etc/doas.conf. The user lets may now reload and only reload the web server configuration. For other operating systems you may achieve similar effects with the sudo configuration.

permit nopass lets as root cmd /usr/sbin/rcctl args reload nginx

cron will now request a new certificate each month with the following crontab. I like it when I receive mail from my script each time it runs.

0 0 1 * * $HOME/bin/renew_certs.sh