You are reading documentation for bouine v0.5.x — not the latest version. View latest →

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_openfalseEnable server-side TCP_FASTOPEN on data-plane listeners (Linux only, no-op elsewhere). Default changed from enabled to disabled in v0.5.29 — deployments that relied on the old default must set true explicitly. With a kernel-cached TFO cookie the request bytes ride in the client’s SYN, saving one RTT on fresh connections. Advisory: accepts never fail because of it. See TCP Fast Open.
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_bytes—RAM 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_concurrency16Bounds concurrent peer-fetch and peer-put RPCs per node (since v0.5.11, range 1–128; default raised from 4 to 16 in v0.5.29 — four slots sat at the queueing knee on 3-node clusters of the 6k req/s class, where any peer-RPC slowdown cascaded into shed-runaway and origin ejections). 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.
peer_tcp_fast_openfalseEnable outbound TCP Fast Open (TCP_FASTOPEN_CONNECT) on peer-bound dials — peer fetch/put and the invalidation-broadcast client (since v0.5.29). With a kernel-cached TFO cookie the first RPC bytes ride in the SYN, saving one RTT on every fresh peer connection. Opt-in, Linux 4.11+ only, no-op elsewhere; bouine↔bouine peer dials use TFO end-to-end when the dialing side sets this flag, the answering side sets listen.tcp_fast_open, and each peer’s kernel has net.ipv4.tcp_fastopen including the server bit. Advisory: dials never fail because of it.
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 (exact or a leading-*. wildcard since v0.5.27), path_prefix or path (an anchored RE2 regex, since v0.5.27), and optionally methods. Routes are matched in declaration order; the first match wins. A later route fully covered by an earlier one (host, path, and methods all covered) is dead config and is reported at Error level at boot while boot proceeds. Ban predicates still use their own path_regex — see ban predicates.

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 the host + path predicate when empty (a match.path regex is shown verbatim).
match.host""Match on Host header (empty = any). Since v0.5.27 a leading *. performs a suffix match anchored on the label boundary: *.staging.example.com covers abc.staging.example.com and the whole per-PR subdomain tree, never the bare staging.example.com or evilstaging.example.com. Any wildcard that is not the single leading *. form is rejected at config validation.
match.path_prefix""Match on URL path prefix (empty = any)
match.path""Anchored RE2 pattern matched against the request path, query excluded, e.g. ^/[a-z]{2}-[a-z]{2}/l/campaign-.*$ (since v0.5.27). Mutually exclusive with path_prefix. Validation rejects invalid regexes, unanchored patterns, patterns over 512 B, and control bytes. Regexes compile once at startup, so route resolution never pays compile cost; prefix-only route tables keep their zero-alloc hit path. Reduces route-label and config cardinality versus the per-host/per-page route explosion they replace.
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.
pool—Upstream 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.
path_rewrite—Per-route regex path rewrite applied to the origin-bound request path (since v0.5.21) — the nginx rewrite ... break / Varnish regsub equivalent. match (Go RE2, linear time) and replace ($1 capture references, ${1}x braced disambiguation) are compiled once at config load and applied exactly once on every origin-bound fetch: miss, bypass, invalidating methods, revalidation, background refresh, streams. The query string is never matched or modified; only the first match is replaced (nginx semantics); pattern and template are capped at 512 B; every $reference must resolve against the pattern’s groups at config load. Mutually exclusive with strip_prefix (validation rejects both). The cache key, ban matching, purges, and all client-facing surfaces keep the original public path.
forwardedoffClient-identity header injection on origin-bound fetches (since v0.5.27): X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host, and Via. Accepts four YAML shapes — the standard preset, a token list ([client_ip, proto]), the opt-out (false/none), or a full mapping with max_append (default 5, range 1–64). Never applied on the hit path, never part of the cache key or stored request headers. Requires a pool; mutually exclusive with header_set entries targeting the same header. Declare once for every route with route_defaults.request.forwarded. See Forwarded headers.

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. Since v0.5.29 the override applies to success and redirect responses only (status < 400) — storable error responses follow the negative-caching TTL ladder (origin explicit freshness > negative_ttl > heuristic > ttl_default) instead of being pinned fresh for the override duration. 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. Since v0.5.22 also accepts a per-status map — negative_ttl: {404: 1m, 5xx: 10s, 410: 0} (exact codes 400–599, classes 4xx/5xx, exact shadows class, 0 disables that status) — which outranks ttl_default/heuristic freshness for the statuses it covers. See Negative caching.
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.
bypass_on_cookiefalseWhen true, a request carrying any non-empty Cookie header never touches the cache on this route: no lookup, no storage, no in-flight sharing — it proxies to origin (X-Cache: BYPASS). The Varnish return (pass) equivalent for req.http.Cookie; designed for personalized SSR routes whose origin renders per-user content from the request cookie (since v0.5.26). The trigger is Cookie-header presence, not a name list. See Cookie bypass.
bypass_on_cookie_names[]Cookie names that route a request carrying any of them entirely around the cache — the same no-lookup/no-storage/no-in-flight contract as bypass_on_cookie, scoped to the listed names (since v0.5.27). Requests carrying only unlisted cookies participate in the cache per RFC 9111. Matching is case-insensitive on the cookie-name token only, in every ; -separated position and on every repeated Cookie field line. Capped at 16 names; mutually exclusive with bypass_on_cookie. See Cookie-name bypass.
bypass_on_user_agent[]User-Agent *-glob patterns that route matching requests entirely around the cache — no lookup, no storage, no in-flight sharing; the request proxies to origin with X-Cache: BYPASS (since v0.5.27). Patterns match the full UA string case-insensitively; * matches any run of bytes including /; an exact pattern matches the whole string (*Bot* for substring semantics). Capped at 16 entries / 256 B each. Declare once via route_defaults.cache.bypass_on_user_agent. See User-Agent bypass.
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.
max_variants1024 (built-in)Cap on distinct Vary variants stored per primary cache key on this route (since v0.5.25). Unset/0 applies the built-in default; negative values are rejected; the cap cannot be disabled. When hit, further variant storage is skipped and vary_cap_hits_total increments — requests keep being proxied. See Variant caps.
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[]Request header names added to the Vary-based variant key exactly as if the origin had listed them in Vary (unioned at object-build time, never a replacement — since v0.5.21). A listed header absent from the request hashes as an empty value. Validation rejects *, non-token, duplicate, and exclude_headers-overlapping entries; max 16. Must be identical on every cluster node serving the route. See Cache key headers.
include_hosttrueExplicitly false drops the host segment from the primary cache key (since v0.5.22): the same URL+query resolves to one entry regardless of request Host. Requests still forward the client’s Host; only key computation changes. Absent/true keeps the key byte-for-byte. Rejected on routes that set match.host. Must be identical across cluster nodes serving the route. See Dropping the host from the key.
verbatim_encodingfalsetrue restores the pre-bucketing Accept-Encoding keying: the raw value is lowercased+sorted and forwarded to the upstream unchanged. Use only when the origin varies response bodies by the full AE string — it re-fragments the variant space (since v0.5.22). Must be identical across cluster nodes. See Accept-Encoding bucketing.
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.
cookie_presence[]Cookie names whose presence (never the value) contributes one bit each to the Vary variant key (since v0.5.27) — for origins that render different content depending on whether a consent/analytics cookie exists. Presence rides a synthetic Vary field, so store/lookup pairing, peer gates, 304 revalidation, and background-refresh replay hash the same bits everywhere; repeated Cookie lines are canonicalized to the RFC 6265 §4.2 form before the bits are computed. Capped at 16 names (N names = up to 2^N presence combinations); mutually exclusive with both bypass knobs. Must be identical on every cluster node serving the route. See Cookie presence keying.
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.

