How to hot-reload policy without dropping connections

Problem: you want to change a deny/redact/rate-limit rule on a running gateway without dropping any in-flight requests.

Solution: edit mcpgw.yaml and send SIGHUP to the mcpgw process. It re-reads the config, validates it, and atomically swaps the policy engine.

Recipe

# Edit the file in-place
$EDITOR /etc/mcpgw/mcpgw.yaml

# Reload (Docker)
docker kill --signal=HUP mcpgw

# Reload (systemd)
systemctl kill --signal=HUP mcpgw.service

# Reload (raw process)
kill -HUP "$(pidof mcpgw)"

The gateway logs:

policy reload: ok (rules=4)

Or, on a config error:

policy reload: rejected: invalid regex on rule "redact-secrets": missing closing bracket

If the new config is invalid, the old policy stays in effect. The gateway never enters a half-loaded state.

What is reloadable, what is not

Field Hot-reload?
policy, guardrails, upstreams, routes, default_upstream ✅ yes
audit, telemetry, auth, identity, tool_search ✅ yes
License contents at the existing license.path ✅ yes; re-read and validate-then-swap
TLS certificate/key/CA contents while TLS remains enabled ✅ yes
listen, license.path, trusted_proxies ⚠️ requires restart
Enabling/disabling TLS, rate-limit store type, shutdown ⚠️ requires restart
allowed_protocol_versions, task_affinity, subscriptions ⚠️ requires restart

For non-reloadable fields, do a rolling restart: bring up a second container with the new config, drain the first.

Pitfalls

  • SIGHUP re-reads the license too. A valid replacement at the existing license.path is swapped in atomically; an invalid replacement is rejected and the current license remains active.
  • In-flight requests keep using the old engine. A request that arrives during reload but was admitted before the swap will be evaluated against the previous rule set. New requests get the new engine. This is a feature, not a bug — it prevents partial application.
  • Reload results are operational logs. They are written through the process logger (stderr/journald), not as request records in audit.jsonl.

Verifying

# Trigger a reload and watch the log
docker logs -f mcpgw &
docker kill --signal=HUP mcpgw

You should see one operational log line reporting the accepted or rejected reload. Check /readyz; restart-only differences are exposed in its restart_pending field.