Configuration

bouine is configured via a YAML file passed with --config. Environment variable interpolation is supported: ${VAR} is replaced with the value of VAR, and ${VAR:-default} provides a fallback. $$ escapes to a literal $.

Pages in this section

Minimal working config

listen:
  http: ":8080"
  admin: ":9000"

storage:
  hot_max_bytes: 256MiB

upstream_pools:
  - name: app
    targets: ["app.default.svc:8080"]

routes:
  - match: { path_prefix: / }
    pool: app
    cache:
      ttl_default: 60s

Full example

listen:
  http: ":80"
  https: ":443"
  admin: ":9000"
  cluster: ":8443"

tls:
  certs:
    - cert_file: /etc/bouine/tls/cert.pem
      key_file: /etc/bouine/tls/key.pem
      sni: ["example.com", "*.example.com"]
  min_version: "1.2"

storage:
  hot_max_bytes: 2GiB
  warm_dir: /var/lib/bouine
  warm_max_bytes: 50GiB

cluster:
  join:
    - "bouine-0.bouine-headless.ns.svc.cluster.local:8443"
    - "bouine-1.bouine-headless.ns.svc.cluster.local:8443"
    - "bouine-2.bouine-headless.ns.svc.cluster.local:8443"
  hop_limit: 2

upstream_pools:
  - name: api
    targets: [api.default.svc:8080]
    tls:
      enabled: false
    health:
      active:
        path: /healthz
        interval: 5s
        timeout: 1s
        unhealthy_threshold: 3
      passive:
        consecutive_5xx: 5
    connect:
      timeout: 10s
      keep_alive: 15s

routes:
  - match: { host: "api.example.com", path_prefix: /v1/ }
    pool: api
    cache:
      ttl_default: 60s
      stale_while_revalidate: 30s
      stale_if_error: 300s
      negative_ttl: 5s
      jitter_percent: 10
      refresh_before_expiry: true
      refresh_margin_percent: 20
      refresh_timeout: 5s
      refresh_concurrency: 16
      refresh_min_hits: 3
      refresh_persist_cycles: 2
      refresh_min_score: 1048576
      refresh_max_rps: 100
      refresh_reactive_first: true
      key:
        include_headers:
          - Accept-Language
        exclude_headers:
          - x-request-id
          - x-trace-id
          - x-forwarded-for

Field reference

listen

FieldDefaultDescription
http":80"HTTP/1.1 plaintext listener
https""HTTPS (TLS) listener. See TLS.
admin":9000"Admin API (health, metrics, purge)
cluster""Gossip cluster port
max_connections0Max concurrent data-plane connections (0 = default 4096; Helm chart sets 4096 default / 8192 production / 16384 HA). Protects against FD exhaustion. Idle keep-alive connections hold a slot.
idle_timeout120sKeep-alive idle timeout for data-plane connections: how long a connection with no in-flight request stays open. Also used by the H1 fast-path parser, so the two stay in sync. With an upstream proxy or LB in front, keep its keep-alive idle timeout below this value so it closes idle connections first — otherwise bouine can close a connection mid-reuse and the upstream logs upstream prematurely closed connection.
tcp_fast_opentrue (Linux)Enable TCP_FASTOPEN on data-plane listeners. Defaults to true on Linux, no-op elsewhere.
tcp_defer_accepttrue (Linux)Enable TCP_DEFER_ACCEPT on data-plane listeners. Defaults to true on Linux, no-op elsewhere.
reuse_porttrue (Linux)Enable SO_REUSEPORT on data-plane listeners (N parallel accept loops). Defaults to true on Linux, false on other platforms.
tcp_quickacktrue (Linux)Enable TCP_QUICKACK on accepted data-plane connections to reduce latency by avoiding delayed ACKs. Defaults to true on Linux, no-op elsewhere.
read_timeout30sBounds how long reading a single request’s header and body may take, per request (since v0.5.9). This is the slowloris defense for clients that drip-feed bytes — it is not an end-to-end request deadline (origin fetches are bounded by fetch_timeout). Raise it for slow mobile clients or large uploads. Must stay below the 5-minute data-plane safety-net write timeout.

storage

