Configuration reference
Reference major Sockudo configuration sections and the deployment decisions they control.
Sockudo's TOML configuration is organized by runtime responsibility.
For configuration loading order, JSON equivalence, and guidance on what belongs in files, environment overrides, or secret stores, see Static configuration. The complete environment variable reference lists every runtime variable parsed by the server and push subsystem.
Top-level
| Key | Purpose |
|---|---|
host | Bind host for WebSocket and HTTP API. |
port | Bind port for WebSocket and HTTP API. |
max_connections | Per-node WebSocket connection limit. When exceeded the server returns close code 4100 so clients reconnect with backoff. 0 disables the limit. |
debug | Enables verbose diagnostics for local development. |
App manager
| Section | Purpose |
|---|---|
[app_manager] | Select app storage driver. |
[app_manager.array] | Static in-config app definitions. |
[[app_manager.array.apps]] | App ID, key, secret, enabled flag, limits, and policy. |
Ably compatibility keys
App.key and App.secret are always the implicit primary credential. When the
server is built with ably-compat, [ably_compat] can opt an app into additional
Ably key names without creating sibling apps:
[ably_compat]
enabled = true
realtime_admission = "accept"
attach_timeout_ms = 10000
max_token_ttl_ms = 86400000
token_request_timestamp_skew_ms = 900000
nonce_ttl_seconds = 900
stats_fixture_ingest_enabled = false
stats_queue_capacity = 4096
stats_flush_interval_ms = 10
stats_retention_seconds = 34560000
stats_max_scan_entries = 100000
stats_cas_retries = 8
[[ably_compat.keys]]
app_id = "app-id"
key_name = "app-key-readonly"
secret = "replace-with-a-secret"
capability = '{"chat:*":["subscribe","history"]}'
revocable_tokens = true
enabled = true
rotation_id = "2026-07"Each entry supports optional created_at_ms, expires_at_ms, and
revoked_at_ms rotation boundaries. Disabled, expired, or revoked keys reject
new key authentication. Tokens from a key with revocable_tokens = true are
also rejected after that key is disabled, revoked, expired, or replaced with a
different rotation_id. Secrets and bearer tokens are never written to logs.
TokenRequest timestamps are accepted only within the configured skew. Nonces
are claimed atomically in the configured cache for nonce_ttl_seconds, and
issued token records use the same cache, allowing issuance on one node and use
on another. Use a shared cache driver such as Redis in multi-node deployments.
The server's ably-compat Cargo feature explicitly enables native AI Transport,
recovery, delta, and push support. Runtime [ably_compat].enabled and the key
registry remain the opt-in boundary; Protocol V1 routes and frames are unchanged.
realtime_admission = "placement_constraint" is an operator/drain mode for a
listener: authenticated Ably WebSocket upgrades receive DISCONNECTED error
50320, prompting the SDK to try its configured fallback hosts over WebSocket.
It does not enable Comet, polling, streaming, SSE, or any other realtime
transport. The default is accept.
attach_timeout_ms bounds the complete native presence/recovery/history attach
path. On expiry Sockudo removes the partial subscription and returns
DETACHED/50003; the default is 10 seconds.
Ably delta=vcdiff is negotiated per realtime channel and uses a fixed bounded
compatibility cache; native [delta_compression] settings continue to configure
only Sockudo's Pusher/V2 delta contract and do not select the Ably VCDIFF mode.
Compatibility statistics use canonical UTC minute buckets in the configured
cache and derive hour, day, and month results when queried. Sum counters and
peak gauges retain their distinct rollup semantics. In multi-node or
restart-safe deployments, use a shared persistent cache such as Redis; the
memory cache is node-local and does not survive a restart.
Redis-backed entries retain their configured TTL across an individual Sockudo
node's graceful shutdown; use the explicit cache administration APIs for
destructive prefix cleanup.
The recording worker is bounded by stats_queue_capacity, combines at most 256
observations per write batch, and waits up to stats_flush_interval_ms to form a
batch. REST and realtime publishes that receive an application ACK wait for the
canonical bucket merge first. Outbound delivery accounting uses the nonblocking
bounded path, so an abrupt process crash can lose observations that were still
queued; queue saturation is reported as a drop rather than allowing unbounded
memory growth. Retention, bounded query scans, and compare-and-swap retries are
controlled by stats_retention_seconds, stats_max_scan_entries, and
stats_cas_retries.
stats_fixture_ingest_enabled exposes authenticated POST /stats only for
conformance fixture provisioning and defaults to false. Fixture intervals are
validated and written through the same minute-store and rollup contract as live
observations; the HTTP handler does not return canned fixture values.
Compatibility device registrations and channel subscriptions use
[push].storage_driver, so clustered or restart-safe deployments should select
the same durable push store used by native push APIs.
Runtime backends
| Section | Purpose |
|---|---|
[adapter] | Cross-node fanout. |
[cache] | Shared cache and coordination. |
[queue] | Webhook and push background work. |
[rate_limiter] | Request, connection, event, and push limits. |
Redis Sentinel and TLS
[database.redis] configures the Redis connection used by the Redis adapter, cache, queue, and rate limiter. When sentinels is non-empty, Sockudo connects through Redis Sentinel using a native Sentinel client (rather than a direct URL) and can secure both connection hops independently.
| Key | Purpose |
|---|---|
sentinels | List of { host, port } Sentinel nodes. A non-empty list enables Sentinel mode. |
name | Monitored master (Sentinel service) name. |
username / password | Auth for the master/replica data connection. |
sentinel_username / sentinel_password | Auth for the Sentinel control connection. |
[database.redis.sentinel_tls] | TLS for the client→Sentinel control connection. |
[database.redis.master_tls] | TLS for the client→master/replica data connection. |
Each TLS block accepts enabled, accept_invalid_certs (skips verification; dangerous), ca_path (PEM CA for private CAs), and client_cert_path + client_key_path (PEM pair for mutual TLS / client-certificate auth).
[database.redis]
name = "mymaster"
db = 0
username = "appuser"
password = "app-secret"
sentinel_username = "sentineluser"
sentinel_password = "sentinel-secret"
[[database.redis.sentinels]]
host = "sentinel-1.internal"
port = 26379
[[database.redis.sentinels]]
host = "sentinel-2.internal"
port = 26379
[database.redis.sentinel_tls]
enabled = true
ca_path = "/etc/sockudo/tls/ca.pem"
client_cert_path = "/etc/sockudo/tls/client.pem"
client_key_path = "/etc/sockudo/tls/client.key"
[database.redis.master_tls]
enabled = true
ca_path = "/etc/sockudo/tls/ca.pem"
client_cert_path = "/etc/sockudo/tls/client.pem"
client_key_path = "/etc/sockudo/tls/client.key"Note: Sentinel TLS secures the horizontal Redis adapter (pub/sub) path and Queue v2 when
queue.driver = "redis"inherits this topology. Aredis_pub_options.urloverride, if set, takes precedence for the adapter and uses its standalone connection path instead.
Protocol features
| Section | Purpose |
|---|---|
[recovery] | V2 replay buffers and resume behavior. |
[history] | Durable channel history. |
[versioned_messages] | V2 mutable-message storage, version paging, and retention. |
[presence_history] | Historical presence transitions and snapshots. |
[annotations] | V2 annotation publish/delete/summary surfaces. |
[delta] | Delta algorithms, cache size, and conflation behavior. |
[tag_filtering] | V2 event/tag/JMESPath subscription predicates and tag projection controls. Predicate source, AST, tree, and projected-message memory are hard-bounded. |
[webhooks] | Webhook delivery, batching, and retry. |
[ai_transport] | AI Transport validation and session-channel matching. |
[ai_transport.rollup] | Append rollup egress coalescing and orphan tracking. |
[[ai_transport.channels]] | Channel prefixes where AI Transport validation applies. |
Annotations, including the optional Ably projection, require both global
[annotations].enabled = true and app/channel policy opt-in. Set
annotations_enabled = true under [app_manager.array.apps.policy.channels]
or on the matching channel namespace. Enabling the Ably facade does not bypass
either gate, and Protocol V1 never receives annotation frames.
[versioned_messages].driver also selects the annotation authority. postgres,
mysql, dynamodb, scylladb, and surrealdb each use their native atomic or
conditional commit primitives for annotation serials, stable create IDs,
canonical events, projections, replay, and retention. The corresponding Cargo
feature and database configuration must be present. memory is a single-node
development implementation; clustered startup rejects it.
For any non-local adapter, use cache.driver = "redis" or
cache.driver = "redis-cluster" for compatibility tokens, nonce replay,
revocations, session-owner leases, idempotency receipts, and retained stats.
Sockudo fails startup or the affected security operation when this coordination
authority is unavailable; it does not fall back to a private per-process cache.
Push
| Section | Purpose |
|---|---|
[push] | Enables push, async behavior, limits, status retention. |
[push.providers.fcm] | Firebase Cloud Messaging credentials. |
[push.providers.apns] | Apple Push Notification service credentials. |
[push.providers.webpush] | Web Push VAPID configuration. |
[push.providers.hms] | Huawei Mobile Services credentials. |
[push.providers.wns] | Windows Notification Service credentials. |
[[push_rules]] | Optional channel-publish to push-notification rules. |
Capability tokens
Protocol V2 capability tokens do not currently have a TOML [auth.capability_tokens] switch. They
are accepted through the WebSocket token query parameter and refreshed with sockudo:auth.
Limits are compiled constants in sockudo-core/src/capability_token.rs: HS256 only, max token size
8 KiB, max client_id 128 bytes, max jti 128 bytes, max lifetime 24 hours, and 30 seconds clock
skew. Revocation uses signed HTTP POST /apps/{appId}/revocations and shared cache keys.
AI Transport and Mutable Defaults
Runtime config uses [versioned_messages]; VersionStore is the Rust trait/storage abstraction.
There is no separate [version_store] TOML section in the current code.
The following block is machine checked by sockudo-core tests. Update it whenever code defaults
change.
[versioned_messages]
enabled = false
driver = "memory"
max_page_size = 100
retention_window_seconds = 0
purge_interval_seconds = 300
purge_batch_size = 1000
max_purge_per_tick = 100000
[history]
enabled = false
rewind_enabled = true
backend = "postgres"
retention_window_seconds = 86400
max_page_size = 100
writer_shards = 16
writer_queue_capacity = 4096
purge_interval_seconds = 300
purge_batch_size = 1000
max_purge_per_tick = 100000
[history.postgres]
table_prefix = "sockudo_history"
write_timeout_ms = 5000
[presence_history]
enabled = false
retention_window_seconds = 86400
max_page_size = 100
[annotations]
enabled = false
[ai_transport]
enabled = false
max_accumulated_message_bytes = 1048576
max_appends_per_message = 4096
max_open_streaming_messages_per_channel = 1024
[ai_transport.rollup]
enabled = true
default_window_ms = 40
min_window_ms = 0
max_window_ms = 500
orphan_ttl_ms = 60000
wheel_tick_ms = 5
shards = 64
[push]
storage_driver = "memory"
queue_driver = "memory"
allow_memory_drivers = false
fcm_enabled = false
apns_enabled = false
webpush_enabled = false
hms_enabled = false
wns_enabled = false
accept_worker_count = 1
planner_worker_count = 1
shard_worker_count = 1
dispatch_worker_count = 1
dispatch_max_outbound_requests = 32
feedback_worker_count = 1
retry_worker_count = 1
queue_partition_count = 1
channel_shard_count = 1
fanout_fast_threshold = 10000
fanout_shard_size = 100000
fanout_sync_threshold = 0
backpressure_lag_threshold_secs = 60
publish_status_ttl_days = 30
stale_device_max_age_days = 90
dry_run = false
analytics_enabled = false
analytics_retention_days = 30
scheduler_interval_secs = 5
repair_interval_secs = 30
repair_min_age_secs = 30
repair_batch_size = 100
cleanup_interval_secs = 300
cleanup_batch_size = 1000
cleanup_max_deleted_per_tick = 100000
[push.retry]
max_attempts = 5
initial_backoff_ms = 1000
max_backoff_ms = 60000
max_elapsed_secs = 86400
jitter = true
jitter_ratio_percent = 20
respect_retry_after = true
[push.circuit_breaker]
failure_threshold = 5
cooldown_secs = 60
half_open_max_inflight = 10
[push.default_quotas]
acceptance_rps = 100
delivery_quota_daily = 0
fanout_max = 0
inflight_max = 1000
[push.payload_redaction]
redact_payload = true
redact_template_data = true
redact_provider_overrides = true
allow_debug_payload_logging = false
[[push_rules]]
enabled = true
channel_pattern = ""
event_filter = []
rate_limit_per_second = 100
[push_rules.payload_mapping]
title_field = "title"
body_field = "body"
template_data_field = "data"
include_remaining_fields = truemax_messages_per_channel, max_bytes_per_channel, max_events_per_channel, credential refs, and
external key refs default to unset and are omitted from the checked block.
storage_driver = "memory" and queue_driver = "memory" are node-local development drivers. In
mode = "production", Sockudo rejects them unless allow_memory_drivers = true is set explicitly.
Push publish admission also requires a healthy queue, a safe store, local pipeline workers, and
local provider-worker capability. Raw provider-specific recipients require the matching provider
worker before the request is accepted; shard-path publishes also require a local shard worker.
backpressure_lag_threshold_secs limits the oldest actionable ready or inflight push queue item
age; set it to 0 to rely only on queue-depth thresholds.
Push repair runs in monolith workers when repair_interval_secs > 0; it scans stale queued
durable publish logs and recreates missing push.publish.v1 queue work after the publish has been
queued for at least repair_min_age_secs.
Push cleanup runs in monolith workers when cleanup_interval_secs > 0. It purges terminal publish
statuses older than publish_status_ttl_days, delivery events and operator invalidations older
than analytics_retention_days, expired idempotency records, expired scheduler locks, and any
persisted dead-letter inspection rows written by a selected backend. Queue-native dead-letter
metadata is inspected through /apps/{appId}/push/deadLetters. Cleanup is bounded by
cleanup_batch_size per category and cleanup_max_deleted_per_tick overall.
Observability
| Section | Purpose |
|---|---|
[metrics] | Enables metrics and configures the Prometheus scrape endpoint. |
[metrics.prometheus] | Prometheus metric naming options. |
[metrics.tcp_exporter] | Optional metrics-rs TCP event exporter for live clients and sidecars. |
[logging] | Controls ANSI colors and tracing-target inclusion for text/JSON output. |
| logging environment | Runtime filters, structured output, lifecycle fields, and data-safety policy. |
Example production skeleton
host = "0.0.0.0"
port = 6001
debug = false
[adapter]
driver = "redis"
[cache]
driver = "redis"
[queue]
driver = "redis"
[metrics]
enabled = true
port = 9601
[metrics.tcp_exporter]
enabled = false
host = "127.0.0.1"
port = 5000
buffer_size = 1024
[push]
storage_driver = "postgres"
queue_driver = "redis"
publish_status_ttl_days = 30
analytics_retention_days = 30
cleanup_interval_secs = 300Queue v2 reliability
Queue v2 provides at-least-once delivery. Successful processors acknowledge a job; errors retry with bounded exponential backoff and jitter; exhausted jobs move to a dead-letter queue. Exactly-once execution is not promised, so handlers must remain idempotent. Redis, Redis Sentinel, and Redis Cluster additionally provide stable job IDs, deduplication, delayed jobs, renewable leases, stalled worker recovery, lifecycle events, and queue depth statistics through atomic Lua transitions.
Redis workers preserve the configured callback concurrency while pipelining
Redis I/O. worker_prefetch bounds the number of leased jobs buffered ahead of
each callback worker; the default 16 amortizes claim, acknowledgement, and
lease-renewal round trips without creating unbounded memory or failover
exposure. Lower it for a smaller redelivery window, or raise it when Redis
latency leaves callback workers idle. queue.redis.response_timeout_ms
defaults to 5000, allowing Sentinel clients to discard a stale primary
connection promptly; 0 disables that deadline.
For the legacy-equivalent high-throughput workload—batch enqueue, generated
job IDs, immediate delivery, the configured default attempt count, no
application deduplication key, and event_retention = 0—the Redis drivers use
compact length-delimited frames and one raw atomic Redis transaction. Workers
materialize each frame into the same leased Queue v2 state machine when they
claim it, so retries, acknowledgements, stalled recovery, and dead lettering
are unchanged while admission avoids per-job Redis writes. Supplying a stable
job_id, delay, deduplication key, attempt override, or lifecycle-event
retention selects the stricter per-job atomic path. Use that path when a caller
must safely retry an enqueue whose network result was ambiguous.
[queue]
driver = "redis" # memory, redis, redis-cluster, nats, rabbitmq, kafka, iggy, pulsar, google-pubsub, sqs, sns
[queue.reliability]
max_attempts = 5
retry_base_delay_ms = 1000
retry_max_delay_ms = 60000
retry_jitter = 0.2
lease_duration_ms = 30000
lease_renew_interval_ms = 10000
stalled_batch_size = 100
worker_poll_interval_ms = 500
worker_prefetch = 16
shutdown_timeout_ms = 30000
completed_retention = 1000
failed_retention = 10000
event_retention = 10000
deduplication_ttl_ms = 300000
memory_capacity = 100000
max_batch_size = 1000Broker-backed queues use native bulk/pipelined publishing rather than the
compatibility loop: NATS and RabbitMQ pipeline acknowledgements, Kafka feeds
librdkafka concurrently, Iggy and Pulsar use their producer batch APIs, Google
Pub/Sub reuses its batching publisher, and SQS/SNS use provider batches of at
most 10 entries. max_batch_size is an upper bound; a provider's smaller hard
limit still applies. Topic, stream, queue, and publisher handles are cached
after successful provisioning instead of issuing control-plane requests for
every job.
worker_prefetch also bounds concurrent callbacks for NATS, RabbitMQ, Pulsar,
Google Pub/Sub, and standard SQS queues. SQS FIFO batches remain serial to
preserve message-group order. max_attempts configures NATS delivery limits,
Kafka and Iggy retry/DLQ transitions, RabbitMQ republish/DLQ transitions, and
Pulsar's native dead-letter policy. SQS retry/dead-letter limits remain an AWS
queue redrive-policy concern; Sockudo does not advertise a DLQ unless it owns
that policy.
Queue v2 options are fail-closed. A broker that cannot honor per-job delay, deduplication, or attempt overrides returns an error instead of silently discarding the option. SQS standard queues support delays up to 15 minutes; SQS FIFO queues support stable IDs/deduplication but not per-message delay.
The Redis drivers use a new :v2:{queue-id}:* key namespace. All keys for one
logical queue share a Redis Cluster hash tag, so enqueue, claim, ack, retry,
lease renewal, delayed promotion, and dead-letter transitions are single-slot
and atomic. Existing RPUSH/BLPOP list keys are not deleted during startup or
shutdown; drain them with the previous release before switching producers.
When database.redis.sentinels is configured and queue.driver = "redis", the
queue resolves the current primary through native Sentinel support. Sentinel and
primary authentication/TLS settings remain independent. A queue.redis.url_override
selects standalone Redis/rediss instead and intentionally disables inherited
Sentinel discovery for that queue.
Primary promotion preserves at-least-once, not exactly-once, semantics. A hard primary loss can redeliver callbacks that were active or prefetched when the node failed; a planned Sentinel switchover can temporarily expose a larger duplicate window while the old primary is still reachable. Use the stable job ID as an idempotency key, and quiesce producers/workers before a planned switchover when duplicate callbacks must be minimized.
SNS is producer-only. Configuring a consumer on SNS now fails instead of reporting a successful no-op. A requested backend that was not compiled into the binary also fails startup instead of silently falling back to memory.