Update: I published a new post explaining how to use acme-tiny for even better security. Check it out, it is a bit more advanced, but also more secure.
Running letsencrypt as an unprivileged user (non-root) is surprisingly easy, and even more surprisingly undocumented. There is no mention in the official documentation, nor was I able to find anything online.
There are alternative clients that were designed to be run as unprivileged, but they are not as beginner-friendly as the official one. I, for one, have switched to acme-tiny (and created an AUR package for it). Its much smaller and lets me have an even more secure setup.
Why bother?
Security.
You should strive to run every process with the lowest privileges possible. This both reduces the chances of a bug causing data loss, and more importantly, a security issue leading to a compromise.
Summary
In this tutorial we will setup letsencrypt to run as an unprivileged user using the webroot plugin.
This tutorial uses basic letsencrypt commands for simplicity. Refer to the official documentation for more advance usage.
Definitions and assumptions:
- The domain: example.com
- The web server's web root: /srv/http/example.com
- Commands preceded by # should be run as root.
- Commands preceded by $ should be run as the letsencrypt user.
Preparing the environment
First we need to create an unprivileged user for letsencrypt. I chose letsencrypt. The following command will create a new system user without a login shell or a home directory.
# useradd --shell /bin/false --system letsencrypt
Now we will create the needed directory for the webroot plugin, and set the correct permissions.
# mkdir -p /srv/http/example.com/.well-known/acme-challenge
# chown -R letsencrypt: /srv/http/example.com/.well-known
Optional: verify the web server can serve files created by our user:
$ echo works > /srv/http/example.com/.well-known/acme-challenge/test.txt
$ curl example.com/.well-known/acme-challenge/test.txt
If the last command printed "works", everything works. Otherwise, something is wrong with your configuration. This is unfortunately out of scope for this tutorial, though feel free to contact me, maybe I can help.
Preparing and running letsencrypt
There are two options for this step. The first option is easier and preferred if you already have a working letsencrypt. The second option is more correct and is preferred if you are starting fresh.
Option 1: Update the permissions of the default paths
By default letsencrypt (at least on Arch Linux) uses these three paths:
- logs-dir: /var/log/letsencrypt
- config-dir: /etc/letsencrypt
- work-dir: /var/lib/letsencrypt
We need to change these directories to be owned by our user and group:
# chown -R letsencrypt: /var/log/letsencrypt /etc/letsencrypt /var/lib/letsencrypt
Now we need to run letsencrypt so it creates the initial configuration and our first certificate.
$ letsencrypt certonly --webroot -w /srv/http/example.com -d example.com -d www.example.com
At this stage letsencrypt will complain about not running as root, that is fine. Ignore it. Just follow the steps and answer the questions.
Option 2: Create new directories for letsencrypt
letsencrypt supports a few undocumented flags that let you change the running environment.
First we need to create the relevant directory structure, for simplicity I chose /home/letsencrypt as the base directory and the rest as subdirectories:
# mkdir /home/letsencrypt
# chown letsencrypt: /home/letsencrypt
And as the user:
$ cd /home/letsencrypt
$ mkdir logs config work
Now we can run letsencrypt as we normally do, just with the addition of the --logs-dir
, --config-dir
and the --work-dir
flags.
$ letsencrypt certonly --config-dir /home/letsencrypt/config --work-dir /home/letsencrypt/work --logs-dir /home/letsencrypt/logs/ --webroot -w /srv/http/example.com -d example.com -d www.example.com
At this stage letsencrypt will complain about not running as root, that is fine. Ignore it. Just follow the steps and answer the questions.
Both: verify everything worked
If you got here, you should already have your certificate issued.
You can verify this by running:
Verify option 1:
$ ls /etc/letsencrypt/live/example.com
Verify option 2:
$ ls /home/letsencrypt/config/live/example.com
This should output cert.pem chain.pem fullchain.pem privkey.pem
Certificate renewal
This section is here just for completeness. You should run letsencrypt in a cron
job so the certificate is renewed before it expires (at the time of writing, 3 months). I have a cron job running once a month.
When renewing, you should run:
Renew option 1:
$ letsencrypt certonly --agree-tos --renew-by-default --webroot -w /srv/http/example.com -d example.com -d www.example.com
Renew option 2:
$ letsencrypt certonly --agree-tos --renew-by-default --config-dir /home/letsencrypt/config --work-dir /home/letsencrypt/work --logs-dir /home/letsencrypt/logs/ --webroot -w /srv/http/example.com -d example.com -d www.example.com
Important: do not forget to make the server reload the certificates after they are renewed. Nginx for example, does not do this automatically.
Finishing notes
For more information about letsencrypt, please refer to the official documentation.
This tutorial does not cover setting up your web server to use the new certificates. This is very simple and covered at length elsewhere.
However, here is an example for nginx:
server {
server_name example.com;
# Snip ...
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Snip ...
}
Please let me know if you encountered any issues or have any suggestions.
Please let me know if you spotted any mistakes or have any suggestions, and follow me on Twitter or RSS for updates.