Let’s Encrypt, automated, but please not as root

3 minute read

I made the switch today to Let’s Encrypt for all the certificates used by services hosted at siosm.fr.

The official setup instructions are well written and simple to follow but not safe enough for my taste. The Let’s Ecrypt page on the Arch Wiki also has most of the information required to get a working setup but does not care for security either.

So here is a non root, confined setup for certbot, the official Let’s Encrypt client.

Although this was done on Arch Linux, this is probably generic enough to work on any systemd enabled distribution.

Update: I have improved this post to avoid using a path unit to trigger service restart upon certificate update. Apart from the fact that the configuration now involves fewer units, this also solves a minor issue. The previous setup could have been turned into a potential denial of service against systemd and the services using the certificates.

certbot is available in the official repositories:

# pacman -Syu certbot

Create a non root user and group for certbot:

# groupadd -r certbot
# useradd -d /var/lib/letsencrypt -g certbot -G certbot -l -M -r -s /usr/bin/nologin certbot

Give certbot ownership to the directories it is going to use:

# chown certbot:certbot /etc/letsencrypt
# chown certbot:http /var/lib/letsencrypt
# chown certbot:log /var/log/letsencrypt
# chmod u=rwx,g=rx,o-rwx /etc/letsencrypt /var/{lib,log}/letsencrypt

Create a configuration file:

/etc/letsencrypt/cli.ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Register with the specified e-mail address
email = root@example.com

# Generate certificates for the specified domains. Required the first time you
# run certbot.
# Not necessary for renewal requests and thus must be commented out then.
domains = example.com, stuff.example.com

# Use a text interface instead of ncurses
text = True

# Run without ever asking for user input
non-interactive = True

# Enables OCSP Stapling
staple-ocsp = True

# Use the webroot authenticator
authenticator = webroot
webroot-path = /var/lib/letsencrypt

# Touch a specific file each time we get a new certificate
# (see certbot-renewed.service)
renew-hook = date --iso=min > renewed

Make sure permissions are OK:

# chmod 400 /etc/letsencrypt/cli.ini
# chown certbot:root /etc/letsencrypt/cli.ini

Configure your HTTP server (nginx example here):

...
    location /.well-known/acme-challenge {
        root /var/lib/letsencrypt;
        default_type "text/plain";
        try_files $uri =404;
    }
...

Run certbot manually the first time to make sure everything is OK:

# sudo -u certbot bash
(certbot)% certbot certonly

Then comment out the domains variable in /etc/letsencrypt/cli.ini.

Configure your services to now use the new certificates available in /etc/letsencrypt/live/example.com/. Use the certbot group to give specific users access to this folder:

# usermod -aG certbot http
# chmod g+rx /etc/letsencrypt/{archive,live}

Use the following systemd units to regularly check for certificates renewal:

/etc/systemd/system/certbot.timer:

1
2
3
4
5
6
7
8
9
10
11
12
13
[Unit]
Description=Weekly check for Let's Encrypt's certificates renewal

[Timer]
# The official documentation suggests running certbot two times per day but I
# find once a week to be reasonable.
OnCalendar=Sun *-*-* 04:00:00
# Use this line instead of you prefer running the check daily.
# OnCalendar=*-*-* 04:00:00
Persistent=true

[Install]
WantedBy=timers.target

/etc/systemd/system/certbot.service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Unit]
Description=Let's Encrypt certificate renewal

[Service]
Type=oneshot

User=certbot
Group=certbot
UMask=0027

PermissionsStartOnly=yes
ExecStart=/usr/bin/certbot renew
ExecStartPost=/usr/bin/systemctl start --no-block certbot-renewed

NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=yes
ProtectHome=yes

CapabilityBoundingSet=
AmbientCapabilities=

And this one to automatically restart selected services if necessary:

/etc/systemd/system/certbot-renewed.service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[Unit]
Description=Restart selected daemons when Let's Encrypt certificates are renewed
ConditionFileNotEmpty=/var/lib/letsencrypt/renewed

[Service]
Type=oneshot

User=certbot
Group=certbot
UMask=0027

PermissionsStartOnly=yes
ExecStart=/usr/bin/rm -- /var/lib/letsencrypt/renewed
ExecStartPost=/usr/bin/systemctl restart --no-block nginx

NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=yes
ProtectHome=yes

CapabilityBoundingSet=
AmbientCapabilities=

Enjoy your non root Let’s Encrypt fully automated setup!

Updated:

Comments


Comments are disabled on this blog but feel free to start a discussion with me on Mastodon.
You can also contact me directly if you have feedback.