Binding & network exposure
Paddock runs commands, holds your tokens, and can read and write your repositories. Where it listens is therefore a security decision, not a convenience one — so since v0.44 the defaults treat it that way.
The two rules
Section titled “The two rules”1. The default is loopback
Section titled “1. The default is loopback”HOST defaults to 127.0.0.1. A fresh git clone or tarball run is network-closed:
reachable from the box itself, from nothing else. You opt in to more.
Resolution order, highest first:
HOSTPADDOCK_HOST(an alias —HOSTwins if both are set)host:inpaddock.config.yaml127.0.0.1
2. Exposed and unauthenticated refuses to start
Section titled “2. Exposed and unauthenticated refuses to start”Binding a non-loopback address while PADDOCK_AUTH_MODE=none is the actual footgun,
and Paddock will not do it. Startup fails closed with a message naming your options
— the same fail-closed treatment as jwt mode without a JWKS URL.
| Bind host | Auth mode | Result |
|---|---|---|
| Loopback | anything | ✅ Starts |
| Non-loopback | trusted-header or jwt | ✅ Starts — no flag needed (but see the caution below: the guard checks only that a mode is set) |
| Non-loopback | none | ❌ Refuses to start |
| Non-loopback | none, with PADDOCK_DANGEROUSLY_ALLOW_OPEN | ⚠️ Starts, logs a loud warning |
Note the second row: binding widely is fine once you have real authentication. The guard isn’t about exposure alone, it’s about exposure without a way to tell who’s knocking.
What counts as loopback
Section titled “What counts as loopback”localhost, the whole IPv4 127.0.0.0/8 range, the IPv6 loopback ::1, and
IPv4-mapped loopback like ::ffff:127.0.0.1. Bracketed forms ([::1]), surrounding
whitespace and casing are all normalised first.
0.0.0.0 and :: are not loopback. They mean every interface, which is exactly
the case the guard exists for.
I upgraded and now I can’t reach it
Section titled “I upgraded and now I can’t reach it”Pick whichever of these matches what you actually want:
Put authentication in front of it (the right answer). Set an auth mode and bind wide. Nothing else is needed — no dangerous flag, no warning.
HOST=0.0.0.0PADDOCK_AUTH_MODE=trusted-header # behind a proxy that authenticatesWith trusted-header, “the right answer” carries a condition: the network must
guarantee that proxy is the only route to the port, because the header is forgeable
by anything that can reach it — see the caution above.
jwt needs no such condition.
See Securing Paddock for the ladder from a VPN through to SSO.
Keep it on loopback and reach it through a proxy on the same box. A reverse proxy
(Caddy, nginx, Traefik) listening publicly and forwarding to 127.0.0.1:4000 is a very
good posture: only the proxy is exposed, and it’s the thing doing TLS and auth. Nothing
to change — this is the default.
Reach it over a VPN or overlay network. Bind loopback (or the tailnet interface) and let WireGuard / Tailscale be the network boundary.
You genuinely want an open, unauthenticated server. Then say so out loud:
HOST=0.0.0.0PADDOCK_AUTH_MODE=nonePADDOCK_DANGEROUSLY_ALLOW_OPEN=1It boots, and logs a warning on every start telling you that anyone who can reach the
port can run code and spend your Claude tokens as you. The variable is named the way it
is on purpose. Accepts 1 / true / yes.
Containers are different
Section titled “Containers are different”The published images still set HOST=0.0.0.0, and that’s correct — not an oversight.
Inside a container the network namespace is the isolation boundary. Docker cannot
reach 127.0.0.1 inside the container, so a loopback bind there would make the app
unreachable even from a deliberately-published port. The app also can’t see the host’s
publish posture from inside, so it’s in no position to police it.
The safe posture for a container is therefore carried by how you publish the port, not by the bind host:
# reachable only from the host, not the LANdocker run -p 127.0.0.1:4000:4000 \ -e PADDOCK_DANGEROUSLY_ALLOW_OPEN=1 \ ghcr.io/edspencer/paddock:latestThe paddock-deploy
recipes already do both — the loopback publish and the override, with the reasoning
inline. The distinction that matters is which mechanism protects you: on bare metal,
tarball, VM and systemd-in-LXC runs it’s the bind host; in a container it’s the publish
posture, and the flag is how you tell the app so.
See also
Section titled “See also”- Securing Paddock — authentication in front of Paddock, tier by tier.
- Environment variables — the
HOSTandPADDOCK_DANGEROUSLY_ALLOW_OPENrows. - Deploying Paddock — where to run it in the first place.