# Caddy + Tailscale Port 443 Conflict On servers running both Caddy (public HTTPS) and Tailscale (tailnet HTTPS), both services want port 443. This conflict causes Caddy's systemd service to fail with: ``` listen tcp :443: bind: address already in use ``` ## Root Cause Tailscale binds `100.x.x.x:443` (its specific Tailnet interface IP). Caddy by default binds `:443` (all interfaces), which includes the Tailscale IP. The kernel rejects Caddy's bind because the port is already claimed on that specific address. The public interface IP (e.g. `152.53.192.33:443`) is completely unused — only Tailscale's tailnet IP is occupied. ## The Fix Add a global `default_bind` directive at the top of Caddyfile to restrict Caddy to the public IP only: ``` { default_bind } ``` Where `` is the server's public-facing interface IP (e.g. `152.53.192.33` for Core). ### After the fix | Service | IP Binding | Purpose | |---------|-----------|---------| | Caddy | `:443` | Public HTTPS for all domains | | Tailscale | `:443` | Tailnet HTTPS only | | Both | | **No conflict** — different IPs | ## Verification ```bash # Caddy should start cleanly systemctl start caddy systemctl is-active caddy # → "active" # Both should be bound without conflict ss -tlnp | grep ':443 ' # Expected: two listeners — one on public IP (Caddy), one on tailnet IP (tailscaled) # Public-facing sites should respond curl -sS -o /dev/null -w '%{http_code}' https://core.itpropartner.com/health # → 200 ``` ## When This Pattern Applies Any server running both: - **Caddy** (or any reverse proxy) wanting `:443` for Let's Encrypt + public HTTPS - **Tailscale** (which binds `:443` on its tailnet interface) This is expected on all servers where Tailscale is used for private infrastructure access AND Caddy serves public web traffic.