Securely running dehydrated with systemd
Dehydrated is a light-weight client to obtain SSL certificates from letsencrypt. It comes packaged in Debian, but it's left up to you to configure your system so that it runs regularly and refreshes your certificates before they expire.
So after configuring dehydrated itself, you can use the following instructions to create a locked-down systemd service and an accompanying timer unit.
I'd rather avoid running dehydrated as root (even given the systemd
lockdown shown below), as it doesn't need to be root to do its job. As
dehydrated needs to share data with the webserver, I'm not going to
use systemd's DynamicUser
feature, but use a dedicated system
user. The Debian package does not create a dedicated user for
dehydrated
, so let's create one (including a dedicatd group)
ourselves, and adjust its home directory's ownership accordingly:
adduser --system --no-create-home --home /var/lib/dehydrated \ --shell /usr/bin/nologin --disabled-login \ --gecos "dehydrated ACME client,,," --group dehydrated chown -R dehydrated: /var/lib/dehydrated
Note that I did not need to make the certs
subdirectory readable by
nginx, as it will be started as root, and reads the certificate files
before dropping privileges. If your server is started unprivileged,
you are advised to use hooks (see this github
issue).
Note that your WELLKNOWN
directory must be writable by the
dehydrated
user as well, and this directory must be readable by
the unprivileged web server worker processes. In my case, the
WELLKNOWN
directory is /srv/www/.acme-challenges
:
mkdir /srv/www/.acme-challenges chown dehydrated:www-data /srv/www/.acme-challenges chmod u=rwx,go=rx /srv/www/.acme-challenges
Next, let's create a systemd service in
/etc/systemd/system/dehydrated.service
with the following contents:
[Unit]
Description=Run letsencrypt certificate refresh
[Service]
Type=oneshot
User=dehydrated
Group=dehydrated
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/srv/www/.acme-challenges /var/lib/dehydrated
PrivateTmp=yes
ExecStart=/usr/bin/dehydrated -c
ExecStartPost=+/bin/systemctl reload nginx.service
This will run dehydrated as the user/group we have created above, make
the whole file system hierarchy read-only, hide home directories, and
create a private /tmp
, not shared with the host. Other than /tmp
,
only the ReadWritePaths
will be writable.
When dehydrated has run sucessfully, we reload nginx. Note the +
in
the ExecStartPost
line, which will cause this command to be run with
full privileges.
You can now try if the service works:
systemctl daemon-reload systemctl start dehydrated
Now, for the timer unit, create /etc/systemd/dehydrated.timer
:
[Unit]
Description=Daily letsencrypt run
[Timer]
OnCalendar=*-*-* 00:00:00
Persistent=true
[Install]
WantedBy=timers.target
Let's enable and start it:
systemctl daemon-reload systemctl enable dehydrated.timer systemctl start dehydrated.timer
You can verify the service is now scheduled using systemctl list-timers
.