Any process can be run as a non-root user service.

The first criteria to have a service as a non-root user is to have the service be kept running even when the user is not logged in.

However, having a web server places an additional challenge – port 80 and port 443 which are basically the http and the https ports need to be bound by a user with administrative privileges because they are lower ports.

1. Keep non-root user service running

For this we use Linux’s loginctl command which is used to control the systemd login manager.

This is used in with the enable-linger option. From the page linked above –

When enabled for a specific user, a user manager is spawned for the user at boot and kept around after logouts. This allows users who are not logged in to run long-running services.

The command to be run is –

sudo loginctl enable-linger <user name>

2. Set capability to run on lower ports

To allow caddy to run on port 80 and 443, run the following command –

sudo setcap 'cap_net_bind_service=+ep' /usr/bin/caddy

This is explained in more detail in this answer.

Essentially, we are allowing a program (caddy, in this case), to bind to low-numbered ports via the set capability command. The setcap man page is here with the details of the effective (e) and permitted (p) values here.

3. creating a caddy service unit

For non-root users, the service unit files should be in ~/.config/systemd/user folder. In this case, we create a caddy.service file under that folder with the following –

# /home/<username>/.config/systemd/user/caddy.service
[Unit]
Description=Caddy web server (user)
After=network.target

[Service]
ExecStart=/usr/bin/caddy run --config /path/to/caddy_config_file --adapter caddyfile
ExecReload=/usr/bin/caddy reload --config /path/to/caddy_config_file --adapter caddyfile
Restart=on-failure
TimeoutStopSec=5s
LimitNOFILE=1048576

[Install]
WantedBy=default.target

Before we run this as a service, it’s always a good practice to test the command manually, by running –

/usr/bin/caddy run --config /path/to/caddy_config_file --adapter caddyfile

4. Reload user systemd manager and start the service

If this works and everything looks fine, then we can reload the user systemd manager by

systemctl --user daemon-reload

Enable and start the caddy service

systemctl --user enable caddy
systemctl --user start caddy

Check the status to confirm that everything is running smoothly –

systemctl --user status caddy