Files
reverse-proxy/docs/architecture/decisions/008-static-dynamic-config-split.md
glm-5.1 8ee6284b62 Add architecture specification for Rust/axum reverse proxy
Phase 1 architecture docs covering proxy handler, TLS termination (ACME +
manual), TOML config with static/dynamic split (ArcSwap), and operations
(rate limiting, logging, health check, systemd, graceful shutdown).

Nine ADRs documenting key decisions: Rust/axum, custom proxy handler,
TOML config, rustls-acme for cert management, tokio-rustls direct,
token bucket rate limiting, custom log format for fail2ban,
static/dynamic config split, and signal handling strategy.

Includes threat landscape research documenting the nginx CVEs motivating
this project.
2026-06-11 07:25:50 +00:00

2.8 KiB

ADR-008: Static/Dynamic Configuration Split with ArcSwap

Status

Accepted

Context

The proxy needs configuration that can be partially reloaded at runtime (site definitions, rate limits) without restarting the process and dropping active connections. However, some configuration (bind addresses, TLS mode) fundamentally requires creating new listeners and cannot be changed at runtime.

Two approaches:

  • Full restart for all config changes: Simple, but requires dropping active connections for every change, including trivial rate limit adjustments.
  • Static/dynamic split: Immutable parameters (bind address, TLS mode) in a StaticConfig that requires restart; runtime-adjustable parameters (sites, rate limits) in a DynamicConfig that can be atomically swapped via Arc<ArcSwap<DynamicConfig>> without dropping connections.

This pattern is proven in the alknet project, which uses the same ArcSwap<DynamicConfig> approach for auth policy, forwarding rules, and rate limits.

Decision

Split configuration into StaticConfig (immutable after startup) and DynamicConfig (hot-reloadable via ArcSwap). The split is:

StaticConfig (restart required):

  • Bind address, HTTP port, HTTPS port
  • TLS mode (ACME vs. manual), cert paths, ACME settings
  • Log level and format

DynamicConfig (hot-reloadable via SIGHUP):

  • Site definitions (hostname → upstream mappings)
  • Rate limits (requests per second, burst)
  • Body size limits

ConfigReloadHandle provides a reload(DynamicConfig) method that atomically swaps the entire config. All request handlers read DynamicConfig via ArcSwap::load() — a lock-free operation.

Rationale

  • Rate limits and site definitions change more frequently than bind addresses and TLS settings. Hot-reload avoids unnecessary downtime.
  • ArcSwap provides lock-free reads and atomic writes — no partial updates, no lock contention on the hot path.
  • Proven pattern from alknet, where it's used for auth policy, forwarding rules, and rate limits.
  • SIGHUP trigger is simple, well-understood, and compatible with systemd and process supervisors.
  • The entire config is swapped at once, preventing inconsistent states where some sites use the old config and others use the new one.

Consequences

Positive:

  • Zero-downtime config reload for sites and rate limits
  • Lock-free reads on the request hot path
  • Atomic config updates — no partial states
  • Proven pattern from alknet

Negative:

  • Two config types add conceptual complexity
  • SIGHUP reload requires reading the config file from disk (need to handle file read errors gracefully)
  • Must validate DynamicConfig before swapping (invalid config must not replace valid config)

References

  • config.md
  • alknet ADR-030 (static/dynamic config split)