2016-05-31 letsencrypt.sh instead

---

I’ve been using the official Let’s Encrypt client, now Certbot on GitHub, using the webroot plugin, but then I saw Andreas Gohr’s blog post Simple Let's Encrypt on Debian/Apache, based on the German post letsencrypt.sh, based on Lukas Schauer’s letsencrypt.sh on GitHub.

now Certbot on GitHub

using the webroot plugin

Simple Let's Encrypt on Debian/Apache

letsencrypt.sh

letsencrypt.sh on GitHub

Setting it up required a bit of fiddling.

I created the so-called BASEDIR `/etc/letsencrypt.sh`, with an empty `domains.txt` file, the suggested `hook.sh` and a `config` (no longer `config.sh`!) as follows:

hook.sh:

#!/bin/bash

if [ ${1} == "deploy_cert" ]; then
    echo " + Hook: Restarting Apache..."
    service apache2 reload
else
    echo " + Hook: Nothing to do..."
fi

config:

BASEDIR="/etc/letsencrypt.sh/"
WELLKNOWN="/var/www/letsencrypt.sh/"
PRIVATE_KEY="${BASEDIR}/private_key.pem"
HOOK="${BASEDIR}/hook.sh"
CONTACT_EMAIL="kensanata@gmail.com"

I also created `/var/www/letsencrypt.sh/`. These are the permissions and ownership I’m looking for:

drwxr-xr-x 2 www-data www-data 4096 May 31 12:50 letsencrypt.sh

I followed the Import from official letsencrypt client instructions. In the bash file, I had to set `BASEDIR="/etc/letsencrypt.sh"` or it wouldn’t work, and I had to run it as root. This created the `certs` subdirectory in my BASEDIR with all the certificates and it populated the `domains.txt` file. I had to look over the list of domains because five out of six domains didn’t have the `www` subdomain I wanted. Probably because the very first time I made those calls I had forgotten about them.

Import from official letsencrypt client

The perl file just required me to `cpanm --sudo Crypt::OpenSSL::RSA` and `cpanm --sudo Crypt::OpenSSL::Bignum`. The result of calling it I piped into `private_key.pem` in my BASEDIR.

For all my sites, I replaced all the `.pem` locations.

Old:

SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/alexschroeder.ch/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/alexschroeder.ch/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/alexschroeder.ch/chain.pem
SSLVerifyClient None

New:

SSLEngine on
SSLCertificateFile /etc/letsencrypt.sh/certs/alexschroeder.ch/cert.pem
SSLCertificateKeyFile /etc/letsencrypt.sh/certs/alexschroeder.ch/privkey.pem
SSLCertificateChainFile /etc/letsencrypt.sh/certs/alexschroeder.ch/chain.pem
SSLVerifyClient None

Do this for all sites in `sites-enabled`.

Don’t forget to place the following in `/etc/apache2/conf.d/letsencrypt`:

Alias /.well-known/acme-challenge /var/www/letsencrypt.sh/

<Directory /var/www/letsencrypt.sh/>
        Options None
        AllowOverride None
        Order allow,deny
        Allow from all
</Directory>

When I ran `~/src/letsencrypt.sh/letsencrypt.sh -c` the first time, I got an error, presumably because I had forgotten to restart Apache and so the challenge wasn’t being served:

alex@kallobombus:~/src/letsencrypt.sh$ sudo ~/src/letsencrypt.sh/letsencrypt.sh -c
1. INFO: Using main config file /etc/letsencrypt.sh/config
Processing alexschroeder.ch with alternative names: www.alexschroeder.ch
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: alexschroeder.ch
 + Configured names: alexschroeder.ch www.alexschroeder.ch
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:16:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for alexschroeder.ch...
 + Requesting challenge for www.alexschroeder.ch...
 + Hook: Nothing to do...
 + Responding to challenge for alexschroeder.ch...
 + Hook: Nothing to do...
ERROR: Challenge is invalid! (returned: invalid) (result: {
...
})

When I tried to `sudo service apache2 reload` I got an error in my config. The problem was that my config was referring to the `chain.pem` but the import script hadn’t copied it over!

