documentation
deployment hardening
Pre-deploy security checklist. Admin port, trusted proxies, body limits, and reload safety. Read this once before publishing.
updated
GroundShade’s security guarantees depend on correct network boundaries. Confirm these settings before publishing.
Admin port
- Bind admin to loopback on bare metal:
listen.admin: "127.0.0.1:9090". - If admin binds any non-loopback address, set
listen.admin_tokenorGROUNDSHADE_ADMIN_TOKEN. Startup refuses unauthenticated non-loopback admin listeners. - In Docker, publish the host port to loopback only:
"127.0.0.1:9090:9090". The container itself can listen on0.0.0.0:9090(the published image does this by default). - Prometheus must send
Authorization: Bearer <admin-token>when a token is set.
Trusted proxies
GroundShade ignores X-Forwarded-For and X-Real-IP unless the
immediate TCP peer is in listen.trusted_proxies.
Defaults trust only loopback:
listen:
trusted_proxies:
- "127.0.0.1/32"
- "::1/128"
For Docker, add your fronting proxy’s pinned container IP:
listen:
trusted_proxies:
- "172.18.0.10/32"
or via env:
GROUNDSHADE_TRUSTED_PROXIES=172.18.0.10/32
Do not set 0.0.0.0/0 or a broad public range. Any trusted
peer can supply the client IP used for fast-lane IP allowlists,
crawler verification, behavioural signal buckets, logs, and
trust-token binding.
Connection-cap bypass
Peers in listen.trusted_proxies also bypass the per-IP connection
cap (selfdef.max_connections_per_ip). This is intentional: a
fronting Caddy or nginx container aggregates many real clients into
one TCP peer and would otherwise saturate the cap.
The global cap (max_connections_total) and backpressure still
apply.
If groundshade_connections_rejected_total{reason="per_ip"} is
climbing behind a fronting proxy, the proxy’s IP is not in
listen.trusted_proxies.
Pin tightly. Use a /32 for a single container IP. A /16
for the entire docker bridge grants cap bypass to every container
on it, so a compromised sibling container could exhaust the global
cap without per-IP throttling.
Behind Cloudflare orange-cloud. The XFF chain has an extra hop
(CF edge → your fronting proxy). Both must appear in
trusted_proxies. See behind cloudflare for
the full recipe and the architectural limits you accept.
Body limits
selfdef.max_body_bytes (default 10 MiB) applies to forwarded
request bodies and to GroundShade’s own POST endpoints:
/.well-known/groundshade/solve/admin/login/admin/shields
Oversized internal-endpoint requests return 413 Payload Too Large.
selfdef.max_header_bytes (default 32 KiB) applies to all inbound
headers.
Reloads
SIGHUP re-reads the config file and reapplies environment
overrides. Docker and systemd-style deployments can keep values like
GROUNDSHADE_ADMIN_TOKEN, GROUNDSHADE_UPSTREAM_URL, and
GROUNDSHADE_TRUSTED_PROXIES outside YAML and they survive each
reload.
A validation failure logs a warning and keeps the old config running. SIGHUP cannot take the proxy down through bad config.
Pre-deploy checklist
-
listen.trusted_proxieslists every fronting peer with a/32(or its v6 equivalent). -
listen.adminis127.0.0.1:9090, or an admin token is set. -
GROUNDSHADE_ADMIN_TOKENis fromopenssl rand -hex 32, not a sentence. - State directory is mounted as a volume so
trust.keysurvives restarts. - Host port
9090is published to host loopback only. - Webhook URLs (if any) point at internal endpoints, not third parties.
-
observe.log_ip_hash: trueunless you have a written reason to log raw IPs.