Skip to content

Configuration

Numax resolves its runtime configuration from four sources, applied in this order:

CLI flags > NX_* environment variables > numax.toml > runtime defaults

A later source only fills in what the earlier ones left unset. You can run a single node with nothing but CLI flags, or describe a full cluster with a TOML file and override individual fields at launch time. The same node configuration is accepted by both nx run and nx serve.


Generating a config file

Terminal window
nx config init --output numax.toml

This writes a fully commented file with all available fields and their defaults. Pass --force to overwrite an existing file.

To inspect what the runtime will actually use after all sources are merged:

Terminal window
nx config show --config numax.toml --effective

To validate a file without running a module:

Terminal window
nx config validate --config numax.toml

Full default file

# Numax configuration file.
# Precedence: CLI flags > NX_* environment variables > this file > defaults.
[storage]
datastore_path = "./nx-data"
[network]
listen = "0.0.0.0:9000"
peers = []
serialization_format = "bincode"
[tls]
# cert = "./certs/node.pem"
# key = "./certs/node-key.pem"
# ca = "./certs/ca.pem"
allowed_peers = []
insecure = false
[observability]
# listen = "127.0.0.1:9100"
log_level = "info"
log_format = "text"
request_timeout_secs = 5
[management]
# listen = "127.0.0.1:9102"
# token_file = "./management.token"
allow_non_loopback = false
request_timeout_secs = 10
[limits]
max_peers = 64
queued_ops_limit = 10000
op_log_limit = 10000
seen_ops_limit = 100000
max_message_size = "16MiB"
socket_timeout_secs = 30
reconnect_initial_delay = "500ms"
reconnect_max_delay = "30s"
peer_dead_after_failures = 3
anti_entropy_interval = "30s"
[discovery]
mode = "static"
# cluster_id = "default"
# advertised_endpoint = "127.0.0.1:9000"
# max_candidates = 1024
# Bootstrap: seeds, refresh_interval, retry_initial, retry_max, stale_after, max_seeds
# mDNS: instance_name, max_instances
# DNS-SRV: service_name, retry_interval, max_refresh_interval
# File: path, poll_interval, max_file_bytes

All fields are optional. Unknown fields are rejected at validation time.


[storage]

Local datastore location. The store is a sled embedded database.

FieldTypeDefaultDescription
datastore_pathpath./nx-dataDirectory where the local sled datastore is written

The datastore persists between runs. Each node must use its own directory. To start fresh, delete the directory before running.

[storage]
datastore_path = "./data/node-a"

[network]

Controls whether sync is enabled and who to connect to. Sync is disabled when this section is absent and no CLI/env flags provide a listen address.

FieldTypeDefaultDescription
listenstringAddress to listen on, e.g. 0.0.0.0:9000. Required to enable sync
peersstring[][]Peer addresses to connect to, e.g. ["127.0.0.1:9001"]
serialization_formatstringbincodeWire format: bincode (production) or json (debug inspection)
[network]
listen = "0.0.0.0:9000"
peers = ["127.0.0.1:9001", "127.0.0.1:9002"]
serialization_format = "bincode"

[tls]

Optional TLS and mTLS configuration. If this section is absent, connections are unencrypted.

To enable TLS, provide cert and key. To enable mTLS (mutual authentication), also provide ca.

FieldTypeDefaultDescription
certpathThis node’s TLS certificate (PEM)
keypathThis node’s TLS private key (PEM)
capathCA certificate used to verify peer certificates (PEM). Enables mTLS
allowed_peersstring[][]Allowlist of peer NodeIds (hex). Requires ca
insecureboolfalseSkip TLS certificate verification. Development only. Never use in production

Rules:

  • cert and key must be provided together.
  • insecure is mutually exclusive with ca and allowed_peers.
  • allowed_peers requires ca.
[tls]
cert = "./certs/node-a.pem"
key = "./certs/node-a-key.pem"
ca = "./certs/ca.pem"
allowed_peers = ["node-b-id-hex", "node-c-id-hex"]
insecure = false

[observability]

Optional HTTP endpoint for metrics and log configuration.

FieldTypeDefaultDescription
listenstringAddress to expose the metrics HTTP endpoint, e.g. 127.0.0.1:9100
log_levelstringinfoLog verbosity: trace, debug, info, warn, error
log_formatstringtextLog output format: text or json
request_timeout_secsinteger5Observability HTTP request timeout in seconds. Must be > 0
[observability]
listen = "127.0.0.1:9100"
log_level = "debug"
log_format = "json"
request_timeout_secs = 5

[management]

Controls the authenticated Management API started by nx serve. The listener is disabled unless a bearer token is available. Store the token in a file or provide it through NX_MANAGEMENT_TOKEN; nx config show never prints it.

