Skip to content

Operations

Reload

The config file's directory is watched (so atomic write-temp-then-rename replacement is observed, and events are debounced (rapid repeated events collapse into one)), and SIGHUP (a Unix signal that here means "reload config") also triggers a reload:

A file-watch reload of unchanged content is a no-op — the generation only advances when content actually changes. SIGHUP always force-republishes, even if the file content is identical. If the file watcher fails to start, SIGHUP reload still works.

The route table and upstream connection pools hot-swap together in one atomic publish, so a new route table is never paired with stale pools. The listener bind set (addresses/ports, protocol, proxy_protocol flag, and TLS mode) is hot-reloaded: adding, removing, or changing a TCP listener takes effect on the next config reload without a restart. Removed listeners drain in-flight connections before dropping; changed listeners are drained and re-bound. H3 (QUIC) and UDP listeners remain restart-only (they bind UDP sockets, not TCP). Certificate material on terminate listeners reloads live via the cert watcher (see below).

Certificate hot-reload

TLS certificate/key files on terminate listeners are watched the same way as the config file. A change rebuilds the TLS configuration and swaps it in without dropping connections: new handshakes use the new material, and handshakes/sessions already in flight keep what they negotiated. A failed reload (e.g. an unreadable PEM) is logged and the previous certificates keep serving.

Health endpoints

Dwara reserves /healthz, /readyz, and /metrics on every terminate and cleartext listener, served before route resolution:

PathMeaning
/healthz200 whenever the process is up (liveness)
/readyz200 once a config generation has published successfully, 503 before that (readiness)
/metricsPrometheus text format (a plain-text metrics format Prometheus scrapes), see Observability

These paths are not routable: a configured route matching one of them is permanently shadowed by the reserved handler. TLS-passthrough listeners never serve them (they don't speak HTTP).

Shutdown

SIGTERM/SIGINT (graceful-shutdown signals) stop accepting new connections, drain live connections (including ones still in the kernel accept backlog (the kernel's queue of not-yet-accepted connections)) within DWARA_SHUTDOWN_TIMEOUT_SECS (default 10), then exit 0. Anything still draining past the budget is force-closed. In-flight TLS-passthrough and L4 splices drain over the same budget: the process waits for the byte relays to complete (the peer closes or the bidirectional copy returns) up to the deadline. The gateway does not terminate TLS in passthrough mode, so it cannot emit a TLS close_notify alert; the drain is a bounded wait, and whatever remains at the deadline is force-closed.

Accept-loop supervision

Every serving surface (each data-plane listener and the admin listener) runs its accept loop under a panic supervisor: a panicked incarnation is respawned on the same bound socket (no re-bind, no port loss) up to 8 times per surface for the process lifetime, logging a warning each time. Once that budget is spent, the surface is given up on with an ERROR log and stays down — loudly — while the process and every other surface keep serving.

Protocol hardening

Every serving surface applies one hardening posture: parser and amplification bounds, plus a request-body inactivity timeout. These are environment variables, read once at startup (an invalid value falls back to its default) and applied process-wide — hardening is a property of the parser, not of a route.

Env varDefaultBounds against
DWARA_HTTP1_MAX_HEADERS100header-count bombs
DWARA_HTTP1_MAX_BUF_KIB64oversized header/line bombs
DWARA_HTTP1_HEADER_TIMEOUT_MS10000slowloris (a slow-header attack that ties up connections) (headers trickling in)
DWARA_H2_MAX_CONCURRENT_STREAMS128HTTP/2 stream floods
DWARA_H2_STREAM_WINDOW_KIB1024per-stream h2 receive buffering
DWARA_H2_CONNECTION_WINDOW_KIB4096connection-wide h2 receive buffering
DWARA_H2_MAX_SEND_BUF_KIB1024outbound h2 send buffer / write amplification
DWARA_REQUEST_BODY_TIMEOUT_MS30000 (0 disables)slow-body attacks (inactivity gap between body frames, not total upload time)

A request head carrying both Content-Length and Transfer-Encoding is rejected with a bare 400 before parsing and the connection is closed — the classic CL+TE smuggling (an attack that desyncs how two proxies parse a request) vector. This only needs to inspect the first request head on a connection: every forwarded request is rebuilt from parsed parts, so framing cannot desync through the gateway on later keep-alive requests.

Environment variables reference

See the full environment variables reference.

Runnable demo

Run hot config reload against a live gateway: demos/09-operations/ in the repository (test script: test-01-hot-reload.sh -- touch the mounted config and the gateway keeps answering while it re-reads and re-publishes). The category README covers prerequisites and teardown.