route_defaults

Declare per-route blocks once instead of repeating them on every route (since v0.5.27). Values are merged into every route before validation; only the two blocks with defined merge semantics are accepted — any other route field under route_defaults is rejected at decode time.

route_defaults:
  request:
    forwarded: standard        # inherited by every route that does not opt out
  cache:
    bypass_on_user_agent: ["*ShoppingFeedBot*"]
FieldMerge semantics
request.forwardedA route without its own forwarded block inherits the default wholesale; a preset or token list replaces the default’s flags (only max_append falls back); the mapping form ORs field-by-field with the default — use the token-list form to drop a single header; forwarded: false/none opts the route out. Static routes ignore the inherited default (no origin-bound request exists); validation (pool requirement, header_set conflict, max_append bounds) runs on the resolved value.
cache.bypass_on_user_agentEvery pool route without its own list inherits the default wholesale; a route’s own list replaces it (never a union); an explicit empty list (bypass_on_user_agent: []) opts the route out; static routes inherit nothing. An invalid default is reported once at route_defaults’ own path.

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. Since v0.5.29 a fetch that times out (dial, response-header, or per-route deadline) surfaces to clients as 504 Gateway Timeout, not 502 — every other upstream failure keeps 502.
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.
tcp_fast_openfalseEnable outbound TCP Fast Open (TCP_FASTOPEN_CONNECT) on origin dials for this pool (since v0.5.29) — the pool’s shared and SSE clients set the option before connect, so with a kernel-cached TFO cookie the first request bytes ride in the SYN, saving one RTT on every fresh origin connection. Opt-in, Linux 4.11+ only, no-op elsewhere; origins without TFO never issue a cookie and the dial silently falls back to the regular handshake, so there is no penalty for enabling it. See TCP Fast Open.
preserve_hostfalseKeep the request’s own Host header on origin-bound fetches instead of replacing it with the pool target (since v0.5.28). Only the wire-level Host header changes — the dial target stays the configured pool target, and the cache key, Vary variant key, X-Bouine-Host attribution, ban/purge matching, and request.forwarded are untouched. Enable for origins that derive behaviour from the request Host (market/country selection, virtual-host routing); host-blind origins keep the historical behaviour, and the host token of request.forwarded remains the mechanism for origins that read X-Forwarded-Host.

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).

