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

76 lines
2.8 KiB
Markdown

# 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](../config.md)
- alknet ADR-030 (static/dynamic config split)