automatic certbot renewal with NixOS and systemd

2023-04-21

I asked gpt-3.5-turbo. There was a lot of content missing, but in conjunction with the ArchWiki, the NixOS options search, and the man pages for systemd.service(5), systemd.unit(5) and systemd.timer(5) gave me enough to finish.

Here's how it looks:

systemd.services."certbot-renew" = {
    description = "Renew certbot certificates";
    script = ''
        #!/usr/bin/env sh
        ${pkgs.certbot-full}/bin/certbot renew --verbose
    '';
    serviceConfig = {
        Type = "oneshot";
        Restart = "on-failure";
        RestartSec = "1d";
        ExecStartPost = "systemctl reload nginx.service";
    };
    after = [ "network.target" ];
};
systemd.timers."certbot-renew" = {
    description = "Run certbot renewal once a month";
    timerConfig = {
        OnCalendar = "monthly";
        RandomizedDelaySec = "86400s";
        Persistent = true;
        Unit = "certbot-renew.service";
    };
    wantedBy = [ "timers.target" ];
    after = [ "network.target" ];
};

Explanations are as follows:

  1. Type is set to oneshot so that the service is tied to the successful execution of the certbot renew command.
  2. Since this is oneshot we can reload nginx automatically with ExecStartPost.
  3. I set RandomizedDelaySec because otherwise "monthly" means "at the start of each month". certbot includes some automated randomised jitter when being run automatically, but I assume that they otherwise get a lot of traffic on-the-hour.
  4. WantedBy timers.target is needed to actually activate the timer.
  5. After network.target is just for safety in case this launches on boot.

It works!

NEXT                        LEFT               LAST                        PASSED       UNIT                         ACTIVATES
Wed 2023-04-19 19:00:00 UTC 43min left         Wed 2023-04-19 18:00:07 UTC 16min ago    logrotate.timer              logrotate.service
Thu 2023-04-20 12:39:47 UTC 18h left           Wed 2023-04-19 12:39:47 UTC 5h 36min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2023-05-01 11:14:34 UTC 1 week 4 days left Wed 2023-04-19 17:42:41 UTC 34min ago    certbot-renew.timer          certbot-renew.service

bonus content: systemd-run

I used to run actions at particular times of day with the following command, hacked up in a frenzy:

sleep $(( $(date -d '23:35' +%s) - $(date +%s) )) && curl -X POST https://example.com/api/success

Sleeping until a certain time is brittle to clock drift (e.g. if my laptop is suspended before the command can run). I was aware of the at function, but reading through the ArchWiki gave showed me the systemd-run command, which sets a systemd timer to do the thing:

systemd-run --user --on-calendar=23:35 curl -X POST https://example.com/api/success

Cleaner and more reliable! The results can be viewed with journalctl.