---
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.
Simple Let's Encrypt on Debian/Apache
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/
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
(Please contact me if you want to remove your comment.)
⁂
I really should read the draft for the ACME protocol (via 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.
– Alex Schroeder 2017-03-14 06:21 UTC