When running Caddy v0.9.3 under systemd with a dedicated caddy
user, you might encounter the frustrating port binding error:
listen tcp :443: bind: permission denied
This typically occurs when your service account lacks sufficient privileges to bind to privileged ports (below 1024). Here's why this happens in Linux systems:
On Unix-like systems, ports below 1024 are considered privileged and require root access. When running Caddy as a non-root user via systemd, you have several technical options:
The most secure approach is granting specific capabilities without full root access:
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
Verify the change with:
getcap /usr/local/bin/caddy
Create a socket unit (/etc/systemd/system/caddy.socket
):
[Unit]
Description=Caddy HTTP/2 web server socket
[Socket]
ListenStream=80
ListenStream=443
NoDelay=true
ReusePort=true
[Install]
WantedBy=sockets.target
Then modify your service unit to include:
[Service]
...
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy -conf /etc/caddy/Caddyfile
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
For Debian-based systems:
sudo apt install authbind
sudo touch /etc/authbind/byport/443
sudo chown caddy:caddy /etc/authbind/byport/443
sudo chmod 755 /etc/authbind/byport/443
Then modify your Caddy service to use authbind:
ExecStart=/usr/bin/authbind --deep /usr/local/bin/caddy -conf /etc/caddy/Caddyfile
After applying any solution:
sudo systemctl daemon-reload
sudo systemctl restart caddy
journalctl -u caddy --no-pager -n 30
Check for successful binding:
ss -tulnp | grep caddy
When dealing with privileged ports:
- Always prefer capability-based solutions over full root access
- Regularly audit your capability assignments
- Consider using Linux security modules like SELinux or AppArmor
When running Caddy as a non-root user under systemd (specifically user caddy
), you might encounter this binding error because Linux systems restrict non-privileged users from binding to ports below 1024. This is a fundamental security feature of Unix-like systems.
First confirm if your system follows this restriction:
cat /proc/sys/net/ipv4/ip_unprivileged_port_start
If this returns 1024 (typical default), then non-root users cannot bind to ports below this value.
Here are three approaches to resolve this:
1. Using authbind (Recommended)
Install authbind and configure it:
sudo apt install authbind
sudo touch /etc/authbind/byport/443
sudo chown caddy:caddy /etc/authbind/byport/443
sudo chmod 755 /etc/authbind/byport/443
Then modify your systemd service file to use authbind:
[Service]
User=caddy
ExecStart=/usr/bin/authbind --deep /usr/local/bin/caddy -conf /etc/caddy/Caddyfile
2. Port Forwarding with iptables
Run Caddy on a high port and forward:
sudo iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 8443
Update your Caddyfile:
example.com:8443 {
# Your configuration
}
3. Capabilities Approach (Advanced)
Give Caddy specific capabilities:
sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/caddy
Then verify:
getcap /usr/local/bin/caddy
The authbind method is generally preferred because:
- It doesn't require running as root
- Maintains better security boundaries
- Works well with systemd's user isolation
If issues persist after applying these solutions:
# Check if port is already in use
sudo netstat -tulnp | grep 443
# Verify Caddy binary permissions
ls -la /usr/local/bin/caddy
# Check systemd logs
journalctl -u caddy -f
A complete systemd service file using authbind:
[Unit]
Description=Caddy HTTP/2 web server
After=network.target
[Service]
User=caddy
Group=caddy
ExecStart=/usr/bin/authbind --deep /usr/local/bin/caddy -conf /etc/caddy/Caddyfile
Restart=on-failure
LimitNOFILE=8192
[Install]
WantedBy=multi-user.target