gastownhall/beads · error
--addr %q binds beyond loopback, which requires --allow-non-
Error message
--addr %q binds beyond loopback, which requires --allow-non-loopback (and, with it, --auth-token-file)
What it means
ValidateBindAddr rejects binding to a non-loopback IP unless the caller explicitly opts in with allowNonLoopback (the --allow-non-loopback flag, which requires --auth-token-file). This is a security guard against accidentally exposing the server on a public interface without authentication.
Source
Thrown at internal/httpapi/server.go:451
// Hostnames are refused, "localhost" included. A name is not a listener
// specification — it resolves to whatever the host's resolver says today, so
// the operator cannot tell from the flag which interfaces they just opened.
// Unix sockets are not supported at all; they fail here because they do not
// parse as host:port.
func ValidateBindAddr(addr string, allowNonLoopback bool) (net.IP, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("--addr %q must be HOST:PORT with a numeric IP literal host (unix sockets are not supported): %w", addr, err)
}
if _, err := strconv.ParseUint(port, 10, 16); err != nil {
return nil, fmt.Errorf("--addr %q: port must be a number from 0 to 65535 (0 picks an ephemeral port)", addr)
}
ip := net.ParseIP(host)
if ip == nil {
return nil, fmt.Errorf("--addr %q: host must be a numeric IP literal, not a name — use 127.0.0.1 rather than localhost", addr)
}
if !ip.IsLoopback() && !allowNonLoopback {
return nil, fmt.Errorf("--addr %q binds beyond loopback, which requires --allow-non-loopback (and, with it, --auth-token-file)", addr)
}
return ip, nil
}
// Listen validates the configuration, binds the listener, and reports the
// bound address on stdout and the startup state on stderr. It does not accept
// anything until Serve runs.
//
// There is no lock file, pid file or discovery file: bd serve is
// operator-invoked and the TCP bind IS the mutual exclusion, so a second
// instance on the same fixed port fails here with the operating system's own
// address-in-use error. (Under the ephemeral default that exclusion does not
// exist — N instances simply run on N ports — which is why fixed ports are the
// deployment recommendation.)
func Listen(cfg Config) (*Server, error) {
if err := checkDatabaseSource(cfg); err != nil {
return nil, err
}View on GitHub (pinned to 71377f2769)
Solutions
- Bind to a loopback address like 127.0.0.1:PORT if only local access is needed
- If external access is required, pass --allow-non-loopback together with --auth-token-file
- Use a reverse proxy/tunnel (e.g. SSH port forward) instead of binding beyond loopback
Example fix
// before --addr 0.0.0.0:8080 // after --addr 0.0.0.0:8080 --allow-non-loopback --auth-token-file /path/to/token
Defensive patterns
Strategy: validation
Validate before calling
ip, _ := net.ResolveIPAddr("ip", hostOnly(addr))
if ip != nil && !ip.IP.IsLoopback() {
// must enable allowNonLoopback + auth token
} Try / catch
if _, err := httpapi.ValidateBindAddr(addr, allowNonLoopback); err != nil {
log.Fatalf("refusing to start: %v", err)
} Prevention
- Default to loopback binds; only widen deliberately
- When binding non-loopback, always pair --allow-non-loopback with --auth-token-file
- Prefer loopback + tunnel/reverse proxy over direct exposure
When it happens
Trigger: Calling ValidateBindAddr with a loopback=false IP such as 0.0.0.0, 192.168.1.5, or :: while allowNonLoopback is false.
Common situations: Users bind to 0.0.0.0 to share the server on a LAN or in a container and are surprised by the auth requirements; automated deployments pass a pod IP without enabling the flag.
Related errors
- --addr %q: port must be a number from 0 to 65535 (0 picks an
- --addr %q: host must be a numeric IP literal, not a name — u
- got %d close reasons for %d issue IDs; provide exactly one s
- cannot specify both --reason-file and --reason/--resolution/
- --reason-file %q is empty; close reason is required
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/8706e1e0133e54db.
Report an issue: GitHub.