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
SIGHUPre-reads the license too. A valid replacement at the existinglicense.pathis 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.
Related
- Reference: configuration schema
- Explanation: architecture — how the atomic engine swap works