Skip to content

Configuration Reference

The complete schema of ferrixd.toml. Every table and field, with types, defaults, and validation rules. For a guided walkthrough, see the configuration guide.

Unknown keys are errors

Configuration is fail-closed: every table rejects unknown keys. A typo prevents startup instead of being silently ignored. Validate with ferrixd check after every edit.

Conventions below: required fields have no default; everything else may be omitted. Durations are integer seconds; sizes are bytes.

[server] — required

Identity and listeners.

FieldTypeDefaultDescription
namestringrequiredadvertised server name, used in numerics and message prefixes
networkstring"ferrixnet"network name (ISUPPORT NETWORK=)
iconstringunsetnetwork icon URL (IRCv3 draft/network-icon), advertised as ISUPPORT draft/ICON=; use an HTTPS URL to a (square) image, optionally with a {size} template
casemapping"ascii" | "rfc1459""ascii"nick/channel case folding; must be identical network-wide
motdarray of string[]message of the day, one entry per line; REHASH-reloadable
cloak_keystringunsetHMAC key enabling host cloaking; omit to disable; keep secret and identical on linked servers
sidstring"42F"server ID for S2S; unique per network
link_bindsocket addressunsetinbound S2S listener, e.g. "0.0.0.0:6666"
tls_bindsocket addressrequiredprimary TLS listener, e.g. "0.0.0.0:6697"
plain_bindsocket addressunsetplaintext listener; loopback-only unless allow_plain_nonlocal
allow_plain_nonlocalboolfalsepermit a non-loopback plain_bind or ws_bind (e.g. behind a local TLS-terminating proxy)
wss_bindsocket addressunsetsecure WebSocket (wss://) listener; terminates TLS with the [tls] certificate, then negotiates the text.ircv3.net/binary.ircv3.net subprotocols
ws_bindsocket addressunsetplaintext WebSocket (ws://) listener; loopback-only unless allow_plain_nonlocal (prefer wss_bind)
passwordstringunsetconnection password: clients must send a matching PASS before registration (464 otherwise); REHASH-reloadable
ststableunsetIRCv3 strict transport security policy: { port = 6697, duration = 2592000, preload = false }; plaintext connections are told the TLS port, TLS connections the duration (seconds; 0 clears the policy)

Validation: a non-loopback plain_bind or ws_bind with allow_plain_nonlocal = false is a configuration error.

[tls] — required

FieldTypeDefaultDescription
certpathunsetPEM certificate chain (leaf first)
keypathunsetPEM private key
self_signed_devboolfalsegenerate an ephemeral self-signed cert at startup — development only; ignored when cert/key are set
dev_hostnamesarray of string["localhost"]SANs for the dev certificate

Validation: cert and key must be set together; if neither is set, self_signed_dev must be true.

[limits] — optional

All fields optional; defaults shown. See also Limits & Defaults for the hardcoded constants.

FieldTypeDefaultDescription
max_tag_bytesint8191wire budget for the message-tags section (IRCv3)
max_body_bytesint512wire budget for the message body (RFC 1459)
max_line_bytesint8704fatal frame length — longer frames drop the connection; must be ≥ max_tag_bytes + max_body_bytes
registration_timeout_secsint30seconds a connection may stay unregistered
handshake_timeout_secsint15TLS handshake budget
ping_interval_secsint120idle seconds before a server PING; a second missed interval disconnects
max_clients_per_ipint10simultaneous connections per source IP
max_channelsint50channels per client (CHANLIMIT); opers exempt
sendq_linesint2048outbound queue depth in lines; overflow disconnects (SendQ exceeded)
recv_burstint20inbound token-bucket burst allowance
recv_rateint10sustained inbound commands/second; exhaustion disconnects (Excess Flood)
history_lenint500retained messages per chathistory target; 0 = current-run only
history_max_targetsint50000cap on distinct in-memory history targets; LRU-evicted beyond

[[accounts]] — optional, repeatable

SASL seed accounts. See Accounts & SASL.

FieldTypeDefaultDescription
namestringrequiredaccount name
passwordstringunsetplaintext password, Argon2id-hashed at startup — development convenience
password_hashstringunsetprecomputed Argon2 PHC string (ferrixd hash-password) — production
scramstringunsetprecomputed SCRAM-SHA-256 credential (<iterations>:<b64 salt>:<b64 stored_key>:<b64 server_key>). Required for SCRAM logins on a password_hash account — the server never sees that account's plaintext and cannot derive them. ferrixd hash-password --toml prints password_hash and scram together; an account seeded with plaintext password gets SCRAM credentials automatically
fingerprintsarray of string[]permitted TLS client-cert SHA-256 fingerprints (lowercase hex) for SASL EXTERNAL

REHASH-reloadable. Self-registered accounts (via REGISTER) are stored in [persistence] and merged on top.

[[operators]] — optional, repeatable

IRC operator credentials for OPER. See Operators & Moderation.

FieldTypeDefaultDescription
namestringrequiredoperator name
passwordstringunsetplaintext (dev only)
password_hashstringunsetArgon2 PHC string (production)
hostsarray of string[]hostmask globs (nick!user@host or bare IP) allowed to use this block; anywhere else gets 491 ERR_NOOPERHOST; empty = any host

Validation: each operator needs password or password_hash. REHASH-reloadable.

[[bans]] — optional, repeatable

Startup K-lines, enforced at registration.

FieldTypeDefaultDescription
maskstringrequirednick!user@host glob (*, ?)
reasonstring"Banned"shown to the banned client

REHASH-reloadable. Runtime additions: KLINE/DLINE (moderation guide).

[[webirc]] — optional, repeatable

Trusted WEBIRC gateways (IRCv3 WEBIRC). A web/IRC gateway may rewrite a client's apparent host and IP so users behind it are seen — and moderated — by their real address. Empty (the default) disables the WEBIRC command.

FieldTypeDefaultDescription
namestringrequiredgateway identifier, matched against the WEBIRC <gateway> parameter
passwordstringrequiredshared secret the gateway sends as WEBIRC <password>; compared in constant time — use a long random value
hostsarray of stringrequiredsource-address globs (e.g. "127.0.0.1", "10.0.0.*") the gateway may connect from

A WEBIRC is accepted only when it is the connection's first command (before CAP/NICK/USER/PASS), the real peer address matches one of hosts, and the password matches; the rewritten IP is then re-checked against D-lines. Any failure closes the connection. REHASH-reloadable.

[persistence] — optional

SQLite durability for history, registered channels, and self-registered accounts. Omit for in-memory-only operation. See Message History.

FieldTypeDefaultDescription
pathpathrequiredSQLite database file (created if missing; WAL mode)
load_limitint5000most-recent history rows loaded into RAM at startup

[metrics] — optional

FieldTypeDefaultDescription
bindsocket addressrequiredHTTP listener for /metrics; bind to loopback — the endpoint has no auth

Catalogue: Metrics reference.

[plugins] — optional

WASM plugin host. See WASM Plugins.

FieldTypeDefaultDescription
dirpathrequireddirectory scanned for *.wasm at startup (sorted filename order)
fuelint5000000per-hook-call instruction budget
max_memoryint16777216per-instance linear-memory cap, bytes
expose_private_messagesboolfalsefeed user-to-user DMs to the ferrix_on_private_message hook (a privacy decision — off unless you opt in)
state_dirpathunsetdirectory for host-managed per-plugin KV state files; unset → in-memory only
grantstable{}per-plugin capability grants, plugin name → list of capability names (currently "send_notice"); deny-by-default

S2S peer definitions. See Federation.

FieldTypeDefaultDescription
namestringrequiredpeer's advertised server.name; must match at handshake
connectsocket addressunsetpeer's link_bind; omit for accept-only links
fingerprintstringrequiredpeer TLS cert SHA-256, lowercase hex (ferrixd fingerprint) — pinned
passwordstringrequiredshared link secret, compared in constant time
protocolstring"ferrix"wire protocol on this link: "ferrix" (native) or "ts6" (charybdis-family bridge, e.g. solanum)

What REHASH reloads

Reloadable at runtimeRestart required
[[accounts]], [[operators]], [[bans]], [[webirc]], server.motd, server.password, [tls] certificate/key, [[links]] definitionslistener bind addresses, [limits], [persistence], [metrics], [plugins], sid, cloak_key, casemapping

[tls] certificate/key reload is live for every TLS listener (no dropped connections; a bad PEM leaves the previous material armed). REHASH refreshes the [[links]] definitions so operator CONNECT sees edits, but it does not start or stop the boot-time auto-dial loops — use CONNECT/SQUIT to bring newly added or removed links up or down.

Complete annotated example

The exact file ferrixd gen-config writes:

toml
# ferrixd example configuration.
#
# Copy to `ferrixd.toml` and adjust (or run `ferrixd gen-config` to write this
# file for you). Validate any changes with `ferrixd check` before restarting.
# The parser rejects unknown keys (fail-closed configuration), so a
# typo is an error, not a silently-ignored setting.
#
# Helper subcommands for the values below:
#   ferrixd gen-cert       -> a self-signed [tls] cert + key (dev)
#   ferrixd hash-password  -> an Argon2id `password_hash` for accounts/operators
#   ferrixd fingerprint    -> a cert fingerprint for [[links]] / SASL EXTERNAL

[server]
# Advertised server name (used in numerics / source prefixes).
name = "irc.example.test"

# Advertised network name (ISUPPORT NETWORK).
network = "ferrixnet"

# Optional network icon URL (IRCv3 draft/network-icon), advertised as the
# `draft/ICON` ISUPPORT token. Use an HTTPS URL to a (preferably square) image;
# a literal `{size}` in the URL is passed through for clients to substitute.
# icon = "https://example.org/icon.svg"

# Network-wide case folding for nicks/channels: "ascii" (default) or "rfc1459".
casemapping = "ascii"

# HMAC key enabling host cloaking (omit to disable). Keep this secret — it makes
# cloaks unforgeable. Authenticated users are cloaked as account.<network>.
# cloak_key = "change-this-to-a-long-random-secret"

# Server id (SID) and optional inbound S2S link listener.
sid = "42F"
# link_bind = "0.0.0.0:6666"

# Message of the day, one entry per line (optional).
motd = [
    "Welcome to ferrixd.",
    "Be excellent to each other.",
]

# TLS is the primary, mandatory transport.
tls_bind = "0.0.0.0:6697"

# Plaintext is disabled by default. Uncomment ONLY for local testing;
# binding to a non-loopback address will be rejected unless you also set
# `allow_plain_nonlocal = true` below.
# plain_bind = "127.0.0.1:6667"
# allow_plain_nonlocal = false

# IRC over WebSockets (IRCv3). `wss_bind` reuses the [tls] certificate and is the
# recommended form for browser clients. `ws_bind` is plaintext and — like
# `plain_bind` — loopback-only unless `allow_plain_nonlocal = true`.
# wss_bind = "0.0.0.0:443"
# ws_bind  = "127.0.0.1:8080"

# Optional connection password: clients must send `PASS <password>` before
# completing registration (rejected with 464 otherwise).
# password = "change-me"

# Optional IRCv3 strict transport security (`sts`) policy. Plaintext clients
# are told the TLS port to reconnect to; TLS clients are told to remember the
# policy for `duration` seconds (0 clears it).
# sts = { port = 6697, duration = 2592000, preload = false }

[tls]
# Option A — provide a real certificate chain and private key (PEM):
# cert = "/etc/ferrixd/fullchain.pem"
# key  = "/etc/ferrixd/privkey.pem"

# Option B — generate an ephemeral self-signed certificate at startup.
# Convenient for development; NEVER use in production. Ignored if cert/key
# are set above.
self_signed_dev = true
dev_hostnames = ["localhost", "irc.example.test"]

[limits]
# Wire-length budgets. Kept separate for tags and body per IRCv3.
max_tag_bytes = 8191
max_body_bytes = 512

# Fatal frame length before the connection is dropped (DoS guard).
# Must be >= max_tag_bytes + max_body_bytes plus a little slack.
max_line_bytes = 8704

# Seconds a connection may stay unregistered before it is closed (slot guard).
registration_timeout_secs = 30

# Seconds allowed for the TLS handshake before the attempt is aborted.
handshake_timeout_secs = 15

# Retained messages per target for chathistory (0 keeps only the current run).
history_len = 500

# Cap on the number of distinct in-memory history targets (channels + DM pairs).
# Bounds total history memory regardless of activity; least-recently-active
# targets are evicted past this. Persisted rows (if enabled) are unaffected.
history_max_targets = 50000

# Idle seconds before the server PINGs a quiet client (disconnects on a 2nd miss).
ping_interval_secs = 120

# --- DoS controls ---
# Maximum simultaneous connections from one source IP (connection throttling).
max_clients_per_ip = 10
# Maximum channels a single client may be in at once (advertised as CHANLIMIT).
max_channels = 50
# Outbound SendQ depth in lines; a client whose queue overflows is dropped.
sendq_lines = 2048
# Inbound command burst allowance, then sustained rate/sec (token bucket).
# Exceeding this disconnects the client with "Excess Flood".
recv_burst = 20
recv_rate = 10

# --- SASL seed accounts ---
# Each account may authenticate via SASL PLAIN (password) and/or EXTERNAL
# (TLS client-certificate fingerprint).
#
# [[accounts]]
# name = "alice"
# # Development convenience: plaintext, hashed with Argon2id at startup. This
# # form also derives the account's SCRAM-SHA-256 credentials automatically.
# password = "change-me"
# # Preferred in production: a pre-computed Argon2 PHC hash instead of `password`.
# # The server never sees the plaintext, so SCRAM credentials cannot be derived
# # from it — supply them explicitly for SASL SCRAM-SHA-256. Both lines are
# # printed together by `ferrixd hash-password --toml`.
# # password_hash = "$argon2id$v=19$m=19456,t=2,p=1$...$..."
# # scram = "4096:<b64 salt>:<b64 stored_key>:<b64 server_key>"
# # SASL EXTERNAL: permitted client-cert SHA-256 fingerprints (lowercase hex).
# # fingerprints = ["a1b2c3..."]

# --- IRC operators (used by the OPER command) ---
# [[operators]]
# name = "admin"
# password = "change-me"          # or password_hash = "$argon2id$..."
# # Optional hostmask allowlist for this block: `nick!user@host` globs or bare
# # IPs. OPER from anywhere else is refused with 491. Empty/omitted = any host.
# # hosts = ["*!*@203.0.113.7", "*!*@*.trusted.example"]

# --- Startup server bans (K-Lines), matched at registration ---
# [[bans]]
# mask = "*!*@203.0.113.0/24"     # nick!user@host glob (* and ?)
# reason = "Banned network"

# --- Trusted WEBIRC gateways (IRCv3 WEBIRC) ---
# A web/IRC gateway may rewrite a client's apparent host and IP so users behind
# it are seen (and moderated) by their real address. A gateway must present the
# shared password AND connect from one of the `hosts` globs; both are checked
# (the password in constant time) before any rewrite. Empty = WEBIRC disabled.
# [[webirc]]
# name = "kiwi"                    # matched against the WEBIRC <gateway> field
# password = "long-random-shared-secret"
# hosts = ["127.0.0.1", "10.0.0.*"]  # source-address globs the gateway may use

# --- Durable persistence (SQLite; optional) ---
# When set, chathistory AND channel registrations survive restarts (they share
# the same database file). Omit for in-memory-only state.
# [persistence]
# path = "/var/lib/ferrixd/ferrixd.db"
# load_limit = 5000               # recent messages to load into RAM at startup

# --- Prometheus metrics endpoint (optional; bind to loopback) ---
# [metrics]
# bind = "127.0.0.1:9090"         # scrape at http://127.0.0.1:9090/metrics

# --- WASM plugin host (optional) ---
# Loads every *.wasm file in `dir` at startup. Plugins are sandboxed and run
# under a per-call fuel (instruction) budget; a plugin's on_message hook may
# block channel messages. See crates/ferrixd/src/plugin.rs for the ABI.
# [plugins]
# dir = "/etc/ferrixd/plugins"
# fuel = 5000000                  # per-hook-call instruction budget
# max_memory = 16777216           # per-instance linear-memory cap (bytes)
# expose_private_messages = false # feed DMs to plugins (privacy: your call)
# state_dir = "/var/lib/ferrixd/plugin-state"  # persist plugin KV stores
#
# Capability grants (deny-by-default): plugin file stem -> capabilities.
# [plugins.grants]
# "20-modbot" = ["send_notice"]

# --- S2S peer links ---
# Trust is the pinned TLS certificate fingerprint plus the shared password.
# Set server.link_bind above to accept inbound links.
# Links refuse to form cycles: keep the topology a tree.
# [[links]]
# name = "irc.peer.test"
# connect = "peer.example:6666"   # omit for accept-only links
# fingerprint = "a1b2c3…"         # peer cert SHA-256 (lowercase hex)
# password = "shared-link-secret"
# protocol = "ferrix"             # or "ts6" to bridge a charybdis-family
#                                 # IRCd (solanum, …) into the network

Dual-licensed under MIT or Apache-2.0.