Running Caddy Server as a service with systemd

In a previous blogpost I explained how to run Caddy (a brilliant and simple web server) as a service using Upstart. Systemd, however, replaced Upstart on most of the operating systems, so it makes more sense to have a guide for systemd.

Caddy executable

We don’t run Caddy as root to keep things as secure as possible. To allow a non-root user to listen on port 80 or 443, we must allow the executable to do so:

$ sudo setcap cap_net_bind_service=+ep ./caddy

systemd unit file

Now we create a systemd configuration file /etc/systemd/system/caddy.service, which looks like this on my server:

[Unit]
Description=Caddy webserver
Documentation=https://caddyserver.com/
After=network.target

[Service]
User=mathias
#Group=some_group
WorkingDirectory=/home/mathias
LimitNOFILE=4096
PIDFile=/var/run/caddy/caddy.pid
ExecStart=/home/mathias/caddy -agree -email my_lets_encrypt@mail.com -pidfile=/var/run/caddy/caddy.pid
Restart=on-failure
StartLimitInterval=600

[Install]
WantedBy=multi-user.target

A brief explanation of the directives:

  • After=network.target will automatically start Caddy after all networking stuff is loaded at boot.
  • User, Group and WorkingDirectory speak for themselves… Make sure that your Caddyfile is in the working directory. You can always manually specify the location of the Caddyfile: $ caddy -conf="/path/to/Caddyfile".
  • LimitNOFILE=4096 sets max file descriptor (to a good production value), like you would do with ulimit -n 4096 in your terminal.
  • ExecStart is the actual script that will be executed. In this case it runs the Caddy binary which is in my home directory, agrees the Let’s Encrypt user agreement, and sets a default email for your Let’s Encrypt account. If you don’t add those two flags, Caddy will prompt for user input, which will naturally not work when you start it as a daemon.
  • Restart=on-failure will automatically respawn the Caddy process if Caddy crashes (i.e. stopping with non-zero exit code).
  • StartLimitInterval=600 makes sure that if Caddy crashes too many times (being 5 by systemd’s default value) it will wait 10 min before trying again.
  • WantedBy specifies a target we want this service to be included with.

Make sure too take a peak at the systemd documentation, since it’s very powerful  and supports a lot of features!

Running the service

After you have created the service file, you must enable it: sudo systemctl enable caddy. Don’t forget to reload the service file if you make changes to it: systemctl daemon-reload.

Now you can start Caddy using sudo service caddy start. And of course stop, restart, …

You can view the status of your service using sudo service caddy status.

Join the Conversation