FieldDefaultDescription
hot_max_bytesRAM cache size. See size units. Example: 2GiB.
eviction_algorithmsieveEviction policy for both tiers: sieve (visited-bit sweep) or cachaner (SIEVE + 3-bit frequency counter — up to 7 second chances for hot objects). Per-tier overrides: hot_eviction_algorithm, warm_eviction_algorithm.
hot_mmap_slabfalseUse mmap slab allocator for hot body bytes (reduces GC pressure, Linux only)
warm_dir""Path for mmap warm-tier segments. Empty disables. See Storage tiers.
warm_max_bytes""Max warm-tier disk usage
warm_max_entries— (auto)Max warm-tier entry count. Auto-derived from GOMEMLIMIT when unset.
warm_max_disk_bytes""Max total warm-tier disk usage (all segments)
min_free_disk""Minimum free disk space before warm writes are paused
warm_preallocate0Preallocate warm-tier segment files totaling this size at startup. Eliminates disk amplification from append-only segments. Zero = create on demand.
compact_interval30mInterval between warm-tier compaction sweeps. Set to -1 to disable periodic compaction (not recommended).
body_threshold64KiBBody size threshold for warm-tier admission. Objects larger than this are written to warm on every Put; smaller objects only by the background sync loop.
warm_sync_interval60sInterval between hot-to-warm sync batches
warm_sync_batch_size5000Max objects per warm sync batch
wal_sync_interval100msWAL fsync interval (async batching)
compact_startup_delay5mDelay before first compaction on startup. Prevents I/O contention with WAL replay and cluster join. Set to -1 to start immediately.
checkpoint_interval5mWarm-tier checkpoint interval
checkpoint_wal_threshold100000WAL entry count that triggers a checkpoint, regardless of interval. Bounds WAL replay time on unclean restart.
segment_cache_size0 (auto)Number of warm-tier segment files to keep mmap-ed. 0 = auto (min(segCount, 256)). -1 = unlimited (no eviction).
tombstone_queue_size65536Tombstone queue depth for warm-tier deletions. Increasing this reduces drops under bursty eviction pressure.
tombstone_drain_interval1sInterval between tombstone drain sweeps. Set to -1 to disable the dedicated drain goroutine.

cluster

FieldDefaultDescription
modestrongConsistency mode: strong or eventual. The cluster is enabled when listen.cluster is set. See Clustering.
join[]Seed addresses (StatefulSet pod DNS)
hop_limit2Max peer-fetch hops before origin fallback (strong mode only)
peer_max_conns_per_host8Pipelined peer connections per peer. Default 8, with 16 pending requests each, gives 128 concurrent peer fetches per peer. Set to 1 to disable pipelining.
peer_max_idle_conn_duration120sHow long idle peer RPC connections are kept before closing. Must stay below admin.idle_timeout (default 300s) — config validation rejects any explicit value that violates the ordering, because a peer request sent on a connection the admin server already reaped fails with EOF and falls back to origin.
peer_fetch_concurrency4Bounds concurrent peer-fetch and peer-put RPCs per node (since v0.5.11, range 1–128). In strong mode most cache hits are peer hits, so this semaphore sits on the hot path; raise it together with peer_max_conns_per_host under load to cut peer-hit tail latency.
ban_ttl24hHow long a lazy invalidation ban stays in the active ban list before the reaper prunes it (since v0.5.20, must be ≥ 1s when set). RFC 9111 §4.4 exempts objects stored after the ban, so cache-lifecycle surrogate invalidations are safe at minutes scale — lower it to bound the hit-ratio damage of an over-broad ban.
join_timeout120sMax time to wait for cluster join. In strong mode, the pod stays not-ready if join fails. In eventual mode, the pod becomes ready and retries in the background.
handoff_queue_depth4096Memberlist per-peer message buffer. Absorbs bursts of cache invalidations. Negative values are rejected.
tls.ca_bundle""CA certificate path for peer-to-peer mTLS. Empty = plain HTTP.
tls.cert_file""Client certificate for mTLS
tls.key_file""Client private key for mTLS

routes[]

Route matching uses host, path_prefix, and optionally methods. Routes are matched in declaration order; the first match wins. Regex-based path matching is not supported in routes — use path_regex in ban predicates for invalidation.

A route must specify exactly one of pool or static.root. The former proxies to an upstream pool; the latter serves files from a local directory. See Static file serving.

