Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration

Wisp reads configuration from several sources, applied in order, each overriding the last:

  1. Built-in defaults (shown in the tables below).
  2. A TOML file, if you pass one: wisp relay wisp.toml.
  3. Environment variables (WISP_*), which override the file.
  4. A few CLI flags (--spider-admin, --db), which override everything for the settings they touch.

So an environment variable always wins over the same setting in the TOML file. Copy wisp.toml.example to wisp.toml to start from an annotated template.

WISP_PORT=8080 ./zig-out/bin/wisp

Command line

wisp [command] [config.toml]

Commands:
  relay [config]   Start the relay server (default if omitted)
  import           Import events from stdin (JSONL)
  export           Export all events to stdout (JSONL)
  help             Show help

Flags:
  --spider-admin <npub|hex>   Enable spider mode and follow this pubkey's contacts
  --db <path>                 Database path for import/export (default ./data)

The relay’s storage path comes from [storage] path / WISP_STORAGE_PATH. --db applies to the import and export commands only. --spider-admin accepts an npub (decoded to hex), unlike the file/env spider settings which expect raw hex.

Settings

Each setting lists its TOML key (under its [section]), its environment variable, type, and default. Settings with no environment variable are configurable only via the TOML file.

[server]

TOMLEnvTypeDefaultDescription
hostWISP_HOSTstring127.0.0.1Bind address. Use 0.0.0.0 only behind a reverse proxy.
portWISP_PORTu167777Listen port. HTTP (NIP-11, NIP-86, /metrics) and WebSocket share this one port.

[relay] (NIP-11 metadata)

TOMLEnvTypeDefaultDescription
nameWISP_RELAY_NAMEstringWispRelay name advertised in NIP-11.
descriptionstringA lightweight Nostr relayNIP-11 description.
pubkeyhex(unset)Operator pubkey in NIP-11.
contactstring(unset)Operator contact in NIP-11.

[storage]

TOMLEnvTypeDefaultDescription
pathWISP_STORAGE_PATHstring./dataLMDB data directory. Point at /dev/shm/wisp/data (tmpfs) for lowest latency; data is then lost on reboot (see warning).
map_size_mbu3210240LMDB maximum map size in MB. The hard upper bound on database size.
syncWISP_STORAGE_SYNCenummetaWrite durability: none, meta, or full. See Durability.

Warning: /dev/shm is volatile tmpfs — all data is lost on reboot. Use it only for benchmarks or disposable caches. For production, point path at persistent storage and/or configure backups.

Durability

sync controls how aggressively LMDB flushes to disk on each commit, trading throughput for crash safety:

ModeBehaviorOn crash / power loss
noneMDB_NOSYNC + MDB_NOMETASYNC: no flush on commit. Fastest.Recent commits can be lost, and the database can be corrupted.
meta (default)Flush data on every commit, defer only the metapage fsync.The last transaction may roll back, but the database stays consistent.
fullFsync on every commit. Durable.No acknowledged write is lost.

In the durable modes (meta/full) writes go through a group-commit writer thread: events that arrive close together are committed in one transaction, so a batch pays a single fsync rather than one per event. Throughput therefore scales with how many clients publish concurrently.

The default is meta: it never corrupts the database and at worst loses the last transaction on a crash. Use full when no acknowledged write may ever be lost, or none for maximum throughput on a relay where the data is disposable (a cache or benchmark) and a crash may corrupt the database. none uses a faster synchronous write path, since there is no fsync to amortize.

[limits]

TOMLEnvTypeDefaultDescription
max_connectionsWISP_MAX_CONNECTIONSu321000Maximum concurrent connections.
workersWISP_WORKERSu160Epoll worker threads. 0 = auto (min(CPU, 4)). Set 1 on a personal or memory-constrained relay to shed per-worker buffers and threads.
max_subscriptionsu3220Maximum open REQ subscriptions per connection.
max_filtersu3210Maximum filters per REQ.
max_message_sizeu3265536Maximum inbound WebSocket message size, in bytes.
max_event_tagsu322000Maximum tags per event.
max_content_lengthu32102400Maximum event content length, in bytes.
query_limit_defaultu32500Events returned per REQ when the client sets no limit.
query_limit_maxu325000Hard cap on events returned per REQ.
query_scan_multiplierWISP_QUERY_SCAN_MULTIPLIERu3220Caps entries scanned per query at limit × multiplier before stopping, so a selective filter cannot page-fault the whole event DB. 0 disables the cap.
max_event_agei64 (seconds)94608000Reject events whose created_at is older than this (default 3 years).
max_future_secondsi64 (seconds)900Reject events dated more than this far in the future (default 15 minutes).
min_pow_difficultyWISP_MIN_POW_DIFFICULTYu80Required NIP-13 proof-of-work leading-zero bits. 0 disables.