FieldTypeDefaultDescription
listenstring127.0.0.1:9102Management API address when a token is configured
token_filepathFile containing the bearer token; trailing CR/LF characters are ignored
allow_non_loopbackboolfalseExplicitly permit binding to a non-loopback address
request_timeout_secsinteger10Maximum time to read HTTP headers and, separately, to execute a routed request. Must be > 0
[management]
listen = "127.0.0.1:9102"
token_file = "./management.token"
allow_non_loopback = false
request_timeout_secs = 10

The transport always caps request bodies at 16 MiB and processes at most 64 authenticated requests concurrently. These hard safety bounds are not TOML settings.

The Management API does not terminate TLS. Keep it on loopback behind a TLS-terminating reverse proxy whenever possible. A non-loopback bind requires allow_non_loopback = true and must only be used on a transport protected by TLS or equivalent network controls. Bearer tokens are never written to logs or effective configuration output.


[limits]

Fine-grained control over sync behavior and resource bounds. These apply only when sync is enabled. The defaults are conservative and suitable for most single-machine multi-node setups.

FieldTypeDefaultDescription
max_peersinteger64Maximum number of simultaneously connected peers
queued_ops_limitinteger10000Maximum ops queued for broadcast before backpressure
op_log_limitinteger10000Maximum ops kept in the local op-log for anti-entropy
seen_ops_limitinteger100000Maximum op IDs tracked for deduplication
max_message_sizestring16MiBMaximum sync message size. Accepts KiB, MiB or plain bytes
socket_timeout_secsinteger30Socket read/write timeout in seconds. Must be > 0
reconnect_initial_delayduration500msInitial backoff before reconnecting to a lost peer
reconnect_max_delayduration30sMaximum backoff ceiling for reconnect attempts
peer_dead_after_failuresinteger3Consecutive failures before a peer is marked dead
anti_entropy_intervalduration30sInterval between anti-entropy repair cycles

reconnect_initial_delay and reconnect_max_delay must be provided together and reconnect_initial_delay must be ≤ reconnect_max_delay.

All integer fields must be > 0.

[limits]
max_peers = 16
queued_ops_limit = 5000
op_log_limit = 5000
seen_ops_limit = 50000
max_message_size = "8MiB"
socket_timeout_secs = 15
reconnect_initial_delay = "250ms"
reconnect_max_delay = "15s"
peer_dead_after_failures = 5
anti_entropy_interval = "60s"

[discovery]

Controls how peers are discovered in v0.1.5, the current Numax version. Dynamic discovery is available alongside backward-compatible static peer lists.

FieldTypeDefaultDescription
modestringstaticstatic, bootstrap, mdns, dns-srv, or file
cluster_idstringdefaultDiscovery routing scope; not an authorization boundary
advertised_endpointstringderived from listenerConcrete endpoint published by bootstrap or mDNS
max_candidatesinteger1024Positive aggregate bound across all discovery sources; at most 4096 in bootstrap mode

Provider-specific fields are accepted only for their selected mode:

ModeRequired fieldsOptional fields and defaults
staticnonenone
bootstrapseedsrefresh_interval = "20s", retry_initial = "500ms", retry_max = "30s", stale_after = "2m", max_seeds = 32
mdnsinstance_namemax_instances = 1024
dns-srvservice_nameretry_interval = "5s", max_refresh_interval = "5m"
filepathpoll_interval = "2s", max_file_bytes = "1MiB"

Explicit peers from [network].peers, --peer, NX_PEER, or NX_PEERS remain an additional static source when a dynamic mode is selected. They never become bootstrap seeds. Every non-static mode enables sync and therefore requires [network].listen, --listen, or NX_LISTEN.

For compatibility, the effective candidate capacity is raised to at least the number of explicit peer entries. In bootstrap mode that effective value must also be at most 4096: a larger explicit peer list is rejected, not silently truncated. The bootstrap upper bound does not apply to static, mDNS, DNS-SRV or file mode. NX_DISCOVERY_MAX_CANDIDATES overrides the TOML value; validation uses the resolved mode and capacity.

Successful startup means local services are ready, not that discovery has found peers or CRDT state has converged. Candidate expiry stops new dialing but does not close admitted connections; periodic anti-entropy continues over those active connections. Recovery depends on retained operation and deduplication history, not merely on rediscovery. See the discovery contract for freshness, shutdown and mDNS resource limits.

[discovery]
mode = "bootstrap"
cluster_id = "production"
advertised_endpoint = "10.0.0.12:9000"
seeds = ["10.0.0.10:9000", "10.0.0.11:9000"]

Advertised endpoint resolution rules

The advertised_endpoint specifies the dialable address announced to peers through dynamic discovery providers (mDNS, bootstrap gossip, etc.):

  • Explicit unicast listener: When [network].listen specifies a concrete IP address (e.g., 192.168.1.50:9000), advertised_endpoint defaults to that address and is optional.
  • Wildcard listener (0.0.0.0 or [::]): An explicit advertised_endpoint is required because wildcard addresses are not dialable by remote peers.
  • Dynamic port binding (:0): If configured with port zero (e.g., 192.168.1.50:0), Numax automatically resolves the port to the actual ephemeral port assigned by the OS upon binding.