metrics

FieldDefaultDescription
traffic_classes[]Up to 8 named traffic populations matched by request-host patterns (since v0.5.27). Adds an always-present traffic_class label to bouine_requests_total, bouine_request_duration_seconds, and bouine_response_bytes_total, plus a matching traffic_class access-log attribute — enabling per-population hit-ratio, latency, and saved-bandwidth queries inside one instance. Each class carries a unique lowercase name (the reserved unclassified is rejected; it is the fallback for requests matching no class) and up to 64 host patterns: an exact host, a leading *. (suffix match), or a trailing .*/* (prefix match). Declaration order is precedence — first match wins — and a later class fully shadowed by an earlier one is reported at Error level at boot while boot proceeds.
metrics:
  traffic_classes:
    - name: checkout
      hosts: ["checkout.example.com"]
    - name: marketing
      hosts: ["*.example.com"]   # suffix match on the label boundary

Every invalid field is reported at once, each anchored to its own metrics.traffic_classes[i].name / .hosts[j] path. See Monitoring → traffic classes for the label semantics and the series-cardinality note.

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).
route_defaults—Declare request.forwarded and cache.bypass_on_user_agent once for every route instead of repeating them per route. See route_defaults.
metrics.traffic_classes[]Named host-matched traffic populations for the traffic_class metric label. See metrics.

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

Forwarded headers

routes[].request.forwarded (since v0.5.27) injects client-identity headers onto origin-bound requests only — miss, invalidating proxy (POST/PUT/DELETE), bypass, revalidate, streaming/SSE, and the background fetchers. The cache-hit path is untouched, and the headers never join the cache key or the stored request headers.

HeaderTokenBehaviour
X-Forwarded-Forclient_ipAppends the address of bouine’s immediate peer (the edge) to the carried chain — existing entries are never rewritten. Client-supplied chains are append-only untrusted input; a spoofed chain merely costs an origin fetch.
X-Forwarded-ProtoprotoSet to the scheme bouine received the request on (https on a TLS listener, http otherwise) — not the scheme used towards the origin.
X-Forwarded-HosthostSet to the Host header bouine received, replacing any client-supplied value. Tells multi-host origins which hostname was requested, and matters wherever the outbound Host is rewritten to a pool target — pair with connect.preserve_host.
ViaviaAppends 1.1 bouine per RFC 9110 §7.6.3, complementing the internal cluster hop limit.

Four YAML shapes:

request:
  forwarded: standard           # all four headers, one line
  forwarded: [client_ip, proto]  # exactly these headers, no implicit extras
  forwarded: false              # explicit opt-out (kills a route_default)
  forwarded:                    # full mapping, field-by-field
    client_ip: true
    proto: true
    max_append: 10              # chain cap, default 5 (range 1–64)

The XFF and Via chains are capped at max_append entries (the rightmost — nearest — entries are kept) and the 8 KiB per-header budget. Background fetches (SWR revalidation, refresh-before-expiry, shed refill) inject proto/host/Via from the captured request but deliberately never append X-Forwarded-For — there is no live peer, and replaying the original requester’s address would attribute one user’s identity to an anonymous refresh. forwarded requires a pool (rejected on static routes) and is mutually exclusive with header_set entries targeting the same header.


TCP Fast Open

bouine supports TCP Fast Open on three surfaces, all disabled by default since v0.5.29 (previously an unset listen.tcp_fast_open meant “enabled on Linux”):

SurfaceKeyDirection
Data-plane listenerslisten.tcp_fast_openServer-side TFO on accepts
Origin dialsupstream_pools[].connect.tcp_fast_openClient-side TCP_FASTOPEN_CONNECT per pool
Peer dialscluster.peer_tcp_fast_openClient-side TCP_FASTOPEN_CONNECT on peer fetch/put and invalidation broadcast

With a kernel-cached TFO cookie, the first request bytes ride in the SYN packet — one full RTT saved on every fresh connection. The option is advisory: peers or origins without server-side TFO never issue a cookie and the dial silently falls back to the regular handshake, so there is no penalty for enabling it. Linux only (4.11+ for the client side; no-op elsewhere); the kernel is probed once at startup and an unsupported option is left unwired with a single warn line. bouine↔bouine peer dials use TFO end-to-end when both sides opt in — cluster.peer_tcp_fast_open on the dialing side, listen.tcp_fast_open on the answering side — and peers’ kernels need net.ipv4.tcp_fastopen to include the server bit. Per-socket failures are best-effort and never fail a dial.

Upgrade note (v0.5.29): deployments relying on the old listener-side default must set listen.tcp_fast_open: true explicitly after upgrading.


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.