[rate_limits]

TOMLEnvTypeDefaultDescription
events_per_minuteWISP_EVENTS_PER_MINUTEu32120Per-IP event publish rate (token bucket). 0 disables.
queries_per_minuteWISP_QUERIES_PER_MINUTEu32300Per-IP limit on expensive query messages (REQ / COUNT / NEG_OPEN). 0 disables.

[timeouts]

TOMLEnvTypeDefaultDescription
idle_secondsWISP_IDLE_SECONDSu32300Close a connection after this many seconds with no activity.

[auth] (NIP-42)

TOMLEnvTypeDefaultDescription
requiredWISP_AUTH_REQUIREDboolfalseRequire NIP-42 AUTH before any read or write.
to_writeWISP_AUTH_TO_WRITEboolfalseRequire NIP-42 AUTH before publishing events.
relay_urlWISP_RELAY_URLstring(empty)Canonical relay URL bound into the AUTH challenge. Set this to your public wss:// URL when auth is enabled.

[security]

TOMLEnvTypeDefaultDescription
trust_proxyWISP_TRUST_PROXYboolfalseHonor X-Forwarded-For / X-Real-IP for the client IP. Enable only behind a reverse proxy whose backend port is not directly reachable, or clients can spoof their IP.
trusted_proxiesWISP_TRUSTED_PROXIEScsv(empty)IPs/prefixes of proxies whose forwarded headers are trusted. Empty with trust_proxy=true trusts any peer.
max_connections_per_ipWISP_MAX_CONNECTIONS_PER_IPu3210Per-IP concurrent connection cap.
ip_whitelistWISP_IP_WHITELISTcsv(empty)If set, only these IPs/prefixes may connect.
ip_blacklistWISP_IP_BLACKLISTcsv(empty)These IPs/prefixes are refused.

IP list entries match exactly unless they end in . (IPv4 prefix) or : (IPv6 prefix), e.g. 10.0.0. matches 10.0.0.010.0.0.255. CIDR notation and * wildcards are not supported.

[spider]

TOMLEnvTypeDefaultDescription
enabledWISP_SPIDER_ENABLEDboolfalseEnable spider sync.
relaysWISP_SPIDER_RELAYScsv(empty)Upstream relay URLs to pull from.
adminWISP_SPIDER_ADMINhex(empty)Pubkey whose contact list seeds the follow set.
pubkeysWISP_SPIDER_PUBKEYScsv(empty)Additional hex pubkeys to follow.
sync_intervalWISP_SPIDER_SYNC_INTERVALu32 (seconds)300Seconds between sync passes.

[negentropy] (NIP-77)

TOMLEnvTypeDefaultDescription
enabledWISP_NEGENTROPY_ENABLEDbooltrueEnable NIP-77 set reconciliation.
max_sync_eventsWISP_NEGENTROPY_MAX_SYNC_EVENTSu321000000Maximum event IDs buffered per reconciliation session.
max_sessionsWISP_NEGENTROPY_MAX_SESSIONSu324Concurrent reconciliation sessions per connection. Each can buffer up to max_sync_events IDs, so keep this small.

[management] (NIP-86)

TOMLEnvTypeDefaultDescription
admin_pubkeysWISP_ADMIN_PUBKEYScsv(empty)Hex pubkeys allowed to run NIP-86 relay-management commands (ban/allow pubkeys and IPs, etc.).

Spider mode

Spider mode keeps your relay populated by syncing events from the people you follow. Set admin to your hex pubkey (or pass --spider-admin npub1... on the CLI) and Wisp fetches your contact list and mirrors their notes from the configured relays:

[spider]
enabled = true
relays = "wss://relay.damus.io,wss://nos.lol,wss://relay.nostr.band"
sync_interval = 300
admin = ""  # your hex pubkey

Monitoring

Operational metrics are served in Prometheus format at GET /metrics on the relay port: connection counts, events stored/rejected/broadcast, REQ totals, and rate-limit counters. The endpoint honors ip_whitelist/ip_blacklist, so restrict it there or at your reverse proxy/firewall if it should not be public.