How to Fix “Caddy listen tcp :443: bind: permission denied” Error in Linux Systemd Setup


3 views

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