The Problem
If your Caddy service keeps restarting in a loop with no error logs, the cause is almost certainly using caddy start instead of caddy run in your systemd unit file.
Symptoms:
systemctl status caddyshows the service cycling throughactive โ activating โ activejournalctl -u caddy -fshows repeated start messages but no errors- The web server works briefly, then restarts again
Root Cause: start vs run
Caddy has two commands for starting the server:
caddy run # Starts Caddy and BLOCKS (foreground process)
caddy start # Starts Caddy in the BACKGROUND and returns immediately
Systemd expects ExecStart to run a foreground process that stays alive. When you use caddy start, the command launches Caddy in the background and then exits immediately with code 0. Systemd sees the process exit and โ because Restart=always is set โ restarts it. This creates an infinite loop.
The Fix
Change caddy start to caddy run in your service file:
# /etc/systemd/system/caddy.service (BROKEN)
[Unit]
Description=Caddy Web Server
After=network.target
[Service]
User=root
Group=root
Type=simple
Restart=always
RestartSec=15s
WorkingDirectory=/root/caddy
ExecStart=/root/caddy/caddy start # <-- WRONG: exits immediately
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/caddy.service (FIXED)
[Unit]
Description=Caddy Web Server
After=network.target
[Service]
User=root
Group=root
Type=simple
Restart=always
RestartSec=15s
WorkingDirectory=/root/caddy
ExecStart=/root/caddy/caddy run # <-- CORRECT: blocks indefinitely
[Install]
WantedBy=multi-user.target
After editing the file:
sudo systemctl daemon-reload
sudo systemctl restart caddy
sudo systemctl status caddy
A Better Production Caddy Service File
The official Caddy project provides a well-configured service file. Here’s a hardened version:
# /etc/systemd/system/caddy.service
[Unit]
Description=Caddy Web Server
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target
[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Key improvements over the minimal version:
| Setting | Purpose |
|---|---|
Type=notify |
Caddy signals systemd when it’s ready (more reliable than simple) |
ExecReload=caddy reload |
Graceful config reload without downtime |
LimitNOFILE=1048576 |
Raise file descriptor limit for high-traffic sites |
PrivateTmp=true |
Isolate /tmp for security |
ProtectSystem=full |
Read-only access to /usr, /boot, /etc |
AmbientCapabilities=CAP_NET_BIND_SERVICE |
Allow binding to ports 80/443 as non-root |
Restart=on-failure |
Only restart on failure, not on clean exit |
Using the Official Package
If you installed Caddy via the official package (apt/rpm), the service file is already correct:
# Install via apt (Debian/Ubuntu)
sudo apt install caddy
# The package includes a correct service file at:
# /lib/systemd/system/caddy.service
sudo systemctl enable caddy
sudo systemctl start caddy
Reloading Config Without Restart
One advantage of caddy run with systemd is zero-downtime config reloads:
# Reload Caddy config without dropping connections
sudo systemctl reload caddy
# Or directly
caddy reload --config /etc/caddy/Caddyfile
This is much better than restart, which briefly drops all connections.
Debugging Systemd Services
Useful commands when troubleshooting any systemd service:
# View service status
systemctl status caddy
# Follow live logs
journalctl -u caddy -f
# View last 50 lines of logs
journalctl -u caddy -n 50
# View logs since last boot
journalctl -u caddy -b
# Check if service is enabled on boot
systemctl is-enabled caddy
# List all failed services
systemctl --failed
# Show full service file (including overrides)
systemctl cat caddy
Comments