I was forced to run something like the following: `cd /etc/letsencrypt.sh/certs; for d in *; do echo $d; cp -i /etc/letsencrypt/live/$d/chain.pem /etc/letsencrypt.sh/certs/$d/chain-1464687792.pem; cd /etc/letsencrypt.sh/certs/$d; ln -s chain-1464687792.pem chain.pem; done` (I had picked the timestamp by looking at the other files).

Finally reloading Apache worked.

Just to be sure, I created a little text file as `/var/www/letsencrypt.sh/test` and tried to read it using `https://alexschroeder.ch/.well-known/acme-challenge/test`. That seemed to work.

And now it worked!

alex@kallobombus:~$ sudo ~/src/letsencrypt.sh/letsencrypt.sh -c
1. INFO: Using main config file /etc/letsencrypt.sh/config
Processing alexschroeder.ch with alternative names: www.alexschroeder.ch
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: alexschroeder.ch
 + Configured names: alexschroeder.ch www.alexschroeder.ch
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:16:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for alexschroeder.ch...
 + Requesting challenge for www.alexschroeder.ch...
 + Hook: Nothing to do...
 + Responding to challenge for alexschroeder.ch...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Hook: Nothing to do...
 + Responding to challenge for www.alexschroeder.ch...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Hook: Restarting Apache...
[ ok ] Reloading web server config: apache2.
 + Done!
Processing arabisch-lernen.org with alternative names: www.arabisch-lernen.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug  6 17:27:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing campaignwiki.org with alternative names: www.campaignwiki.org
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: campaignwiki.org
 + Configured names: campaignwiki.org www.campaignwiki.org
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:17:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for campaignwiki.org...
 + Requesting challenge for www.campaignwiki.org...
 + Hook: Nothing to do...
 + Responding to challenge for campaignwiki.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Hook: Nothing to do...
 + Responding to challenge for www.campaignwiki.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Hook: Restarting Apache...
[ ok ] Reloading web server config: apache2.
 + Done!
Processing communitywiki.org with alternative names: www.communitywiki.org
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: communitywiki.org
 + Configured names: communitywiki.org www.communitywiki.org
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:17:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for communitywiki.org...
 + Requesting challenge for www.communitywiki.org...
 + Hook: Nothing to do...
 + Responding to challenge for communitywiki.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Hook: Nothing to do...
 + Responding to challenge for www.communitywiki.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Hook: Restarting Apache...
[ ok ] Reloading web server config: apache2.
 + Done!
Processing korero.org with alternative names: www.korero.org
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: korero.org
 + Configured names: korero.org www.korero.org
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:18:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for korero.org...
 + Requesting challenge for www.korero.org...
 + Hook: Nothing to do...
 + Responding to challenge for korero.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Hook: Nothing to do...
 + Responding to challenge for www.korero.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Hook: Restarting Apache...
[ ok ] Reloading web server config: apache2.
 + Done!
Processing oddmuse.org with alternative names: www.oddmuse.org
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: oddmuse.org
 + Configured names: oddmuse.org www.oddmuse.org
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:18:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for oddmuse.org...
 + Requesting challenge for www.oddmuse.org...
 + Hook: Nothing to do...
 + Responding to challenge for oddmuse.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Hook: Nothing to do...
 + Responding to challenge for www.oddmuse.org...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Hook: Restarting Apache...
[ ok ] Reloading web server config: apache2.
 + Done!
Processing orientalisch.info with alternative names: www.orientalisch.info
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: orientalisch.info
 + Configured names: orientalisch.info www.orientalisch.info
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till Jun 10 09:18:00 2016 GMT (Less than 30 days). Renewing!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for orientalisch.info...
 + Requesting challenge for www.orientalisch.info...
 + Hook: Nothing to do...
 + Responding to challenge for orientalisch.info...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Hook: Nothing to do...
 + Responding to challenge for www.orientalisch.info...
 + Hook: Nothing to do...
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + Hook: Restarting Apache...
[ ok ] Reloading web server config: apache2.
 + Done!

Note the message saying that Apache is reloading.

And to verify it, I use `curl` and look for the line saying “expire date”:

