Running letsencrypt as an Unprivileged User

Increase security and peace of mind by running letsencrypt as an unprivileged user

 ·  4 minutes read

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?


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.


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:
  • The web server's web root: /srv/http/
  • 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/
# chown -R letsencrypt: /srv/http/

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/ -d -d

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/ -d -d

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/

Verify option 2:

$ ls /home/letsencrypt/config/live/

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/ -d -d

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/ -d -d

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 {
     # Snip ...

     ssl_certificate /etc/letsencrypt/live/;
     ssl_certificate_key /etc/letsencrypt/live/;

     # 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.

letsencrypt security server