FieldDefaultDescription
name""Human-readable label used in Prometheus route label and the dashboard. Defaults to host:path_prefix when empty.
match.host""Match on Host header (empty = any)
match.path_prefix""Match on URL path prefix (empty = any)
match.methods[]Restrict to listed HTTP methods, e.g. [GET, HEAD]. Empty = all methods. Normalised to upper-case. Lets you give GET and POST on the same path independent cache policies.
poolUpstream pool name. Required unless static.root is set.
static.root""Absolute path to a directory to serve files from. Required unless pool is set. See Static file serving.
static.index[]Index files to try (in order) when the request path maps to a directory, e.g. [index.html].
static.max_file_size10MiBPer-file size cap. Files larger than this are rejected with 413.

routes[].request

FieldDefaultDescription
header_set{}Headers to set on the upstream request. Rewrites apply on every origin fetch (miss, revalidation, invalidating methods, background refresh) — enforced on all emit paths since v0.5.19.
header_remove[]Headers to remove from the upstream request
strip_prefix""Strip this path prefix before forwarding to the upstream (e.g. /api/v1/users/users). Must start with /. The cache key still uses the original path.

routes[].cache

FieldDefaultDescription
enabledtrueSet to false to bypass caching for this route
ttl_default0Default TTL when origin has no Cache-Control
ttl_override0Force bouine’s internal TTL regardless of upstream Cache-Control/Expires; upstream headers are forwarded unaltered. See TTL override.
stale_while_revalidate0Serve stale while refreshing in background
stale_if_error0Serve stale on origin 5xx
negative_ttl0Cache 404/405/410/501 responses for this duration
jitter_percent0Random ±N% on TTLs to prevent stampedes (0–50)
stayin_alivefalseServe stale indefinitely when upstream is down (see Stayin Alive)
allow_set_cookiefalseAllow caching responses that carry Set-Cookie. Default blocks caching such responses (nginx-style). When true, the response is cached but Set-Cookie is stripped from the stored copy. See Set-Cookie caching.
max_object_size0Skip caching responses whose body exceeds this size (e.g. 1MiB). The response is still proxied. 0 = no limit.
max_response_bytes64MiBHard cap on bytes buffered per origin fetch. Aborts the fetch (502) when exceeded. Different from max_object_size which controls caching eligibility. Default derives from GOMEMLIMIT (7%) or a built-in 64 MiB floor.
max_fetch_concurrency32Max concurrent foreground origin fetches per route (collapsed via singleflight). Excess requests wait up to fetch_wait_timeout for a slot, then shed.
fetch_timeoutinherits connect.response_header_timeout (30s)The authoritative per-route origin timeout (header + body), starting once a fetch slot is acquired (semantics since v0.5.11). When unset, the route inherits the pool’s connect.response_header_timeout; when set, the value is enforced verbatim in either direction — a route may exceed the pool-wide knob to give a slow endpoint more time without raising the wait for every other route. Must stay below the 5-minute data-plane safety-net write timeout.
fetch_wait_timeout100msHow long a foreground miss waits for an origin-fetch slot (bounded by max_fetch_concurrency) before shedding: stale object served if one is in scope, otherwise 503 + Retry-After: 1. Validated range: 0–1s. Independent of fetch_timeout. See Streaming and live responses.
max_streaming_buffer_bytesauto (GOMEMLIMIT × 7%, floor 64MiB)Total bytes held in live streaming tee buffers across concurrent miss-fetches on this route. When exceeded, new cacheable misses fall back to synchronous buffering instead of streaming.
refresh_before_expiryfalseEnable proactive background conditional revalidation before TTL expiry. See Refresh before expiry.
refresh_margin_percent10Percentage of TTL before expiry at which the background refresh fires (1–50). E.g. 20 fires at 80% of TTL.
refresh_timeout10sMaximum duration for a single background refresh fetch (5s–120s)
refresh_concurrency8Maximum concurrent background refresh fetches per route (1–64)
refresh_min_hits0Minimum cache hits during a TTL window for an object to qualify for re-scheduling after a refresh. 0 disables the gate. See Popularity gates.
refresh_persist_cycles0Additional TTL cycles to keep refreshing after the popularity gate would block. Requires refresh_min_hits > 0. See Persist cycles.
refresh_min_score0Minimum refresh priority score (staleHits × bodySize) for re-scheduling. Requires refresh_min_hits > 0. See Popularity gates.
refresh_max_rps0Caps background refresh fetches per second per route (0 or 1–10000). 0 = no limit. See Rate limiting.
refresh_reactive_firstfalseSWR-first mode: new objects rely on stale-while-revalidate instead of proactive refresh. Requires stale_while_revalidate > 0 and refresh_min_hits > 0. See Reactive-first mode.

