Skip to main content

A Bash Client for the ACME Protocol

These are my notes on installing acme.sh and using it on an aging Django site.

A short while ago, I experimented with a simple ACME client from Google just to see what minimal framework was available to simply get certificates. With that done, I wanted to see what ACME clients were available that had support for directly updating cert files on a regular basis.

Because my end goal was to automate certificate renewal on some fairly old legacy sites, I was particularly sensitive to any changes that a tool might make to the overall system configuration without my knowledge (e.g., updating openssl or python to newer versions, etc.). This is the overly paranoid OCD sysadmin part of my personality.

The Let's Encrypt certbot certainly does everything that I needed, but it seems to do a huge amount of stuff behind the scenes with python and virtualenvs. It also demands to be run as root, which scares the hell out of me. I remained skittish about simply installing and using certbot. As before, I looked for a simpler, less complicated, ACME client that would do what I needed.

There are a surprising number of acme clients out there. I finally settled on acme.sh, an ACME protocol client written purely in Shell (Unix shell) language, to manage installation and renewal of SSL certificates.

These are the steps that I followed to install, configure, and use acme.sh on a very old Django/wsgi site.

Configure httpd for http-01 Challenge

ACME supports multiple methods of confirming site ownership. I chose to use webroot method which issues an http-01 challenge. The challenge requires that a file be placed in the .well-known/acme-challenge directory of the site's root. acme.sh will do this automatically, however, httpd must be configured to serve files from that directory.

Because httpd was already configured to allow access to the contents of the Django application's static-collect directory, I chose to tell acme.sh to place challenge files there. This required the addition of the following line to my httpd.conf:

Alias  /.well-known/acme-challenge  /home/ec2-user/current/ecom/store/static-collect/.well-known/acme-challenge

Install acme.sh

First, we must get and install acme.sh. Note that running the install as root is optional and I did not do so. Also, at this early point, I did not want to automatically setup a cron job for renewal, as I was interested in seeing how well things worked.

$ git clone https://github.com/Neilpang/acme.sh.git
$ cd ./acme.sh
$ ./acme.sh --install --nocron --accountemail me@foo.com

This will install acme.sh in ~/.acme.sh. It also updates your bash initialization to source in ~/.acme.sh/acme.sh.env, which sets up an alias for acme.sh.

Unless you logout and back in, you'll need to source in the above env to perform the remaining steps.

Dryrun and Debug

LE provides a staging server that can be used to test against, prior to working with valid certificates. It's a good idea to do this so that you don't inadvertently run up against rate limits from testing.

To use the staging server (instead of the live server), supply the --staging command line option.

If you run into any problems, or if, like me, you just want to know more about what's going on, add --debug and --log to the command line options.

Obtain Initial Certificates

At this point, we're ready to get the initial certificates for the site:

acme.sh --staging --issue -d mydomain.com -w /home/ec2-user/current/ecom/store/static-collect

Note that the directory given to the -w option is the one that will contain .well-known/acme-challenge, which we aliased earlier in httpd.conf.

If the above runs successfully, the newly issued certificates will be located in ~/.acme.sh/mydomain.com.

You can list the certificates obtained by acme.sh via:

acme.sh --list

Install the Certificates

Note that the documentation warns against directly copying certificates from ~/.acme.sh. Rather, they recommend that you use --install-cert. This has the beneficial side-effect of telling acme.sh where the certificates ultimately end up. This knowledge is stored in ~/.acme.sh/mydomain.com/mydomain.com.conf; it used again at renewal time.

$ CDIR=/etc/pki/tls/certs
$ acme.sh --staging --install-cert -d mycomain.com \
          --certpath $CDIR/localhost.crt \
          --keypath  $CDIR/localhost.key \
          --capath   $CDIR/intermediate.crt \
          --fullchainpath $CDIR/fullchain.crt \
          --reloadcmd "sudo apachectl restart"

It should be obvious, but... if you run the above without --staging, you will obtain a valid certificate; if you run with --staging, the certificate will be invalid.

You can omit --reloadcmd "sudo apachectl restart" if you wish, however, by using it here, it gets placed in your conf file for later use by --renew and --cron. I have not checked to see if --reloadcmd can be used with either of those invocations.

Manual Renewal

Some of the documentation is brief. Among other things, I was curious as to how renewals actually worked, so I performed a manual renewal against the staging endpoint.

$ acme.sh --staging --renew -d mydomain.com

This told me that the certificate was not expiring soon and it exited. Rerun the command with --force, and it will renew the certificate(s), no matter their age.

What was interesting is that the above command puts the new certificates in the locations provided in the previous invocation of --install-cert. This makes sense, of course, but the documentation is silent on how all this works, so I wanted to investigate the behavior.

If you provided the --reloadcmd "apachectl restart" option to --install earlier, then the --renew command will bounce the httpd server.

Auto Renewal

Once I knew how things worked, I was ready to automate the renewals. This is what the --cron command is for. It should be run via cron on a daily basis. It will attempt to renew each certificate that has been configured. Here's my crontab entry:

0 0 * * * "/home/ec2-user/.acme.sh/acme.sh" --cron --home "/home/ec2-user/.acme.sh" > /dev/null

As with -renew, if your provided the --reloadcmd "apachectl restart" option to --install, then the --cron command will bounce the httpd server if the certificates are renewed.

Conclusion

I found the acme.sh documentation to be a little on the light side when it comes to explaining what happens behind the scenes. This made me somewhat uneasy, but once I did a little experimentation with it, I realized that it is a fantastic alternative to the heavier weight certbot. Setup is trivial and it is rock solid. I love it. This is how I'm using LE from here on out.