Environment variables

Environment variables sit between CLI flags and the TOML file in the precedence chain. They are useful for secrets (TLS paths), container environments, and CI.

VariableTypeEquivalent fieldDescription
NX_DATASTORE_PATHpath[storage].datastore_pathLocal datastore directory
NX_LISTENstring[network].listenSync listen address
NX_PEERstring[network].peers (single)Single peer address
NX_PEERSstring[network].peers (list)Comma-separated peer list
NX_SERIALIZATION_FORMATstring[network].serialization_formatbincode or json
NX_TLS_CERTpath[tls].certNode certificate path
NX_TLS_KEYpath[tls].keyNode key path
NX_TLS_CApath[tls].caCA certificate path
NX_ALLOWED_PEERSstring[tls].allowed_peersComma-separated peer NodeId allowlist
NX_TLS_INSECUREbool[tls].insecure1, true, yes, on / 0, false, no, off
NX_OBSERVABILITY_LISTENstring[observability].listenMetrics endpoint address
NX_MANAGEMENT_LISTENstring[management].listenManagement API address
NX_MANAGEMENT_TOKENstringsecretBearer token; overrides every token file
NX_MANAGEMENT_TOKEN_FILEpath[management].token_fileBearer-token file
NX_MANAGEMENT_ALLOW_NON_LOOPBACKbool[management].allow_non_loopbackExplicit external-bind opt-in
NX_MANAGEMENT_REQUEST_TIMEOUT_SECSinteger[management].request_timeout_secsHTTP header-read and routed-request timeout in seconds
NX_LOG_LEVELstring[observability].log_leveltrace, debug, info, warn, error
NX_LOG_FORMATstring[observability].log_formattext or json
NX_DISCOVERY_MODEstring[discovery].modestatic, bootstrap, mdns, dns-srv, or file
NX_DISCOVERY_CLUSTER_IDstring[discovery].cluster_idDiscovery routing scope
NX_DISCOVERY_ADVERTISED_ENDPOINTstring[discovery].advertised_endpointEndpoint to publish
NX_DISCOVERY_MAX_CANDIDATESinteger[discovery].max_candidatesAggregate candidate bound
NX_DISCOVERY_SEEDSCSV[discovery].seedsBootstrap seed endpoints
NX_DISCOVERY_REFRESH_INTERVALduration[discovery].refresh_intervalBootstrap refresh interval
NX_DISCOVERY_RETRY_INITIAL / NX_DISCOVERY_RETRY_MAXdurationmatching fieldsBootstrap retry bounds
NX_DISCOVERY_STALE_AFTERduration[discovery].stale_afterBootstrap candidate lease
NX_DISCOVERY_MAX_SEEDSinteger[discovery].max_seedsBootstrap seed bound
NX_DISCOVERY_INSTANCE_NAMEstring[discovery].instance_namemDNS instance name
NX_DISCOVERY_MAX_INSTANCESinteger[discovery].max_instancesmDNS instance bound
NX_DISCOVERY_SERVICE_NAMEstring[discovery].service_nameFully qualified DNS-SRV name
NX_DISCOVERY_RETRY_INTERVALduration[discovery].retry_intervalDNS retry interval
NX_DISCOVERY_MAX_REFRESH_INTERVALduration[discovery].max_refresh_intervalDNS refresh ceiling
NX_DISCOVERY_FILEpath[discovery].pathWatched peer file
NX_DISCOVERY_POLL_INTERVALduration[discovery].poll_intervalFile polling interval
NX_DISCOVERY_MAX_FILE_BYTESbyte size[discovery].max_file_bytesPeer-file size bound

NX_PEER and NX_PEERS are additive: if both are set, both peers are used.


Duration format

Duration fields in the TOML file and CLI flags accept:

FormatExampleMeaning
Milliseconds500ms500 milliseconds
Seconds5s5 seconds
Minutes2m2 minutes
Plain number55 seconds

Zero durations are rejected.


Two-node setup pattern

node-a.toml
[storage]
datastore_path = "./data-a"
[network]
listen = "0.0.0.0:9000"
peers = ["127.0.0.1:9001"]
serialization_format = "bincode"
[limits]
anti_entropy_interval = "30s"
[discovery]
mode = "static"
node-b.toml
[storage]
datastore_path = "./data-b"
[network]
listen = "0.0.0.0:9001"
peers = ["127.0.0.1:9000"]
serialization_format = "bincode"
[limits]
anti_entropy_interval = "30s"
[discovery]
mode = "static"
Terminal window
nx config validate --config node-a.toml
nx config validate --config node-b.toml
nx run my_module.wasm --config node-a.toml --settle-for 5s
nx run my_module.wasm --config node-b.toml --settle-for 5s