routes[].cache.key

FieldDefaultDescription
include_headers[]Headers to include in cache key (replaces Vary)
exclude_headers[]Request header names to strip from the Vary-based variant key, preventing cache fragmentation from per-request headers like X-Request-Id. Matched case-insensitively. See Excluding headers.
strip_query_params[]Query parameter names to exclude from the cache key, e.g. [utm_source, fbclid]. The params are still forwarded to the upstream. See Stripping query parameters.
keep_query_params[]When non-empty, restricts the cache key to only these query parameters; all others are excluded. Mutually exclusive with strip_query_params and strip_query_prefix. Equivalent to Varnish qs.keep().
strip_query_prefix[]Strip query params whose names start with any of these prefixes (e.g. [utm_, fb_, _ga]). Covers wildcard stripping without enumerating every variant. Capped at 16 entries.
strip_empty_paramsfalseRemove query params with empty values (?foo=&bar=1?bar=1). Does not apply to params in keep_query_params.
dedup_query_paramsfalseKeep only the first value for duplicate query params (?a=2&a=1?a=2). Values are not sorted.

canonicalize_path was removed in v0.5.19. The knob was parsed and documented but its listener-level wiring never landed, so it had no effect; configs setting it now fail at load time with the strict loader. Remove the key from your config when upgrading.

upstream_pools[].connect

All fields are optional; a zero/empty value applies the built-in default, so existing configs keep their current behaviour.

FieldDefaultDescription
timeout10sTCP dial timeout
keep_alive30sTCP keep-alive probe interval on origin connections
max_connections64Max concurrent connections per origin host (fasthttp MaxConnsPerHost). Per host, not per pool: a pool with N targets gets N × max_connections. Bounds FD consumption under slow origins.
max_idle_conn_duration90sHow long an idle pooled origin connection is kept before closing. Keep this below any LB idle timeout between bouine and the origin (e.g. AWS NLB 350s) so bouine closes idle connections first.
response_header_timeout30sMax time to wait for response headers from upstream. Zero applies a 30s built-in default. Primary defence against slow-origin resource exhaustion — and, since v0.5.11, the default origin timeout (header + body) for every route on the pool that does not set its own cache.fetch_timeout.
hedge_timeout0 (disabled)Hedged fetch (since v0.5.19): fire a duplicate request to the same pool when the primary does not respond within this duration; the first response wins. Only applies to idempotent methods (GET, HEAD, OPTIONS). Zero disables hedging. See Hedged fetch.

Health checks

Active health probes the upstream periodically. Passive ejects after consecutive failures.

health:
  active:
    path: /healthz
    method: GET          # default
    interval: 5s
    timeout: 1s
    healthy_threshold: 1
    unhealthy_threshold: 3
    expected_status_codes: [200]
  passive:
    consecutive_5xx: 5
    eject_for: 30s

passive.eject_for restores passively ejected targets once the window elapses and re-ejects them automatically if they are still broken (enforced since v0.5.19; restores are counted in the origin_restores_total metric with a source label).

admin