17 Comments

  1. Great post! Just wondering why PID file is in there twice? PIDFile and pidfile? 🙂

    1. Nope, it’s just the word wrapping… The second PID is part of the Caddy arguments:

      ExecStart=/home/mathias/caddy -agree -email my_lets_encrypt@mail.com -pidfile=/var/run/caddy/caddy.pid

  2. Thank you, quality article. I had caddy configured and auto booting in no time!

  3. I had trouble getting this working, it didn’t want to start or restart. This was the status:

    “`
    $ sudo service caddy status
    ● caddy.service – Caddy webserver
    Loaded: loaded (/etc/systemd/system/caddy.service; enabled; vendor preset: enabled)
    Active: inactive (dead) (Result: exit-code) since Sun 2016-09-11 02:23:28 CEST; 8min ago
    Docs: https://caddyserver.com/
    Process: 2133 ExecStart=/usr/local/bin/caddy -agree -email=mymail -pidfile=/var/run/caddy/caddy.pid
    Main PID: 2133 (code=exited, status=1/FAILURE)

    Sep 11 02:23:28 peteruithoven systemd[1]: caddy.service: Main process exited, code=exited, status=1/FAILURE
    Sep 11 02:23:28 peteruithoven systemd[1]: caddy.service: Unit entered failed state.
    Sep 11 02:23:28 peteruithoven systemd[1]: caddy.service: Failed with result ‘exit-code’.
    Sep 11 02:23:28 peteruithoven systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
    Sep 11 02:23:28 peteruithoven systemd[1]: Stopped Caddy webserver.
    Sep 11 02:23:28 peteruithoven systemd[1]: caddy.service: Start request repeated too quickly.
    Sep 11 02:23:28 peteruithoven systemd[1]: Failed to start Caddy webserver.
    “`

    Since it complained about restart to quickly and I didn’t find the `StartLimitInterval` property in the docs I replaced that with a `RestartSec`:
    “`
    RestartSec=1s
    “`

    1. THANK YOU FOR THIS!!!!! I’ve been beating my head against the keyboard trying to figure out why this keeps happening for what it seems only me.

  4. Hi just made an update of ubuntu 14 to 16… 🙂 I used upstart but this isn’t working any more. At the moment I figure out what to do. Do you know what to do with this error?

    caddy.service – Caddy HTTP/2 web server
    Loaded: loaded (/etc/systemd/system/caddy.service; enabled; vendor preset: enabled)
    Active: inactive (dead) (Result: exit-code) since Do 2017-06-15 15:44:24 CEST; 3s ago
    Docs: https://caddyserver.com/docs
    Process: 2076 ExecStart=/usr/local/bin/caddy -log stdout -agree=true -conf=/etc/caddy/Caddyfile -root=/var/tmp (code=exited, status=203/EXEC)
    Main PID: 2076 (code=exited, status=203/EXEC)

    Jun 15 15:44:23 v22016043535433702 systemd[1]: caddy.service: Unit entered failed state.
    Jun 15 15:44:23 v22016043535433702 systemd[1]: caddy.service: Failed with result ‘exit-code’.
    Jun 15 15:44:24 v22016043535433702 systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
    Jun 15 15:44:24 v22016043535433702 systemd[1]: Stopped Caddy HTTP/2 web server.
    Jun 15 15:44:24 v22016043535433702 systemd[1]: caddy.service: Start request repeated too quickly.
    Jun 15 15:44:24 v22016043535433702 systemd[1]: Failed to start Caddy HTTP/2 web server.

      1. in the meantime I tried a few changes. But when I look into journalct I have this: Jun 15 17:34:27 v22016043535433702 systemd[1]: Started Caddy webserver.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Main process exited, code=exited, status=203/EXEC
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Unit entered failed state.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Failed with result ‘exit-code’.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: Stopped Caddy webserver.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: Started Caddy webserver.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Main process exited, code=exited, status=203/EXEC
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Unit entered failed state.
        Jun 15 17:34:27 v22016043535433702 systemd[1]: caddy.service: Failed with result ‘exit-code’.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Stopped Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Started Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[3405]: caddy.service: Failed at step EXEC spawning /usr/local/bin/caddy: Permission denied

        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Main process exited, code=exited, status=203/EXEC
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Unit entered failed state.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Failed with result ‘exit-code’.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Stopped Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Started Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Main process exited, code=exited, status=203/EXEC
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Unit entered failed state.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Failed with result ‘exit-code’.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Stopped Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Started Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Main process exited, code=exited, status=203/EXEC
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Unit entered failed state.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Failed with result ‘exit-code’.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Service hold-off time over, scheduling restart.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Stopped Caddy webserver.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: caddy.service: Start request repeated too quickly.
        Jun 15 17:34:28 v22016043535433702 systemd[1]: Failed to start Caddy webserver.

        Thats the log for the last try. Maybe some permissions problems.

        1. EXEC spawning /usr/local/bin/caddy: Permission denied

          Is Caddy executable? Has the user you’re running it under permission to launch it?
          Maybe try it by hand…

          1. manuel I start caddy with /usr/local/bin/caddy: sudo ./caddy -conf=”/etc/caddy/Caddyfile” (
            -rwxr-xr-x 1 root root 23092163 Jun 15 15:10 caddy

            It works fine.

            With systemd it is not working.
            -rw-r–r– 1 root root 440 Jun 15 20:44 caddy.service

            I’ve
            tried your caddy.service file above and the one directly from github.
            But it’s not working. What does “(code=exited, status=200/CHDIR)” mean?
            The working director is specified in my file like:
            WorkingDirectory=/usr/local/bin/caddy -conf=”/etc/caddy/Caddyfile” Is
            this correct?

Leave a comment

Your email address will not be published. Required fields are marked *