Environment Variables Reference
Complete reference for all Felix environment variables, organized by category.
Overview
Section titled “Overview”Felix uses environment variables prefixed with FELIX_ for configuration. These variables provide quick overrides without modifying config files.
Priority: Environment variables override built-in defaults but are overridden by YAML config files.
Network and Binding
Section titled “Network and Binding”FELIX_QUIC_BIND
Section titled “FELIX_QUIC_BIND”Description: QUIC listener bind address and port (UDP).
Type: SocketAddr format
Default: 0.0.0.0:5000
Example:
export FELIX_QUIC_BIND="0.0.0.0:5000"export FELIX_QUIC_BIND="127.0.0.1:5001" # Localhost onlyexport FELIX_QUIC_BIND="10.0.1.5:5000" # Specific interfaceNotes:
- Must be a valid IP:Port combination
- UDP port for QUIC transport
- Use
0.0.0.0to bind all interfaces - With
FELIX_QUIC_LISTENERSabove 1, this is the first port of a consecutive run
FELIX_QUIC_LISTENERS
Section titled “FELIX_QUIC_LISTENERS”Description: How many client-facing QUIC listeners to bind, on consecutive
ports starting at FELIX_QUIC_BIND.
Type: Integer (at least 1)
Default: 1
Example:
export FELIX_QUIC_BIND="0.0.0.0:5000"export FELIX_QUIC_LISTENERS=4 # binds 5000, 5001, 5002, 5003Notes:
- Why it exists: one UDP socket is one QUIC endpoint, and that endpoint’s driver is a single task that reads every inbound datagram and routes it by connection id. It cannot use more than one core, and it is the per-broker throughput ceiling — measured at ~88% of one core while the rest of the machine idled. Separate ports are separate sockets, which are separate drivers.
- The broker advertises the port set during authentication, and a client spreads its connection pools across it. A client that predates this ignores the advertisement and keeps using the single address it dialled.
- Every port in the range must be open in firewalls, security groups and
service definitions — not just
FELIX_QUIC_BIND. FELIX_INTERNAL_BINDmust sit outside the range. Startup fails if it does not, since peer traffic and client traffic must not share a listener.- Startup also fails if the range would run past port 65535, rather than binding fewer listeners than asked for.
- Adding brokers remains the horizontal lever; this raises what one broker can do before you need another.
- Do not pin
FELIX_IO_RUNTIME_THREADSbelow one runtime per listener plus one. It is derived from this setting, and a pool too small puts every listener’s driver back on a single thread. Startup refuses that combination.
FELIX_TLS_CERT_EXPORT
Section titled “FELIX_TLS_CERT_EXPORT”Description: Write the broker’s generated self-signed certificate to this path (PEM) at startup, so clients can trust it explicitly.
Type: File path
Default: unset — no certificate is written
Example:
export FELIX_TLS_CERT_EXPORT="/tmp/felix-dev-ca.pem"Notes:
- Development only. The broker generates a self-signed certificate at startup; without exporting it, the only way for a non-Rust client to connect is to skip verification entirely, which is a habit worth not forming.
- Point a client at the file: the Python client takes
ca_file=, and other clients take whatever their TLS stack calls a CA bundle. - Startup fails if the file cannot be written. A deployment that asked for the export has clients configured to read it, and coming up without it turns into connection failures far from their cause.
- Not a substitute for real certificates. Operator-supplied broker certificates are not wired up yet.
FELIX_BROKER_METRICS_BIND
Section titled “FELIX_BROKER_METRICS_BIND”Description: HTTP metrics and health endpoint bind address.
Type: SocketAddr format
Default: 0.0.0.0:8080
Example:
export FELIX_BROKER_METRICS_BIND="0.0.0.0:8080"Exposed endpoints:
/healthz: Health check/metrics: Prometheus metrics (when telemetry enabled)
Control Plane
Section titled “Control Plane”FELIX_CONTROLPLANE_URL
Section titled “FELIX_CONTROLPLANE_URL”Description: Control plane base URL for metadata synchronization.
Type: String (URL)
Default: None
Example:
export FELIX_CONTROLPLANE_URL="http://felix-controlplane:8443"export FELIX_CONTROLPLANE_URL="https://cp.example.com:8443"Usage:
- Optional for single-node deployments
- Required for multi-broker clusters
- Include scheme (
http://orhttps://)
FELIX_CONTROLPLANE_SYNC_INTERVAL_MS
Section titled “FELIX_CONTROLPLANE_SYNC_INTERVAL_MS”Description: Control plane polling interval in milliseconds.
Type: Unsigned integer
Default: 2000
Example:
export FELIX_CONTROLPLANE_SYNC_INTERVAL_MS="2000"export FELIX_CONTROLPLANE_SYNC_INTERVAL_MS="500" # Fast pollingexport FELIX_CONTROLPLANE_SYNC_INTERVAL_MS="10000" # Slow pollingPublishing Configuration
Section titled “Publishing Configuration”FELIX_ACK_ON_COMMIT
Section titled “FELIX_ACK_ON_COMMIT”Description: Enable publish acknowledgements after commit.
Type: Boolean
Default: false
Accepted values: 1, true, yes (case-insensitive) = enabled
Example:
export FELIX_ACK_ON_COMMIT="true"export FELIX_ACK_ON_COMMIT="1"export FELIX_ACK_ON_COMMIT="yes"Trade-off:
false: Fire-and-forget, lower latencytrue: Explicit acks, higher latency guarantee
FELIX_MAX_FRAME_BYTES
Section titled “FELIX_MAX_FRAME_BYTES”Description: Maximum frame size accepted on QUIC streams.
Type: Positive integer (bytes)
Default: 16777216 (16 MiB)
Example:
export FELIX_MAX_FRAME_BYTES="16777216" # 16 MiBexport FELIX_MAX_FRAME_BYTES="33554432" # 32 MiBexport FELIX_MAX_FRAME_BYTES="8388608" # 8 MiBNotes:
- Value of
0uses default - Affects max message size
- Must align with client configuration
FELIX_PUBLISH_QUEUE_WAIT_MS
Section titled “FELIX_PUBLISH_QUEUE_WAIT_MS”Description: Maximum wait time when publish queue is full.
Type: Positive integer (milliseconds)
Default: 2000
Example:
export FELIX_PUBLISH_QUEUE_WAIT_MS="2000"export FELIX_PUBLISH_QUEUE_WAIT_MS="5000" # More patientexport FELIX_PUBLISH_QUEUE_WAIT_MS="500" # Fail fastBehavior:
- Publisher blocks if queue full
- Returns error after timeout
- Backpressure mechanism
FELIX_ACK_WAIT_TIMEOUT_MS
Section titled “FELIX_ACK_WAIT_TIMEOUT_MS”Description: Maximum wait time for ack-on-commit completion.
Type: Positive integer (milliseconds)
Default: 2000
Example:
export FELIX_ACK_WAIT_TIMEOUT_MS="2000"Notes:
- Only relevant when
FELIX_ACK_ON_COMMIT=true - Publisher gets error if timeout exceeded
Event Batching and Delivery
Section titled “Event Batching and Delivery”FELIX_EVENT_BATCH_MAX_EVENTS
Section titled “FELIX_EVENT_BATCH_MAX_EVENTS”Description: Maximum events per subscription batch frame.
Type: Positive integer (count)
Default: 64
Example:
export FELIX_EVENT_BATCH_MAX_EVENTS="64"export FELIX_EVENT_BATCH_MAX_EVENTS="1" # No batchingexport FELIX_EVENT_BATCH_MAX_EVENTS="256" # Large batchesTuning:
- Small values (1-16): Low latency
- Medium values (32-64): Balanced
- Large values (128-256): High throughput
FELIX_EVENT_BATCH_MAX_BYTES
Section titled “FELIX_EVENT_BATCH_MAX_BYTES”Description: Maximum bytes per subscription batch frame.
Type: Positive integer (bytes)
Default: 65536 (64 KiB)
Example:
export FELIX_EVENT_BATCH_MAX_BYTES="65536" # 64 KiB (default)export FELIX_EVENT_BATCH_MAX_BYTES="524288" # 512 KiBexport FELIX_EVENT_BATCH_MAX_BYTES="1048576" # 1 MiBNotes:
- Batch sent when event count OR byte limit reached
- Adjust based on typical message size
FELIX_EVENT_BATCH_MAX_DELAY_US
Section titled “FELIX_EVENT_BATCH_MAX_DELAY_US”Description: Maximum delay before flushing batch (microseconds).
Type: Unsigned integer
Default: 250
Example:
export FELIX_EVENT_BATCH_MAX_DELAY_US="250"export FELIX_EVENT_BATCH_MAX_DELAY_US="50" # Ultra-low latencyexport FELIX_EVENT_BATCH_MAX_DELAY_US="1000" # Prioritize batchingexport FELIX_EVENT_BATCH_MAX_DELAY_US="5000" # Maximum batchingCritical tuning parameter:
- Lower: Reduced latency, more frequent sends
- Higher: Better batching, higher latency
- Typical range: 50-1000 microseconds
FELIX_FANOUT_BATCH
Section titled “FELIX_FANOUT_BATCH”Description: Subscribers to process in parallel during fanout.
Type: Positive integer (count)
Default: 64
Example:
export FELIX_FANOUT_BATCH="64"export FELIX_FANOUT_BATCH="128" # High fanoutexport FELIX_FANOUT_BATCH="16" # Low fanoutRecommendations:
- Match to typical subscriber count
- Higher values for high-fanout streams
- Lower values reduce concurrency overhead
Event Frame Encoding
Section titled “Event Frame Encoding”Subscription event delivery uses binary EventBatch frames by default.
FELIX_SUBSCRIBER_QUEUE_CAPACITY
Section titled “FELIX_SUBSCRIBER_QUEUE_CAPACITY”Description: Per-subscriber queue capacity in broker core.
Type: Positive integer (count)
Default: 512
export FELIX_SUBSCRIBER_QUEUE_CAPACITY="512"# Alias (same behavior):export FELIX_SUB_QUEUE_CAPACITY="512"FELIX_MAX_SUBSCRIPTIONS_PER_CONN
Section titled “FELIX_MAX_SUBSCRIPTIONS_PER_CONN”Description: Max concurrent subscriptions a single QUIC connection may hold. Independent of FELIX_SUBSCRIBER_QUEUE_CAPACITY (which bounds one subscription’s buffer size) — this bounds how many subscriptions a connection can open in total, protecting broker memory from a connection that opens unbounded subscriptions.
Type: Positive integer (count)
Default: 4096
export FELIX_MAX_SUBSCRIPTIONS_PER_CONN="4096"FELIX_SUB_QUEUE_POLICY
Section titled “FELIX_SUB_QUEUE_POLICY”Description: Backpressure policy when broker subscriber queues are full.
Type: Enum (block, drop_new, drop_old)
Default: drop_new
export FELIX_SUB_QUEUE_POLICY="drop_new"Policy notes:
block: await queue space (strongest delivery guarantee, may reduce publish throughput).drop_new: drop incoming item when queue is full.drop_old: currently emulated withdrop_newsemantics and tracked separately.
FELIX_SUB_SINGLE_WRITER_PER_CONN
Section titled “FELIX_SUB_SINGLE_WRITER_PER_CONN”Description: Keep all subscribers on the same QUIC connection on one writer lane.
Type: Boolean (1|true|yes to enable)
Default: false
export FELIX_SUB_SINGLE_WRITER_PER_CONN="true"FELIX_SUB_WRITER_LANES
Section titled “FELIX_SUB_WRITER_LANES”Description: Requested outbound subscriber writer lanes.
Type: Positive integer (count)
Default: 4
export FELIX_SUB_WRITER_LANES="4"# Alias (checked first, same behavior):export FELIX_SUB_EGRESS_LANES="4"FELIX_SUB_LANE_QUEUE_DEPTH
Section titled “FELIX_SUB_LANE_QUEUE_DEPTH”Description: Queue depth per outbound writer lane.
Type: Positive integer (count)
Default: 64
export FELIX_SUB_LANE_QUEUE_DEPTH="64"# Alias (same behavior):export FELIX_SUB_QUEUE_BOUND="64"FELIX_SUB_QUEUE_MODE
Section titled “FELIX_SUB_QUEUE_MODE”Description: Backpressure policy for the writer-lane command queue (downstream of FELIX_SUB_QUEUE_POLICY, which gates the earlier broker-core fanout enqueue).
Type: Enum (block, drop_new, drop_old)
Default: drop_new
export FELIX_SUB_QUEUE_MODE="drop_new"# Alias (same behavior):export FELIX_SUB_LANE_QUEUE_POLICY="drop_new"FELIX_MAX_SUB_WRITER_LANES
Section titled “FELIX_MAX_SUB_WRITER_LANES”Description: Safety clamp for writer lanes.
Type: Positive integer (count)
Default: 8
export FELIX_MAX_SUB_WRITER_LANES="8"FELIX_SUB_LANE_SHARD
Section titled “FELIX_SUB_LANE_SHARD”Description: Outbound lane sharding policy.
Type: Enum (auto, subscriber_id_hash, connection_id_hash, round_robin_pin)
Default: auto
export FELIX_SUB_LANE_SHARD="auto"Policy notes:
auto: prefers connection-aware routing when connection id is known.subscriber_id_hash: stable by subscriber id.connection_id_hash: stable by connection id.round_robin_pin: pinned RR assignment per subscriber.
FELIX_SUB_FLUSH_MAX_ITEMS
Section titled “FELIX_SUB_FLUSH_MAX_ITEMS”Description: Maximum queued lane commands drained per flush before a write is issued.
Type: Positive integer (count)
Default: 16
export FELIX_SUB_FLUSH_MAX_ITEMS="16"FELIX_SUB_FLUSH_MAX_DELAY_US
Section titled “FELIX_SUB_FLUSH_MAX_DELAY_US”Description: Maximum time spent waiting to fill a lane flush buffer before writing what’s accumulated.
Type: Unsigned integer (microseconds)
Default: 50
export FELIX_SUB_FLUSH_MAX_DELAY_US="50"FELIX_SUB_MAX_BYTES_PER_WRITE
Section titled “FELIX_SUB_MAX_BYTES_PER_WRITE”Description: Upper bound on coalesced bytes per QUIC write call to a subscriber stream.
Type: Positive integer (bytes)
Default: 65536 (64 KiB)
export FELIX_SUB_MAX_BYTES_PER_WRITE="65536"FELIX_SUB_STREAMS_PER_CONN
Section titled “FELIX_SUB_STREAMS_PER_CONN”Description: Number of delivery streams per connection in hashed-pool mode.
Type: Positive integer (count)
Default: 4
export FELIX_SUB_STREAMS_PER_CONN="4"FELIX_SUB_STREAM_MODE
Section titled “FELIX_SUB_STREAM_MODE”Description: Strategy for mapping subscribers to event streams. hashed_pool is not yet enabled — the broker falls back to per_subscriber and logs a debug warning if requested.
Type: Enum (per_subscriber, hashed_pool)
Default: per_subscriber
export FELIX_SUB_STREAM_MODE="per_subscriber"Cache Configuration
Section titled “Cache Configuration”FELIX_CACHE_CONN_POOL
Section titled “FELIX_CACHE_CONN_POOL”Description: Number of QUIC connections in cache pool (client-side).
Type: Positive integer (count)
Default: 8
Example:
export FELIX_CACHE_CONN_POOL="8"export FELIX_CACHE_CONN_POOL="16" # High concurrencyexport FELIX_CACHE_CONN_POOL="4" # Low concurrencyNotes:
- Client-side setting
- Affects concurrent request capacity
- Each connection can have multiple streams
FELIX_CACHE_STREAMS_PER_CONN
Section titled “FELIX_CACHE_STREAMS_PER_CONN”Description: Cache request streams per connection (client-side).
Type: Positive integer (count)
Default: 4
Example:
export FELIX_CACHE_STREAMS_PER_CONN="4"export FELIX_CACHE_STREAMS_PER_CONN="8" # More parallelismexport FELIX_CACHE_STREAMS_PER_CONN="2" # Less overheadTuning:
- Total cache parallelism =
pool × streams_per_conn - Higher values for high-concurrency workloads
FELIX_CACHE_CONN_RECV_WINDOW
Section titled “FELIX_CACHE_CONN_RECV_WINDOW”Description: Cache connection flow-control receive window (broker).
Type: Positive integer (bytes)
Default: 268435456 (256 MiB)
Example:
export FELIX_CACHE_CONN_RECV_WINDOW="268435456" # 256 MiBexport FELIX_CACHE_CONN_RECV_WINDOW="536870912" # 512 MiBexport FELIX_CACHE_CONN_RECV_WINDOW="134217728" # 128 MiBMemory impact:
- Per-connection credit
- Multiplied by connection pool size
- Affects burst tolerance
FELIX_CACHE_STREAM_RECV_WINDOW
Section titled “FELIX_CACHE_STREAM_RECV_WINDOW”Description: Cache stream flow-control receive window (broker).
Type: Positive integer (bytes)
Default: 67108864 (64 MiB)
Example:
export FELIX_CACHE_STREAM_RECV_WINDOW="67108864" # 64 MiBexport FELIX_CACHE_STREAM_RECV_WINDOW="134217728" # 128 MiBexport FELIX_CACHE_STREAM_RECV_WINDOW="33554432" # 32 MiBNotes:
- Per-stream credit
- Total:
stream_window × streams_per_conn × conn_pool
FELIX_CACHE_SEND_WINDOW
Section titled “FELIX_CACHE_SEND_WINDOW”Description: Cache connection send window (broker).
Type: Positive integer (bytes)
Default: 268435456 (256 MiB)
Example:
export FELIX_CACHE_SEND_WINDOW="268435456"FELIX_CACHE_BENCH_CONCURRENCY
Section titled “FELIX_CACHE_BENCH_CONCURRENCY”Description: Concurrency level for cache benchmark (demo only).
Type: Positive integer
Default: 32
Example:
export FELIX_CACHE_BENCH_CONCURRENCY="32"export FELIX_CACHE_BENCH_CONCURRENCY="64" # Stress testFELIX_CACHE_BENCH_KEYS
Section titled “FELIX_CACHE_BENCH_KEYS”Description: Number of keys for cache benchmark (demo only).
Type: Positive integer
Default: 1024
Example:
export FELIX_CACHE_BENCH_KEYS="1024"Event Connection Pool (Client)
Section titled “Event Connection Pool (Client)”FELIX_EVENT_CONN_POOL
Section titled “FELIX_EVENT_CONN_POOL”Description: Number of QUIC connections for event delivery (client).
Type: Positive integer (count)
Default: 8
Example:
export FELIX_EVENT_CONN_POOL="8"export FELIX_EVENT_CONN_POOL="4" # Lower overheadexport FELIX_EVENT_CONN_POOL="16" # More parallelism# Alias used by perf scripts:export FELIX_SUB_CONNS="8"FELIX_EVENT_CONN_RECV_WINDOW
Section titled “FELIX_EVENT_CONN_RECV_WINDOW”Description: Event connection receive window (client).
Type: Positive integer (bytes)
Default: 268435456 (256 MiB)
Example:
export FELIX_EVENT_CONN_RECV_WINDOW="268435456"FELIX_EVENT_STREAM_RECV_WINDOW
Section titled “FELIX_EVENT_STREAM_RECV_WINDOW”Description: Event stream receive window (client).
Type: Positive integer (bytes)
Default: 67108864 (64 MiB)
Example:
export FELIX_EVENT_STREAM_RECV_WINDOW="67108864"FELIX_EVENT_SEND_WINDOW
Section titled “FELIX_EVENT_SEND_WINDOW”Description: Event connection send window (client).
Type: Positive integer (bytes)
Default: 268435456 (256 MiB)
Example:
export FELIX_EVENT_SEND_WINDOW="268435456"FELIX_CLIENT_SUB_QUEUE_CAPACITY
Section titled “FELIX_CLIENT_SUB_QUEUE_CAPACITY”Description: Bounded queue capacity between client subscription IO and dispatch stages.
Type: Positive integer (count)
Default: 256
export FELIX_CLIENT_SUB_QUEUE_CAPACITY="256"FELIX_CLIENT_SUB_QUEUE_POLICY
Section titled “FELIX_CLIENT_SUB_QUEUE_POLICY”Description: Client-side backpressure policy for subscription pipeline queues.
Type: Enum (block, drop_new, drop_old)
Default: drop_new
export FELIX_CLIENT_SUB_QUEUE_POLICY="drop_new"Publishing Pool (Client)
Section titled “Publishing Pool (Client)”FELIX_PUB_CONN_POOL
Section titled “FELIX_PUB_CONN_POOL”Description: Number of publishing QUIC connections (client).
Type: Positive integer (count)
Default: 4
Example:
export FELIX_PUB_CONN_POOL="4"export FELIX_PUB_CONN_POOL="8" # More publishersFELIX_PUB_STREAMS_PER_CONN
Section titled “FELIX_PUB_STREAMS_PER_CONN”Description: Publishing streams per connection (client).
Type: Positive integer (count)
Default: 2
Example:
export FELIX_PUB_STREAMS_PER_CONN="2"export FELIX_PUB_STREAMS_PER_CONN="4" # More concurrencyFELIX_PUBLISH_CHUNK_BYTES
Section titled “FELIX_PUBLISH_CHUNK_BYTES”Description: Chunk size for publishing large messages (client).
Type: Positive integer (bytes)
Default: 16384 (16 KiB)
Example:
export FELIX_PUBLISH_CHUNK_BYTES="16384" # 16 KiBexport FELIX_PUBLISH_CHUNK_BYTES="32768" # 32 KiBexport FELIX_PUBLISH_CHUNK_BYTES="8192" # 8 KiBFELIX_PUBLISH_QUEUE_DEPTH
Section titled “FELIX_PUBLISH_QUEUE_DEPTH”Description: Bounded request queue depth per client publish worker.
Type: Positive integer (count)
Default: 64
export FELIX_PUBLISH_QUEUE_DEPTH="64"FELIX_PUBLISH_INFLIGHT_BYTES
Section titled “FELIX_PUBLISH_INFLIGHT_BYTES”Description: Shared queued and in-flight publish byte budget across client workers.
Type: Positive integer (bytes)
Default: 4194304 (4 MiB)
export FELIX_PUBLISH_INFLIGHT_BYTES="4194304"Broker Workers and Queues
Section titled “Broker Workers and Queues”FELIX_BROKER_PUB_WORKERS_PER_CONN
Section titled “FELIX_BROKER_PUB_WORKERS_PER_CONN”Description: Publish workers in the broker’s pool. Despite the name the
pool is process-wide, not per connection — it is built once, before the
accept loop, because per-connection pools multiplied concurrent callers into
shared broker state. A stream-shard handle maps to one worker
(handle.id() % count), so raising this spreads different shards across more
workers; it cannot give one shard more than one. The name is misleading and is
tracked for a rename in #535.
Type: Positive integer (count)
Default: 4
Example:
export FELIX_BROKER_PUB_WORKERS_PER_CONN="4"export FELIX_BROKER_PUB_WORKERS_PER_CONN="8" # High concurrencyexport FELIX_BROKER_PUB_WORKERS_PER_CONN="2" # Lower overheadFELIX_BROKER_PUB_FLUSH_CONCURRENCY
Section titled “FELIX_BROKER_PUB_FLUSH_CONCURRENCY”Description: Durable publishes one publish worker may have awaiting their
device flush at once (broker). Offsets are still claimed serially, in arrival
order, so this does not affect the order records land in — it decides how many
flushes group commit gets to coalesce. 1 restores the pre-0.4.1 behaviour of
one flush at a time, which capped a shard at roughly one batch per flush (#535).
Type: Positive integer (count)
Default: 32
Example:
export FELIX_BROKER_PUB_FLUSH_CONCURRENCY="32"export FELIX_BROKER_PUB_FLUSH_CONCURRENCY="64" # Deeper coalescing on fast devicesexport FELIX_BROKER_PUB_FLUSH_CONCURRENCY="1" # Serialise, as before 0.4.1FELIX_BROKER_PUB_QUEUE_DEPTH
Section titled “FELIX_BROKER_PUB_QUEUE_DEPTH”Description: Per-worker publish queue depth (broker).
Type: Positive integer (count)
Default: 64
Example:
export FELIX_BROKER_PUB_QUEUE_DEPTH="64"export FELIX_BROKER_PUB_QUEUE_DEPTH="256" # More bufferingexport FELIX_BROKER_PUB_QUEUE_DEPTH="32" # Less memoryFELIX_BROKER_PUBLISH_INFLIGHT_BYTES
Section titled “FELIX_BROKER_PUBLISH_INFLIGHT_BYTES”Description: Shared in-flight publish byte budget across all publish workers (process-wide). Bounds queued-or-processing bytes independent of FELIX_BROKER_PUB_QUEUE_DEPTH’s item count, so a handful of large payloads/batches can’t blow past the intended ingress memory budget.
Type: Positive integer (bytes)
Default: 67108864 (64 MiB)
export FELIX_BROKER_PUBLISH_INFLIGHT_BYTES="67108864"FELIX_BROKER_PUBLISH_CONN_INFLIGHT_BYTES
Section titled “FELIX_BROKER_PUBLISH_CONN_INFLIGHT_BYTES”Description: Per-connection share of FELIX_BROKER_PUBLISH_INFLIGHT_BYTES. Bounds how much of the shared, process-wide publish byte budget a single connection can occupy at once, so one connection publishing large batches can’t starve every other connection’s admission into the shared budget.
Type: Positive integer (bytes)
Default: 16777216 (16 MiB)
export FELIX_BROKER_PUBLISH_CONN_INFLIGHT_BYTES="16777216"FELIX_PUB_INGRESS_WAIT
Section titled “FELIX_PUB_INGRESS_WAIT”Description: When enabled, un-acked (fire-and-forget) publishes wait — bounded by FELIX_PUBLISH_QUEUE_WAIT_MS — for ingress capacity instead of being shed when the publish queue or byte budget is full. Backpressure then propagates through QUIC flow control to the publisher. Leave off in production for visible shedding under overload; turn on for lossless pipelines and sustainable-throughput benchmarking.
Type: Boolean (1, true, yes = enabled)
Default: disabled
export FELIX_PUB_INGRESS_WAIT="1"FELIX_CORE_SHARDS
Section titled “FELIX_CORE_SHARDS”Description: Number of core-pinned shard executors owning stream work (thread-per-core, shared-nothing). Each stream is owned by one shard: its publish worker and its subscriptions’ lane feeders run on that shard’s dedicated single-threaded runtime, pinned to a CPU core on Linux. Benefits scale with stream count; single-stream workloads serialize on one shard by design.
Type: Positive integer (count; 0 = disabled)
Default: 0
export FELIX_CORE_SHARDS="4"QUIC Transport Tuning
Section titled “QUIC Transport Tuning”Process-wide levers read by every Felix QUIC endpoint (broker, client, demos). See Benchmarks for measured impact.
FELIX_MTU_UPPER_BOUND
Section titled “FELIX_MTU_UPPER_BOUND”Description: Upper bound for QUIC path-MTU discovery. Discovery converges to the real path MTU at or below it, so on a 1500-byte network the bound never binds.
Raising it above 6,553 on Linux will stall delivery. Linux UDP GSO packs a whole sendmsg batch into one IP datagram, so MTU × segments must stay under 65,535, and quinn batches up to 10. Above that the kernel rejects every batch with EMSGSIZE, which quinn does not recognise as a GSO failure — it falls back only on EIO/EINVAL — so the transmit is dropped after quinn has counted it as sent, and the stall is permanent rather than degrading. This is not a throughput preference; it is the difference between working and not.
The default was 16384 until 0.5.0, which is above that ceiling. It was harmless on a 1500-byte path and fatal on a jumbo-frame one, where discovery climbs past 6,553 — which is exactly the network you would buy for throughput. 4096 rather than the exact 6,553 because quinn’s batch size is private to it and 6,553 breaks the moment it rises; 4,096 also measured fastest on Linux. macOS has no GSO and no such limit.
Type: Positive integer (bytes, clamped to 1200–65527)
Default: 4096 (16384 on macOS)
export FELIX_MTU_UPPER_BOUND="4096"export FELIX_MTU_UPPER_BOUND="16384" # macOS, or any path with no GSOFELIX_INITIAL_MTU
Section titled “FELIX_INITIAL_MTU”Description: Starting datagram size before path-MTU discovery completes. The RFC-safe default works everywhere; raising it on known-good paths (jumbo-frame LAN) skips the discovery ramp. Connections to a loopback peer automatically start at the loopback MTU and guarantee it, which makes the path immune to spurious black-hole collapse (see FELIX_MTU_BLACK_HOLE_COOLDOWN_MS) and, because the guarantee also freezes the discovery bound, removes probe traffic entirely.
The guaranteed size is 16,336 bytes on macOS and 4,096 elsewhere (both capped by FELIX_MTU_UPPER_BOUND). The lower cap off macOS is not conservatism. Linux UDP GSO packs a whole sendmsg batch into a single IP datagram, so MTU × segments must stay under 65,535; quinn batches up to 10, putting the real ceiling at 6,553 bytes. Above it the kernel rejects every batch and delivery stalls outright — measured as a total stall at both 8,192 and 16,336. macOS has no GSO (one syscall per datagram) and no such limit. 4,096 also measured fastest on Linux; see the performance case study.
The loopback path additionally requires the socket’s granted UDP buffers to reach ~1 MiB. That threshold is a proxy for “this host has been tuned”, not a burst-headroom calculation — hosts where Linux silently clamps SO_RCVBUF to a stock net.core.rmem_max (~208 KB) keep the RFC-safe default; raise rmem_max/wmem_max to enable it. Setting FELIX_INITIAL_MTU explicitly disables the loopback special case and applies to every path.
Type: Positive integer (bytes, clamped to 1200–65527)
Default: 1200 (loopback peers: 16336 on macOS, 4096 elsewhere)
export FELIX_INITIAL_MTU="1200"FELIX_MTU_BLACK_HOLE_COOLDOWN_MS
Section titled “FELIX_MTU_BLACK_HOLE_COOLDOWN_MS”Description: How long a connection waits after an MTU black-hole verdict before re-probing for a larger MTU. Quinn’s black-hole detector cannot distinguish a path that silently drops large packets from a congestive loss burst that happened to contain only full-MTU packets (which is what overflowing the peer’s UDP socket buffer looks like at high rate). A false verdict collapses the path MTU to the initial value, multiplying datagram and syscall counts per byte by ~13× on a 16 KiB-MTU path; quinn’s stock 60-second cooldown then pins that state. Felix shortens the cooldown so a spurious collapse re-probes at the connection’s next idle gap. Note that quinn only sends recovery probes when the connection has nothing else to transmit, so a sender with a continuous backlog cannot recover until its load has a gap regardless of this setting — which is why loopback connections start at full MTU instead (see FELIX_INITIAL_MTU).
Type: Positive integer (milliseconds, minimum 100)
Default: 2000
export FELIX_MTU_BLACK_HOLE_COOLDOWN_MS="2000"FELIX_INITIAL_CWND
Section titled “FELIX_INITIAL_CWND”Description: Optional initial congestion window override in bytes. By default Felix keeps Quinn’s RFC 9002 behavior, including raising the minimum window to two datagrams after path-MTU discovery. Increase only on trusted low-loss paths where a larger initial burst is acceptable.
Type: Positive integer (bytes)
Default: Quinn’s RFC 9002 default
export FELIX_INITIAL_CWND="1048576"FELIX_UDP_SEND_BUFFER / FELIX_UDP_RECV_BUFFER
Section titled “FELIX_UDP_SEND_BUFFER / FELIX_UDP_RECV_BUFFER”Description: Requested UDP socket buffer sizes (SO_SNDBUF / SO_RCVBUF). Applied best-effort: halved until the OS accepts. Kernel-level datagram drops surface as QUIC retransmits and tail-latency spikes, so large buffers matter at high message rates.
Type: Positive integer (bytes)
Default: 8388608 (8 MiB)
export FELIX_UDP_SEND_BUFFER="8388608"export FELIX_UDP_RECV_BUFFER="8388608"FELIX_MAX_UDP_PAYLOAD
Section titled “FELIX_MAX_UDP_PAYLOAD”Description: Largest UDP datagram the endpoint accepts (receive side). Must be at least the peer’s discovered MTU or large datagrams are rejected.
Type: Positive integer (bytes, clamped to 1200–65527)
Default: 65527
export FELIX_MAX_UDP_PAYLOAD="65527"FELIX_IO_RUNTIME_THREADS
Section titled “FELIX_IO_RUNTIME_THREADS”Description: Size of the dedicated QUIC I/O runtime pool. Quinn’s driver tasks (endpoint receive loop, per-connection transmit/ACK loops) do a bounded slice of work per poll and reschedule themselves, so their scheduler re-poll latency is the transport’s throughput ceiling. Felix therefore runs them on a pool of single-threaded runtimes isolated from application tasks, assigned by role: server endpoints spread across every runtime but the last, client endpoints share the last. 0 disables the isolation and runs drivers on the application runtime (the pre-fix behavior). A larger pool cannot make a single endpoint faster — an endpoint’s driver is one task on one runtime — and it splits endpoints that talk to each other onto separate threads, which measured 5–6× slower.
Type: Non-negative integer
Default: derived on macOS — one runtime per server endpoint plus one for
clients, so a broker with one client listener gets 2, and one with
FELIX_QUIC_LISTENERS=4 gets 6 (four client listeners, the internal
listener, and the client runtime). 0 elsewhere.
export FELIX_IO_RUNTIME_THREADS="2"export FELIX_IO_RUNTIME_THREADS="0" # disable driver isolationFELIX_ACK_ELICITING_THRESHOLD
Section titled “FELIX_ACK_ELICITING_THRESHOLD”Description: How many ack-eliciting packets a peer may receive before it must send an ACK (QUIC ACK-frequency extension; applies between quinn peers). The RFC default of every other packet costs a reverse-path datagram — plus its wakeup chain — per 2 datagrams of data; the higher default trades a little loss-detection latency (bounded by the 2 ms +15% throughput on loopback).max_ack_delay Felix also negotiates) for measurably less per-byte wakeup traffic (
Type: Positive integer (packets)
Default: 20
export FELIX_ACK_ELICITING_THRESHOLD="20"export FELIX_ACK_ELICITING_THRESHOLD="1" # RFC-like cadenceFELIX_ACK_FREQ_DISABLE
Section titled “FELIX_ACK_FREQ_DISABLE”Description: Set to any value to skip negotiating the ACK-frequency extension entirely, restoring stock quinn ACK behavior (25 ms max ack delay, ACK every other packet).
Type: Presence toggle
Default: unset (extension negotiated)
export FELIX_ACK_FREQ_DISABLE="1"FELIX_CONN_STATS_MS
Section titled “FELIX_CONN_STATS_MS”Description: Log live quinn::ConnectionStats (path MTU, cwnd, rtt, loss, congestion events, blocked-frame counters, UDP datagram/byte/io counts) for every connection on this interval, on both the broker and the client. The client-side log is the only place the publish path’s sender-side congestion state is visible. Diagnostic; off unless set.
Type: Positive integer (milliseconds)
Default: unset (disabled)
export FELIX_CONN_STATS_MS="1000"Performance and Monitoring
Section titled “Performance and Monitoring”FELIX_DISABLE_TIMINGS
Section titled “FELIX_DISABLE_TIMINGS”Description: Disable per-stage timing collection.
Type: Boolean
Default: false
Accepted values: 1, true, yes = disabled
Example:
export FELIX_DISABLE_TIMINGS="false" # Enable timingsexport FELIX_DISABLE_TIMINGS="true" # Disable for performanceexport FELIX_DISABLE_TIMINGS="1"Trade-off:
false: Detailed metrics, slight overheadtrue: Maximum performance, no timing data
FELIX_CONTROL_STREAM_DRAIN_TIMEOUT_MS
Section titled “FELIX_CONTROL_STREAM_DRAIN_TIMEOUT_MS”Description: Timeout for control stream drain (broker).
Type: Positive integer (milliseconds)
Default: 50
Example:
export FELIX_CONTROL_STREAM_DRAIN_TIMEOUT_MS="50"export FELIX_CONTROL_STREAM_DRAIN_TIMEOUT_MS="100" # More gracefulexport FELIX_CONTROL_STREAM_DRAIN_TIMEOUT_MS="20" # Faster shutdownFELIX_SHUTDOWN_DRAIN_TIMEOUT_MS
Section titled “FELIX_SHUTDOWN_DRAIN_TIMEOUT_MS”Description: Total budget for draining in-flight work after SIGTERM or SIGINT, before remaining tasks are force-cancelled. Applies to both the broker and the control plane. This is a single budget shared by every subsystem, not a per-subsystem timeout, so total shutdown time stays bounded by this value.
Type: Positive integer (milliseconds)
Default: 25000
Example:
export FELIX_SHUTDOWN_DRAIN_TIMEOUT_MS="25000"export FELIX_SHUTDOWN_DRAIN_TIMEOUT_MS="55000" # With terminationGracePeriodSeconds: 60export FELIX_SHUTDOWN_DRAIN_TIMEOUT_MS="5000" # Fast rollouts, short-lived requestsNote: Keep this below the platform’s kill deadline — Kubernetes’
terminationGracePeriodSeconds (default 30) — so the drain finishes and logs its
outcome before SIGKILL. See Graceful Shutdown.
FELIX_SHUTDOWN_PREDRAIN_MS
Section titled “FELIX_SHUTDOWN_PREDRAIN_MS”Description: How long the control plane keeps serving after it starts reporting unready, before it stops accepting connections. Readiness-first shutdown only helps if something has time to act on it: a load balancer learns an instance is draining by polling, so closing the listener the moment readiness flips leaves requests still being routed to a socket that is gone.
Applies to: Control plane.
Type: Non-negative integer (milliseconds); 0 skips the wait.
Default: 5000
Example:
export FELIX_SHUTDOWN_PREDRAIN_MS="5000"export FELIX_SHUTDOWN_PREDRAIN_MS="15000" # readinessProbe periodSeconds 5 x failureThreshold 3export FELIX_SHUTDOWN_PREDRAIN_MS="0" # single instance, nothing routing to itNote: Size it above the prober’s periodSeconds multiplied by its
failureThreshold, so the load balancer has actually removed this instance before the
listener closes. It is spent inside the platform’s kill deadline, so
terminationGracePeriodSeconds must cover this plus
FELIX_SHUTDOWN_DRAIN_TIMEOUT_MS. A second SIGTERM ends the wait early.
FELIX_INTERNAL_MAX_INBOUND_CONNECTIONS
Section titled “FELIX_INTERNAL_MAX_INBOUND_CONNECTIONS”Description: Inbound peer connections this broker holds at once, across all peers. Over the limit, a connection is refused rather than queued — a peer told no can back off, where one left waiting cannot tell a busy broker from a stuck one.
Type: Integer
Default: 512
FELIX_INTERNAL_MAX_INBOUND_PER_SOURCE
Section titled “FELIX_INTERNAL_MAX_INBOUND_PER_SOURCE”Description: Inbound peer connections from any one address. The total alone does not stop one peer consuming the whole allowance, which is the case that matters: a peer looping on a reconnect bug starves the rest of the cluster before anyone notices.
Startup refuses a value above
FELIX_INTERNAL_MAX_INBOUND_CONNECTIONS, since the per-source limit would then
never be the one that applies.
Type: Integer
Default: 16
Configuration File
Section titled “Configuration File”FELIX_BROKER_CONFIG
Section titled “FELIX_BROKER_CONFIG”Description: Path to YAML configuration file.
Type: String (file path)
Default: /usr/local/felix/config.yml (optional)
Example:
export FELIX_BROKER_CONFIG="/etc/felix/broker.yml"export FELIX_BROKER_CONFIG="/tmp/felix-dev.yml"Behavior:
- If set and file missing: error
- If not set and default missing: continue with defaults
- A key the broker does not know is an error, naming the key. A setting that looks like it is in effect and is not is worse than a refusal, so a misspelled key stops startup rather than silently leaving the default in place. The same is true of the control plane’s config file, nested sections included.
Seeing what is in effect
Section titled “Seeing what is in effect”felix-broker --print-configPrints the configuration the broker would run with — defaults, the config file, and the environment already folded together — as YAML, and exits. Nothing is bound, so it is safe to run on a node that is already serving.
It doubles as a pre-flight check. The config is loaded exactly as startup loads it, so a file that will not parse, or a key the broker does not know, fails here with the same message it would have produced on the node — before a rollout rather than during one.
The node credential is shown as <redacted>, or <unset> when there is none:
this output is meant to be pasted into an issue, and whether a token is set is
exactly what someone debugging a registration failure needs to see. Warnings
about unrecognised variables go to stderr, so --print-config > current.yml
gives a clean document and still shows them.
Settings that are wrong together
Section titled “Settings that are wrong together”Each variable validates its own value where it is parsed. Some pairs are each fine alone and wrong in combination, and those are refused at startup too — because the failure they produce otherwise is behaviour nobody configured, with no error to explain it:
| Refused when | Why |
|---|---|
event_batch_max_bytes > max_frame_bytes |
The broker would send subscribers frames larger than it will itself accept, and a client applying the same limit drops them |
pub_conn_inflight_bytes > pub_inflight_bytes |
The per-connection limit could never be the one that applies, so one connection may take the whole broker-wide allowance |
cache_stream_recv_window > cache_conn_recv_window |
A single stream can never reach its own window, because the connection’s runs out first |
FELIX_INTERNAL_BIND shares a port with FELIX_QUIC_BIND |
The internal and client-facing listeners must be separate |
Equal is allowed everywhere: these bound each other and do not have to differ.
felix-broker --print-config runs the same checks without starting anything,
so a bad combination fails before a rollout rather than on the node.
Typos in variable names
Section titled “Typos in variable names”A misspelled variable cannot be refused the same way — the process cannot
tell a typo from a variable meant for something else sharing the container. So
both binaries warn instead, at startup, naming every FELIX_* variable that is
set and that nothing reads:
WARN FELIX_METRICS_BIND is set and nothing reads it — did you mean one of FELIX_BROKER_METRICS_BIND, FELIX_CONTROLPLANE_METRICS_BIND? Those settings are using their defaultsThe suggestion looks for a missing segment first and a misspelling second, so a plausible-but-wrong shorter name — the mistake someone makes without noticing — is matched to the real one. Nothing close enough means no suggestion rather than the nearest arbitrary name.
Logging
Section titled “Logging”RUST_LOG
Section titled “RUST_LOG”Description: Rust logging filter (not Felix-specific but commonly used).
Type: String (filter expression)
Default: Varies by build
Example:
export RUST_LOG="info"export RUST_LOG="debug"export RUST_LOG="felix_broker=debug,felix_wire=trace"export RUST_LOG="warn"Levels: error, warn, info, debug, trace
Performance Profiles
Section titled “Performance Profiles”Balanced Profile
Section titled “Balanced Profile”export FELIX_EVENT_CONN_POOL="8"export FELIX_EVENT_CONN_RECV_WINDOW="268435456"export FELIX_EVENT_STREAM_RECV_WINDOW="67108864"export FELIX_EVENT_SEND_WINDOW="268435456"export FELIX_EVENT_BATCH_MAX_DELAY_US="250"export FELIX_CACHE_CONN_POOL="8"export FELIX_CACHE_STREAMS_PER_CONN="4"export FELIX_DISABLE_TIMINGS="0"High Memory Profile
Section titled “High Memory Profile”export FELIX_EVENT_CONN_POOL="8"export FELIX_EVENT_CONN_RECV_WINDOW="536870912"export FELIX_EVENT_STREAM_RECV_WINDOW="134217728"export FELIX_EVENT_SEND_WINDOW="536870912"export FELIX_EVENT_BATCH_MAX_DELAY_US="250"export FELIX_CACHE_CONN_POOL="8"export FELIX_CACHE_STREAMS_PER_CONN="4"export FELIX_DISABLE_TIMINGS="1"Low Latency Profile
Section titled “Low Latency Profile”export FELIX_EVENT_BATCH_MAX_EVENTS="1"export FELIX_EVENT_BATCH_MAX_DELAY_US="50"export FELIX_FANOUT_BATCH="16"export FELIX_DISABLE_TIMINGS="1"High Throughput Profile
Section titled “High Throughput Profile”export FELIX_EVENT_BATCH_MAX_EVENTS="256"export FELIX_EVENT_BATCH_MAX_BYTES="1048576"export FELIX_EVENT_BATCH_MAX_DELAY_US="1000"export FELIX_FANOUT_BATCH="128"export FELIX_DISABLE_TIMINGS="1"Durable Storage Configuration
Section titled “Durable Storage Configuration”Durable stream storage is opt-in. With FELIX_DURABLE_STORAGE_DIR unset the
broker is in-memory only, and any stream the control plane marks durable: true
is rejected at registration rather than silently downgraded to a guarantee
the broker cannot keep.
See Durable Storage for what each policy guarantees and what it costs.
FELIX_DURABLE_STORAGE_DIR
Section titled “FELIX_DURABLE_STORAGE_DIR”Description: Root directory for durable stream segments. Setting it enables durable streams; one subdirectory is created per stream shard.
Type: Path
Default: unset (durable storage disabled)
Example:
export FELIX_DURABLE_STORAGE_DIR="/var/lib/felix/streams"FELIX_DURABLE_FSYNC_MODE
Section titled “FELIX_DURABLE_FSYNC_MODE”Description: When written bytes are pushed to the storage device.
Type: One of none, periodic, on_commit
Default: periodic
| Value | Acknowledged when | Loss window |
|---|---|---|
none |
bytes reach the page cache | unbounded — survives a process crash, not a power loss |
periodic |
bytes reach the page cache | one flush interval |
on_commit |
bytes reach the device | none |
Example:
export FELIX_DURABLE_FSYNC_MODE="on_commit"Trade-off: on_commit costs one device flush per commit (~4ms on typical
NVMe), amortised across concurrent publishers by group commit. periodic adds
no measurable append latency at all.
FELIX_DURABLE_FSYNC_INTERVAL_MS
Section titled “FELIX_DURABLE_FSYNC_INTERVAL_MS”Description: Flush interval for FELIX_DURABLE_FSYNC_MODE=periodic. Bounds
how much acknowledged data a machine crash can lose.
Type: Positive integer (milliseconds)
Default: 250
Example:
export FELIX_DURABLE_FSYNC_INTERVAL_MS="100"Note: Setting this without setting the mode implies periodic. Zero is
rejected at startup — it is a busy loop, not “always sync”; use on_commit for
per-commit durability.
FELIX_DURABLE_SEGMENT_BYTES
Section titled “FELIX_DURABLE_SEGMENT_BYTES”Description: Size at which the active segment rolls over to a new file.
Type: Positive integer (bytes)
Default: 268435456 (256 MiB)
Example:
export FELIX_DURABLE_SEGMENT_BYTES="67108864" # 64 MiBTrade-off: Smaller segments bound recovery time (only the active segment is fully scanned at startup) at the cost of more files and more rollovers.
FELIX_DURABLE_RETENTION_BYTES
Section titled “FELIX_DURABLE_RETENTION_BYTES”Description: Delete the oldest sealed segments once a stream’s log exceeds this size. Unset means the log grows without bound, which is the default and the pre-retention behaviour.
Type: Positive integer (bytes)
Default: unset (no size bound)
Example:
export FELIX_DURABLE_RETENTION_BYTES="10737418240" # 10 GiB per stream shardNote: The active segment is never deleted, so a log settles at roughly this
size and never below FELIX_DURABLE_SEGMENT_BYTES regardless of how small this
is set. Records below the retained range report CursorTooOld to a resuming
subscriber, naming the oldest offset still available.
FELIX_DURABLE_RETENTION_SECONDS
Section titled “FELIX_DURABLE_RETENTION_SECONDS”Description: Delete sealed segments whose newest record is older than this.
Combines with FELIX_DURABLE_RETENTION_BYTES; either bound alone is enough to
trigger a deletion.
Type: Positive integer (seconds)
Default: unset (no age bound)
Example:
export FELIX_DURABLE_RETENTION_SECONDS="604800" # 7 daysNote: Age comes from the records’ own timestamps rather than file mtime, so restoring a backup does not reset it. A segment survives until its newest record has expired, so nothing younger than the bound is ever deleted.
FELIX_DURABLE_RETENTION_INTERVAL_SECONDS
Section titled “FELIX_DURABLE_RETENTION_INTERVAL_SECONDS”Description: How often retention is evaluated. Ignored unless a retention bound is set.
Type: Positive integer (seconds)
Default: 60
Example:
export FELIX_DURABLE_RETENTION_INTERVAL_SECONDS="300"Trade-off: Retention is bulk file deletion and runs on its own timer so it never lands on a publish. A longer interval means disk usage overshoots the bound for longer between passes.
FELIX_DURABLE_INDEX_SPACING_BYTES
Section titled “FELIX_DURABLE_INDEX_SPACING_BYTES”Description: Bytes of segment data between sparse index entries. A read binary-searches the index, then scans forward at most one interval.
Type: Positive integer (bytes)
Default: 4096
Example:
export FELIX_DURABLE_INDEX_SPACING_BYTES="8192"Trade-off: Smaller spacing means faster seeks and larger index files.
FELIX_DURABLE_MAX_RECORDS_PER_READ
Section titled “FELIX_DURABLE_MAX_RECORDS_PER_READ”Description: Ceiling on records returned by a single range read, on top of the caller’s byte budget. Payload bytes alone do not bound a response made of empty records.
Type: Positive integer
Default: 10000
Example:
export FELIX_DURABLE_MAX_RECORDS_PER_READ="5000"FELIX_DURABLE_PREALLOCATE
Section titled “FELIX_DURABLE_PREALLOCATE”Description: Reserve a segment’s blocks when it is created, keeping block allocation off the append path.
Type: Boolean
Default: true
Example:
export FELIX_DURABLE_PREALLOCATE="false"Note: Disable on filesystems where reservations are expensive or where thin provisioning makes them counter-productive.
FELIX_STORAGE_IO_URING
Section titled “FELIX_STORAGE_IO_URING”Description: Submit device flushes through io_uring (IORING_OP_FSYNC) on
one process-wide ring, instead of handing each one to the blocking thread pool.
Linux only.
Type: Boolean (1 to enable)
Default: 0
Example:
export FELIX_STORAGE_IO_URING="1"Note: A kernel too old for the opcode, or a container that forbids the syscall, falls back to the blocking pool rather than failing — durability must not depend on an optimisation being available. A perf session measured 956.7 MB/s with it on against 917.2 without, every run better and no overlap between the distributions.
FELIX_DURABLE_VERIFY_ALL_ON_OPEN
Section titled “FELIX_DURABLE_VERIFY_ALL_ON_OPEN”Description: Checksum every record of every segment at startup.
Type: Boolean
Default: false
Example:
export FELIX_DURABLE_VERIFY_ALL_ON_OPEN="true"Trade-off: Off by default because startup would otherwise cost one full pass over all data on disk. The active segment is always fully scanned regardless, and every read verifies the records it returns — so bit rot in cold data is still caught, just when it is read rather than at boot.
Validation
Section titled “Validation”Check current configuration:
# Print effective configurationcargo run --release -p broker -- --dump-config
# Validate without startingcargo run --release -p broker -- --validate-configNext Steps
Section titled “Next Steps”- Full configuration details: Configuration Reference
- Troubleshooting: Troubleshooting Guide
- Performance tuning: Performance Guide
Reference: every remaining variable
Section titled “Reference: every remaining variable”The sections above cover the variables most deployments touch, each with an
example and the reasoning. What follows is the rest, in brief, so the page is
complete — scripts/check_env_reference.py fails the build if a variable
exists in the code and is not named here.
Variables used only by benchmarks, demos and the test harness are deliberately absent; they are listed in that script rather than here.
Control plane
Section titled “Control plane”| Variable | Default | Purpose |
|---|---|---|
FELIX_CONTROLPLANE_BIND |
0.0.0.0:8443 |
Address the control-plane API listens on. |
FELIX_CONTROLPLANE_METRICS_BIND |
— | Separate address for the metrics endpoint. |
FELIX_CONTROLPLANE_CONFIG |
— | Path to a config file; environment variables override it. |
FELIX_CONTROLPLANE_STORAGE_BACKEND |
memory |
memory or postgres. memory loses everything on restart. |
FELIX_CONTROLPLANE_POSTGRES_URL |
— | Connection string. Required when the backend is postgres. |
FELIX_CONTROLPLANE_POSTGRES_MAX_CONNECTIONS |
10 |
Pool size. Caps concurrent database work. |
FELIX_CONTROLPLANE_POSTGRES_CONNECT_TIMEOUT_MS |
5000 |
Bounds establishing a new physical connection. |
FELIX_CONTROLPLANE_POSTGRES_ACQUIRE_TIMEOUT_MS |
5000 |
Bounds waiting for a pooled connection before failing fast. |
FELIX_CONTROLPLANE_CHANGES_LIMIT |
1000 |
Maximum changes returned by one changefeed page. |
FELIX_CONTROLPLANE_CHANGE_RETENTION_MAX_ROWS |
10000 |
Bounds the append-only change tables. Smaller means a watcher can fall behind sooner and need a fresh snapshot. |
FELIX_CONTROLPLANE_OIDC_ALLOWED_ALGORITHMS |
— | Comma-separated JWS algorithms accepted from an upstream IdP. |
FELIX_EXCHANGE_TOKEN_TTL_SECONDS |
900 |
Lifetime of a Felix access token minted by the token exchange. The default is short to limit blast radius if a token leaks. Prefer refresh over raising it: a long-running process should refresh rather than hold one long-lived bearer token. |
FELIX_REFRESH_TOKEN_TTL_SECONDS |
2592000 |
Lifetime of a refresh token (30 days). This is how a long-running process stays authenticated without standing IdP credentials. Refresh tokens are single-use and rotate on every refresh, so this bounds a stolen and never used token — one that is used produces a replay, which revokes its whole chain immediately. |
FELIX_RAFT_NODE_ID |
— | This instance’s id in the metadata Raft group (see Metadata Raft). All three raft variables together select the raft backend, or startup fails on a partial set. |
FELIX_RAFT_DATA_DIR |
— | Where the Raft log, vote, and snapshots live. Must survive restarts: it is what makes a restart a rejoin rather than a fresh member. |
FELIX_RAFT_PEERS |
— | The initial group as id=host:port,... of every member’s main listener. Identical on every member. |
FELIX_RAFT_HEARTBEAT_MS |
150 |
Leader heartbeat interval within the metadata group. |
FELIX_RAFT_ELECTION_TIMEOUT_MIN_MS |
600 |
Lower edge of the election window. Must exceed the heartbeat — a window at or below it elects against healthy leaders, and startup refuses it. |
FELIX_RAFT_ELECTION_TIMEOUT_MAX_MS |
1200 |
Upper edge of the election window. Must exceed the minimum. |
FELIX_RAFT_SNAPSHOT_LOGS_SINCE_LAST |
500 |
Snapshot after this many log entries; metadata state is small, so snapshots are cheap and the log stays short. |
FELIX_RAFT_LOGS_KEPT_BEHIND_SNAPSHOT |
100 |
Entries kept behind the snapshot so a briefly-lagging member catches up from the log rather than a snapshot install. |
FELIX_RAFT_WRITE_TIMEOUT_MS |
10000 |
Overall budget for one proposal, elections and forwarding included. “No quorum” becomes an error at this bound rather than a hang. |
FELIX_READINESS_TIMEOUT_MS |
2000 |
Longest a readiness check may take before it counts as a failure. Keep it below the prober’s own timeout so the reason is reported rather than lost. |
FELIX_READINESS_CACHE_TTL_MS |
1000 |
How long a readiness answer is reused. Bounds probe cost regardless of how many probers there are, and bounds how long recovery takes to become visible. |
FELIX_SHUTDOWN_DRAIN_TIMEOUT_MS |
25000 |
Budget for draining in-flight requests after SIGTERM before tasks are cancelled. |
FELIX_SHUTDOWN_PREDRAIN_MS |
5000 |
How long to keep serving after readiness flips to draining, so load balancers remove this instance before the listener closes. 0 skips it. |
FELIX_REGION_ID |
local |
Region this instance reports. |
FELIX_BOOTSTRAP_ENABLED |
false |
Enables the first-run bootstrap endpoints. Leave off once credentials exist. |
FELIX_BOOTSTRAP_TOKEN |
— | Token the bootstrap endpoints require. |
FELIX_BOOTSTRAP_TOKEN_PREVIOUS |
— | The token being rotated out, still accepted alongside the current one so a rotation is a rolling deploy rather than an outage. Requires FELIX_BOOTSTRAP_TOKEN. |
FELIX_BOOTSTRAP_BIND_ADDR |
127.0.0.1:9095 |
Restricts bootstrap to a separate listener. |
FELIX_BOOTSTRAP_TLS_CERT |
— | PEM certificate chain the bootstrap listener presents. All three TLS variables together, or startup fails — a partial set is a misconfiguration, not “TLS off”. |
FELIX_BOOTSTRAP_TLS_KEY |
— | PEM private key for the bootstrap listener’s certificate. |
FELIX_BOOTSTRAP_TLS_CLIENT_CA |
— | PEM CA bundle; only clients presenting a certificate signed by it can complete the TLS handshake with the bootstrap listener. |
Node identity and membership
Section titled “Node identity and membership”| Variable | Default | Purpose |
|---|---|---|
FELIX_NODE_ID |
— | This broker’s identity in the cluster. Must be stable across restarts. |
FELIX_NODE_ADVERTISE_ADDR |
— | Address peers should reach this broker on. |
FELIX_CLIENT_ADVERTISE_ADDR |
— | Address clients should reach it on, when it differs from the peer address. |
FELIX_NODE_TOKEN / FELIX_NODE_TOKEN_FILE |
— | Access credential this broker presents to the control plane, on every call including the metadata feeds it seeds from (which require node.view:cluster:*). Required with FELIX_NODE_ID; a standalone broker may omit it, but then its sync is refused and it says so at startup. The file form is re-read every 30s, so whatever mints the credential — a Vault agent, SPIRE, a sidecar — can rotate it without a restart; a replacement that is already expired is declined rather than adopted. A broker joining a cluster refuses to start with an expiring token supplied by value and no refresh file, because nothing could then renew it. |
FELIX_NODE_REFRESH_TOKEN_FILE |
— | Path to this broker’s refresh token. With it the broker re-mints its access token before expiry and stays registered indefinitely. A path, not a value: refreshing spends the token and mints a replacement, so the broker writes the replacement back here — a restart that presented a spent one would be read as a replay and revoke the whole chain. The path must be writable. Setting FELIX_NODE_REFRESH_TOKEN instead fails startup, rather than locking the broker out at its first restart. Either this or FELIX_NODE_TOKEN_FILE is required when the credential carries an exp and FELIX_NODE_ID is set. |
FELIX_NODE_HEARTBEAT_INTERVAL_MS |
5000 |
How often a broker reports itself alive. |
FELIX_NODE_EXPIRY_TIMEOUT_MS |
15000 |
Silence after which a node is considered gone. Placement will not promote a replica whose last report is older than roughly twice this. |
FELIX_NODE_EXPIRY_SWEEP_INTERVAL_MS |
2000 |
How often expiry is evaluated. |
FELIX_SHARD_RECONCILE_INTERVAL_MS |
5000 |
How often placement re-plans. Bounds how quickly a failover happens. |
FELIX_CP_URL, FELIX_CP_SYNC_INTERVAL_MS |
— | Short aliases used by the demos and cluster harness. |
Peer protocol, between brokers
Section titled “Peer protocol, between brokers”| Variable | Default | Purpose |
|---|---|---|
FELIX_INTERNAL_BIND |
— | Address for the broker-to-broker QUIC endpoint. Separate from the client one. |
FELIX_INTERNAL_TLS_CERT |
— | PEM certificate chain this broker presents to peers, leaf first. Its DNS name must be the broker’s FELIX_NODE_ID. Set with the two below, or none of the three. |
FELIX_INTERNAL_TLS_KEY |
— | PEM private key for that certificate. Re-read with the certificate every 30s, so a renewal on disk is picked up by the next handshake without a restart. |
FELIX_INTERNAL_TLS_CA |
— | PEM bundle every peer’s certificate must chain to. With all three set, every peer connection is mutually authenticated and the certificate’s name is checked against the node id in both directions. Without them the peer link is encrypted but unauthenticated, and startup warns. |
FELIX_INTERNAL_CONNS_PER_PEER |
1 |
Connections held to each peer. |
FELIX_INTERNAL_STREAMS_PER_CONN |
4 |
Multiplexed streams per peer connection, so one large forwarded batch does not block smaller requests. |
FELIX_INTERNAL_MAX_INFLIGHT |
1024 |
Outstanding requests allowed per peer. |
FELIX_INTERNAL_REQUEST_TIMEOUT_MS |
5000 |
Bounds one forwarded request. |
FELIX_INTERNAL_HANDSHAKE_TIMEOUT_MS |
2000 |
Bounds dialling a peer that is gone. |
FELIX_INTERNAL_IDLE_TIMEOUT_MS |
60000 |
Idle timeout on a peer connection. |
FELIX_INTERNAL_RECONNECT_BASE_MS |
50 |
First reconnect backoff after losing a peer. |
FELIX_INTERNAL_RECONNECT_MAX_MS |
5000 |
Backoff ceiling. |
FELIX_PUBLISH_QUORUM_TIMEOUT_MS |
5000 |
Longest a Quorum publish waits for a majority before failing. |
FELIX_REPLICATION_REBUILD_MAX_CONCURRENT |
1 |
Halted followers this broker rebuilds at once, across every shard it leads. 0 rebuilds nothing and leaves every halt to an operator. |
FELIX_REPLICATION_REBUILD_BYTES_PER_SEC |
0 |
Bytes per second a rebuilding follower is shipped at. 0 is unlimited. |
Consumer groups
Section titled “Consumer groups”| Variable | Default | Purpose |
|---|---|---|
FELIX_GROUP_VISIBILITY_TIMEOUT_MS |
30000 |
How long a consumer’s claim on a record stands. |
FELIX_GROUP_MAX_ATTEMPTS |
5 |
Deliveries before a record is dead-lettered. |
FELIX_GROUP_MAX_WAIT_MS |
30000 |
Cap on a long-polling client’s requested wait. |
Durable storage tuning
Section titled “Durable storage tuning”| Variable | Default | Purpose |
|---|---|---|
FELIX_DURABLE_ROLLOVER_THRESHOLD_PERCENT |
— | How full a segment gets before a rollover is prepared. |
FELIX_DURABLE_MAX_OVERSHOOT_PERCENT |
— | How far a segment may exceed its target rather than splitting a batch. |
FELIX_DURABLE_REPAIR_CHECKSUM_TAIL |
— | Whether recovery re-verifies checksums over the tail as well as the structure. |
Client and transport
Section titled “Client and transport”| Variable | Default | Purpose |
|---|---|---|
FELIX_CLIENT_CONFIG |
— | Path to a client config file. |
FELIX_AUTH_TENANT, FELIX_AUTH_TOKEN, FELIX_TOKEN |
— | Credentials a client presents. |
FELIX_KEEPALIVE_MS |
— | QUIC keep-alive interval. |
FELIX_MAX_IDLE_TIMEOUT_MS |
60000 |
QUIC idle timeout before a connection is dropped. |
FELIX_EVENT_ROUTER_MAX_PENDING |
16384 |
Events buffered by the client’s router before it applies backpressure. |
FELIX_SUB_DEDICATED_THREAD, FELIX_SUB_DEDICATED_QUEUE_CAPACITY |
— | Give a subscription its own thread and queue. |
FELIX_SUB_DELIVERY_SHAPING, FELIX_SUB_EGRESS_CONNS |
— | Delivery shaping and egress fan-out on the broker. |
FELIX_PUBLISH_SHARDING / FELIX_PUB_SHARDING |
— | Route publishes to a worker by stream rather than round-robin. |
FELIX_PUMP_COLOCATE |
— | Colocate the delivery pump with the publish worker for a stream. |
FELIX_WORKER_THREADS |
— | Tokio worker threads. Defaults to the core count. |
FELIX_TIMING_SAMPLE_EVERY |
— | Sample rate for timing histograms. |
FELIX_SERVICE_INSTANCE_ID |
— | Instance identity reported in telemetry. |