FieldDefaultDescription
token"" (auto-generated)Admin bearer token. See Authentication.
max_batch_size1000Max URLs per /v1/purge/batch request
idle_timeout300sKeep-alive idle timeout for admin-server connections, including cluster peer RPCs (/v1/peer/*). Peer clients default to a 120s idle duration, so they close idle connections before the admin server reaps them; keep cluster.peer_max_idle_conn_duration below this value.
rate_limit_per_second0Rate limit on admin write endpoints (0 = no limit)
pprof_enabledfalseEnable /debug/pprof/* profiling endpoints
drain_duration10sDuration the /drain endpoint blocks during shutdown (K8s preStop hook)

tracing

Configure OpenTelemetry span export. Leave endpoint empty (default) to disable tracing.

FieldDefaultDescription
endpoint""OTLP/HTTP collector URL, e.g. http://otel-collector:4318. Empty disables.
service_name"bouine"service.name OTel resource attribute
sampling_rate1.0Fraction of requests to sample (0.0–1.0)

cloudflare

Optional Cloudflare Cache API propagation. See Cloudflare CDN propagation for full details and Kubernetes secret wiring.

FieldDefaultDescription
zone_id""Cloudflare zone identifier (non-secret)
api_token""Cache Purge API token. Prefer CF_API_TOKEN env var.
api_tokens[]Additional API tokens for rate-limit spreading (also injectable via CF_API_TOKENS, comma-separated). The client rotates across all tokens to multiply the effective rate-limit budget.
asynctrueReturn immediately; CF call runs in background goroutine
timeout10sPer-call timeout for CF API requests
batch.max_batch_size0 (passthrough)Max items coalesced per CF API call when > 0. Purges are deduplicated and batched.
batch.max_wait500msMax time a batched item waits before a flush
circuit.enabledfalseCircuit breaker: fail fast during CF API outages
circuit.failure_threshold5Consecutive failures before the circuit opens
circuit.open_timeout30sTime before probing again
circuit.half_open_max_calls1Probe calls allowed in half-open state
retry.enabledfalseDead-letter queue for failed CF purges: retried with exponential backoff so transient CF outages don’t lose invalidations
retry.max_queue_size1000Max items in the retry queue; new failed items are dropped when full
retry.max_retries3Retry attempts per item
retry.base_delay1sInitial retry delay (grows exponentially)
retry.max_delay30sRetry backoff cap
propagate.purgetrueForward POST /v1/purge to CF PurgeSingleFile
propagate.bantrueForward POST /v1/ban to CF (tags / prefixes / hostnames)
propagate.refreshtrueForward POST /v1/refresh to CF PurgeSingleFile

experimental

Opt-in features that are not yet stable. All fields default to off. See Experimental features.

FieldDefaultDescription
h1_fast_pathfalseEnable custom HTTP/1.1 parser for zero-allocation cache hits. Eliminates *http.Request and http.ResponseWriter construction on the hit path (~40% CPU reduction, 0 allocations). Misses and non-GET/HEAD requests fall through to the standard fasthttp handler. See Experimental features.
h1_reactorfalseEnable the single-goroutine epoll event loop that batch-serves cache hits without per-request goroutine park/unpark (Linux only; requires h1_fast_path). See Experimental features.
h1_fast_peer_pathfalseServe peer-fetched objects directly on the H1 fast path without falling through to the slow path (since v0.5.20; requires h1_fast_path and a strong-mode cluster; not wired under h1_reactor). See Experimental features.

Top-level fields

FieldDefaultDescription
gogc100Go GC percentage. Set to -1 to disable percentage-based GC, relying solely on GOMEMLIMIT.
url_ring_sample_rate01-in-N sampling for the dashboard URL ring buffer. 0 = record every non-HIT request. 100 = 1 in 100 (reduces sync.Map overhead under high miss rates). 1 = record every call (debug mode).

Hedged fetch

Set upstream_pools[].connect.hedge_timeout to hedge slow origins (wired into the fetch path since v0.5.19). When the primary request does not get a response within hedge_timeout, bouine fires a duplicate request to the same pool and returns whichever response arrives first. Only idempotent methods (GET, HEAD, OPTIONS) are hedged — SSE and non-idempotent requests never duplicate. Zero (the default) disables hedging.

upstream_pools:
  - name: api
    targets: [api.default.svc:8080]
    connect:
      hedge_timeout: 250ms

Size units

All byte-size fields (hot_max_bytes, warm_max_bytes) accept any of these suffixes (case-insensitive):

SuffixMultiplierFamily
B1exact
K, KB10³SI decimal
KiB, KI1 024IEC binary
Ko10³French SI
M, MB10⁶SI decimal
MiB, MI1 048 576IEC binary
Mo10⁶French SI
G, GB10⁹SI decimal
GiB, GI1 073 741 824IEC binary
Go10⁹French SI
T, TB10¹²SI decimal
TiB, TI2⁴⁰IEC binary
To10¹²French SI

Recommendation: Use IEC binary units (MiB, GiB) for clarity. The Helm chart defaults use GiB.


Config updates

bouine does not support live config reload. All config changes require a process restart. On Kubernetes, use a rolling restart:

kubectl rollout restart statefulset/bouine

For TLS certificate rotation, restart the process or use Kubernetes rolling restarts with cert-manager.