alex@kallobombus:~$ curl --verbose --head --silent https://alexschroeder.ch/

  CApath: /etc/ssl/certs

...

Yay!

Now all I have to do is install this.

I copied `/home/alex/src/letsencrypt.sh/letsencrypt.sh` to `/etc/letsencrypt.sh/letsencrypt.sh` and created the file `/etc/cron.weekly/letsencrypt.sh` which calls it:

#!/bin/sh
exec /etc/letsencrypt.sh/letsencrypt.sh -c

I made sure to grant it `+x` permissions, too. 🙂

And now, just to test things, let’s run the cron job:

alex@kallobombus:~$ sudo /etc/cron.weekly/letsencrypt.sh
1. INFO: Using main config file /etc/letsencrypt.sh/config
Processing alexschroeder.ch with alternative names: www.alexschroeder.ch
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 29 09:32:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing arabisch-lernen.org with alternative names: www.arabisch-lernen.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug  6 17:27:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing campaignwiki.org with alternative names: www.campaignwiki.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 29 09:32:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing communitywiki.org with alternative names: www.communitywiki.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 29 09:32:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing korero.org with alternative names: www.korero.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 29 09:32:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing oddmuse.org with alternative names: www.oddmuse.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 29 09:33:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...
Processing orientalisch.info with alternative names: www.orientalisch.info
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 29 09:33:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...

Very nice.

​#Web ​#Administration ​#Cryptography ​#Dehydrated

Comments

(Please contact me if you want to remove your comment.)

I really should read the draft for the ACME protocol (via their GitHub).

draft for the ACME protocol

their GitHub

– Alex Schroeder 2016-05-31 13:56 UTC

---

I did the same for Emacs Wiki. Since I’m using nginx as the front-end, there are small differences. Here’s the excerpt from `/etc/nginx/sites-enabled/emacswiki`:

server {
        listen 443 ssl;
        server_name www.emacswiki.org po6.ferrier.me.uk;

        # https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.4.6&openssl=1.0.1f&hsts=yes&profile=modern
        # using Nginx, Modern, Server Version 1.4.6 and OpenSSL Version 1.0.1f with HSTS
        ssl_certificate     /etc/letsencrypt.sh/certs/www.emacswiki.org/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt.sh/certs/www.emacswiki.org/privkey.pem;
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;

        # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits
        # using openssl dhparam -out dhparam.pem 2048
        ssl_dhparam         /etc/nginx/dhparam.pem;

        # modern configuration. tweak to your needs.
        ssl_protocols TLSv1.1 TLSv1.2;
        ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
        ssl_prefer_server_ciphers on;

        # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
        # are we ready?
        # add_header Strict-Transport-Security max-age=15768000;

        # OCSP Stapling ---
        # fetch OCSP records from URL in ssl_certificate and cache them
        ssl_stapling on;
        ssl_stapling_verify on;

        # verify chain of trust of OCSP response using Root CA and Intermediate certs
        # https://community.letsencrypt.org/t/will-does-the-letsencrypt-client-create-a-cert-chain-usable-with-ocsp-stapling/2072/15
        ssl_trusted_certificate /etc/letsencrypt/live/www.emacswiki.org/chain.pem;

        ...

        location /.well-known/acme-challenge {
                 alias /var/www/letsencrypt.sh/;
        }
}

And `/etc/letsencrypt.sh/hook.sh` is also different, of course:

#!/bin/bash
if [ ${1} == "deploy_cert" ]; then
    echo " + Hook: Reloading Nginx..."
    service nginx reload
else
    echo " + Hook: Nothing to do..."
fi

And the output:

1. INFO: Using main config file /etc/letsencrypt.sh/config
Processing www.emacswiki.org with alternative names: emacswiki.org
 + Checking domain name(s) of existing cert... unchanged.
 + Checking expire date of existing cert...
 + Valid till Aug 14 06:27:00 2016 GMT (Longer than 30 days). Skipping renew!
 + Hook: Nothing to do...

– Alex Schroeder 2016-06-10 09:16 UTC

---

Oh, the project renamed itself to dehydrated.

dehydrated

– Alex Schroeder 2017-03-14 06:21 UTC