Files

3.0 KiB

Caddy / Tailscale Port 443 Conflict

Symptom

systemctl status caddy shows:

listen tcp :443: bind: address already in use

Root Cause

tailscaled (the Tailscale daemon) binds port 443 on its Tailnet IP (e.g. 100.71.155.7:443). When Caddy tries to bind :443 (all interfaces — 0.0.0.0:443), the kernel blocks it because 100.71.155.7:443 is already taken. The public IP (152.53.192.33:443) is completely free — Caddy just can't reach it via the blanket :443 bind.

Diagnosis

# Check what's on port 443
ss -tlnp | grep 443
# → tailscaled  PID 897  bound to 100.71.155.7:443 (NOT 0.0.0.0:443)

# Confirm Caddy config is trying to use all-interfaces :443
grep ':443' /etc/caddy/Caddyfile

# Confirm Tailscale serve is active
tailscale serve status

Resolution — Use default_bind in Caddyfile

Tailscale binds to its specific Tailnet interface IP. Caddy can bind to the public IP instead, and both coexist:

Add a global options block at the top of /etc/caddy/Caddyfile:

{
    default_bind 152.53.192.33
}

This tells Caddy to bind ALL site blocks to the public IP only, avoiding the Tailscale interface entirely. Leases port 443 for public HTTPS without fighting Tailscale.

Verify Caddy parses the config before restarting:

caddy validate --config /etc/caddy/Caddyfile
# → "Valid configuration"

# Format it
caddy fmt --overwrite /etc/caddy/Caddyfile

# Start it
systemctl start caddy

# Confirm it's listening
systemctl is-active caddy
# → active

# Test a site
curl -sS -o /dev/null -w 'HTTP %{http_code}' https://sign.itpropartner.com/
# → HTTP 200

After the fix, verify the health check passes:

bash /root/.hermes/scripts/service-health-check.sh
echo "Exit code: $?"  # Should be 0 (silent = all healthy)

How It Works

  • Tailscale binds 100.71.155.7:443 (its Tailnet IP, interface tailscale0)
  • Caddy with default_bind 152.53.192.33 binds 152.53.192.33:443 (the public IP, interface eth0)
  • No overlap. Both serve HTTPS on port 443 to their respective audiences.

Without default_bind, Caddy tries 0.0.0.0:443 (all interfaces), which includes the Tailscale IP → conflict.

Resolution Options (ranked)

Option Effort Effect
default_bind <public-ip> (recommended) Add 3 lines to Caddyfile Full Caddy + Tailscale coexistence on :443
iptables redirect from Tailscale IP Moderate Works if Tailscale isn't actively serving
Disable Tailscale :443 tailscale serve --https=443 off Loses Tailscale Serve HTTPS on that box
Run Caddy on alt port (e.g., 8443) Update Caddy config + DNS sign.itpropartner.com needs custom port
Let Caddy fail Update health check to skip Caddy Acceptable if services reachable another way

Current State (Jul 11, 2026)

On Core (netcup, 152.53.192.33): Fixed — Caddy running with default_bind 152.53.192.33. Both Caddy (public HTTPS) and Tailscale (tailnet HTTPS) coexist cleanly.