Configuration reference

Complete reference for nostrfy.toml: every key, its type, its default and exactly what it does — limits, database, daemon, access control and Blossom.

Basics

The configuration is a TOML file, by default named nostrfy.toml. Create it with init:

sh
nostrfy --config nostrfy.toml init

Validate it (recommended before every start):

sh
nostrfy --config nostrfy.toml check

Every command takes --config <path> (default nostrfy.toml).

General syntax:

toml
[section]
key = "string"
key = 8080
key = [1, 2]
key = true

Configuration sections

SectionPurpose
[relay]Identity, URLs and NIP toggles
[server]Network binding, API split, metrics
[rpc]NIP-86 management RPC (auth, body limit)
[limits]All limits and overload protections
[database]LMDB storage, search index, queue caps
[daemon]PID, log and stats files and rotation
[access]Initial access control lists (changeable at runtime)
[blossom]Blossom file server (media hosting)

Every key is optional; a missing key uses its default.

Section [relay] — relay identity

KeyTypeDefaultDescription
namestring"nostrfy"Relay name shown to clients via NIP-11
descriptionstring"A minimal and stable Nostr relay"Relay description (NIP-11)
pubkeystring (64 hex)""Administrator public key (NIP-11 pubkey field)
contactstring""Administrator contact URI (mailto: or https://)
iconstring""Relay icon image URL
post_policystring""URL pointing to the relay's posting policy
private_keystring (64 hex)""The relay's own secret key; required for NIP-29 groups
public_urlstring""Public URL, e.g. wss://relay.example.com
livekit_urlstring""LiveKit server URL for NIP-29 audio/video rooms
livekit_api_keystring""LiveKit API key
livekit_api_secretstring""LiveKit API secret (used to sign JWTs)
livekit_participants_interval_secsinteger15Seconds between LiveKit participant-list refreshes (the NIP-29 39004 event)
enabled_nipsarray of integers[]Explicit NIP allowlist
disabled_nipsarray of integers[]NIPs to disable (ignored when enabled_nips is non-empty)
reject_ephemeralbooleanfalseReject NIP-01 ephemeral events (kinds 20000-29999)
enabled_gitbooleanfalseAccept NIP-34 git events (kinds 1617-1633, 30617/30618)
require_powinteger0Required proof-of-work in leading zero bits
new_pubkey_min_age_secsinteger0Refuse events from pubkeys younger than this (seconds; 0 = off)
max_events_per_min_per_pubkeyinteger0Publish rate limit per pubkey (per minute; 0 = no limit)
max_groupsinteger1000Cap on the in-memory NIP-29 group store
require_authbooleanfalseRequire NIP-42 authentication for REQ/EVENT/COUNT/NEG (REST API reads require NIP-98)
send_auth_challengebooleantrueSend the AUTH challenge on connect
enabled_nip78_authbooleantrueRequire NIP-42 AUTH before accepting kind 78/30078 events
enabled_command_eventsbooleanfalseExecute kind:1 operator commands authored by the admin pubkey

Key details

  • private_key — the relay's own secret key, used to sign relay-generated events: NIP-29 group metadata (39000-39005) and NIP-43 role/membership events. Generate with nostrfy genkey; keep it secret. It is read once at startup, so changing it requires a restart; that restart also heals the stored group metadata — the previous key's relay-signed events are removed and the current state is republished under the new key (the group state itself is restored as-is, not rebuilt). The NIP-43 role state (membership, roles and invite codes) is healed the same way.
  • livekit_participants_interval_secs — how often (seconds) the relay refreshes the NIP-29 kind:39004 participant lists by calling the LiveKit Room Service API (ListParticipants) with the configured credentials. LiveKit webhooks sent to /.well-known/nip29/livekit/webhook refresh a group's list immediately; the poll reconciles missed events. Must be at least 1. Fixed at startup — requires a restart.
  • public_url — used to validate URL-bearing tags from clients: NIP-42 AUTH, NIP-62 vanish and NIP-98 admin auth. When empty, the relay falls back to host:port, which never matches a real client URL when binding 0.0.0.0 or 127.0.0.1 (a warning is logged). Always set it.
  • enabled_nips vs disabled_nips — the allowlist wins: when enabled_nips is non-empty, only its NIPs are advertised and disabled_nips is ignored. Both require a restart.
  • reject_ephemeral — kinds 20000-29999 are rejected, but the exempt kinds NIPs require to be relayed are still forwarded: 22242, 27235, 28934/28935/28936, 24133, 23194/23195, 24242 and 21059. Applies on SIGHUP.
  • enabled_git — opt-in NIP-34: accepts kinds 1617-1633 and 30617/30618 and advertises NIP-34. Off by default because patch payloads can be large. Applies on SIGHUP.

Section [server] — server settings

KeyTypeDefaultDescription
hoststring"127.0.0.1"Bind address; 0.0.0.0 accepts connections from anywhere
portinteger8080Port (1-65535); port 80 requires root
api_hoststring""Hostname dedicated to the REST API
metrics_enabledbooleantrueServe Prometheus metrics at /metrics
ws_pathsstring"root"WebSocket endpoint paths: root, inbox-outbox or all
inbox_write_policystring"any"Who may write to /inbox: "any" or "relay" (events must still carry a p tag)
outbox_write_policystring"any"Who may write to /outbox: "any" (the NIP-42-authenticated pubkey's own events) or "relay"
trusted_proxiesarray of strings[]Reverse-proxy addresses/CIDRs whose X-Forwarded-For is trusted (empty = trust no proxy)
landing_page_filestring""HTML file served to a plain browser GET of the relay URL instead of the built-in decoy page (empty = decoy)
assets_dirstring""Directory served under /assets/<name> for the landing page (empty = no /assets/ route)

Key details

  • host — 0.0.0.0 binds all IPv4 interfaces; 127.0.0.1 is local only.
  • port — 1-65535; port 80 requires root. This one port serves the WebSocket relay, the NIP-11 document, the REST API and the NIP-86 RPC together.
  • api_host — dedicates the REST API to a single hostname so the API and the relay can share one port behind a reverse proxy. Fixed at startup — requires a restart.
  • ws_paths — root serves / only, inbox-outbox serves /inbox and /outbox only, all serves both. Fixed at startup — requires a restart.
  • trusted_proxies — list only the proxy's own addresses (loopback for nginx/Caddy on the same host, the balancer's source range in the cloud). With it set, the client IP is derived from the last untrusted X-Forwarded-For entry for the per-IP caps, rate limit, blockip and the logs. Never add an address clients can reach directly — they could spoof the header and bypass the per-IP limits. Fixed at startup — requires a restart.
  • landing_page_file — by default a plain browser GET of the relay URL is answered with a built-in decoy page that reveals nothing about the relay. Point this at an HTML file to serve your own page instead (relay rules, contact, statistics); the NIP-11 document and WebSocket connections are unaffected. The file is read on demand and re-read when it changes, so an external job can regenerate it without a restart. It must be a regular file of at most 8 MiB (symlinks are not followed); when it is missing, the decoy page is served. Applies on SIGHUP.
  • assets_dir — a directory whose files are served under /assets/<name> (images, CSS, JS for the landing page). Only flat ASCII names of letters, digits, ., _ and - are served (no dot-files, subdirectories or symlinks); unknown extensions are sent as application/octet-stream. Files are read per request, so they can be replaced in place. Fixed at startup — requires a restart.

Section [rpc] — NIP-86 management

KeyTypeDefaultDescription
management_tokenstring""Bearer token for the management APIs
admin_pubkeystring (64 hex)""Administrator pubkey for NIP-98 management auth
max_admin_body_bytesinteger65536Body limit for the NIP-86 management RPC

The NIP-86 RPC is mounted on the relay's public POST / routes — there is no separate management port. management_token and admin_pubkey sometimes appear under [server] in old guides; those spellings are legacy aliases of these [rpc] keys.

Section [limits] — limits and protections

Connections and messages

KeyTypeDefaultDescription
max_connectionsinteger10000Maximum concurrent connections
max_connections_per_ipinteger64Max connections per source IP
max_ws_message_bytesinteger1048576Max bytes per WebSocket message/frame
socket_recv_buffer_kbinteger64Per-connection kernel receive buffer (KiB)
max_out_queue_bytesinteger262144Per-connection outgoing queue cap (bytes)
ws_idle_timeout_secsinteger300Close idle connections after this long
http_read_timeout_secsinteger30HTTP request-head timeout (slow-loris defense)
max_connections_per_sec_per_ipinteger0Max new connections per second per source IP

Subscriptions and queries

KeyTypeDefaultDescription
max_filtersinteger20Max filters per REQ
max_subscriptionsinteger20Max subscriptions per connection
max_limitinteger500Ceiling for the REQ limit
max_countinteger2000Ceiling for COUNT results
max_sub_id_leninteger64Max subscription id length (characters, not bytes)
max_sub_bytesinteger1048576Total subscription filter bytes per connection
max_req_response_bytesinteger33554432 (32 MB)Ceiling for the total bytes a single REQ response may send; responses that fit the outgoing queue are delivered in full; the REST API query pages enforce the same budget

Events

KeyTypeDefaultDescription
max_content_bytesinteger65536Max event content length in characters
max_tagsinteger2000Max tags per event
max_tag_value_bytesinteger1024Max bytes per tag value
max_created_at_future_secsinteger3600Tolerated future skew of created_at
group_late_publish_secsinteger3600Tolerated lag for NIP-29 group admin events (seconds)
max_neg_itemsinteger100000Max records per NIP-77 negentropy sync

Legacy aliases: limits.require_pow, limits.new_pubkey_min_age_secs and limits.max_indexed_words are still accepted as aliases of relay.require_pow, relay.new_pubkey_min_age_secs and database.max_indexed_words.

REST API

KeyTypeDefaultDescription
max_api_concurrentinteger8Max concurrent /api/v1 requests
max_api_limitinteger5000Ceiling for the API limit parameter
max_api_offsetinteger50000Ceiling for the API offset parameter
max_api_fetchinteger55001Max over-fetch window for offset queries — must cover max_api_offset + max_api_limit + 1 (0 = no bound)
max_api_search_bytesinteger2048Max bytes of the API search parameter

Live fan-out

KeyTypeDefaultDescription
live_batch_interval_msinteger20How often live events are flushed (ms)
live_batch_sizeinteger32Max events per live batch
live_bufferinteger65536Live fan-out queue size; above 10,000,000 is rejected at check/startup, above 1,000,000 is warned about

Section [database] — database

KeyTypeDefaultDescription
pathstring"./data"Database directory (LMDB)
max_dbsinteger32LMDB max named databases
max_readersinteger128LMDB max concurrent readers
map_sizeinteger1073741824 (1 GB)Floor for the memory map size (bytes)
max_map_sizeinteger1099511627776 (1 TB)Memory-map ceiling (bytes)
purge_interval_secsinteger300NIP-40 purge interval (seconds)
search_indexbooleantrueEnable the NIP-50 word index
reader_threadsinteger2Dedicated scan threads
max_indexed_wordsinteger32Words of each event's content indexed for search
meta_indexbooleantrueWrite the per-event metadata header used by the scan prefilter
disabled_fsyncbooleanfalseSkip the synchronous disk flush after every write batch
db_buffer_sizeinteger2048Initial per-connection WebSocket buffer (bytes); must stay below the write-buffer bound derived from max_ws_message_bytes / max_neg_items (rejected at check/startup otherwise)
db_request_timeout_secsinteger30How long a database request may wait before failing
max_db_queue_msgsinteger4096Max queued pending messages before failing fast
max_db_queue_eventsinteger262144Max events inside queued batches before failing fast
max_db_queue_bytesinteger268435456 (256 MiB)Max bytes of queued database requests before failing fast (0 = no byte cap)

Key details

  • map_size — the floor for the memory map: the map is always opened at least this large.
  • max_map_size — the ceiling, opened as a sparse virtual reservation: physical disk grows only with the data actually written. Raise it when you hit database map is full.
  • search_index = false — search still works (whole-word matching against content) but scans get slower; on a tiny VPS it halves the database. Recommended on small instances.
  • disabled_fsync — trades durability for throughput: writes commit into the OS page cache and a power loss may lose the most recent writes.

Section [daemon] — daemon

KeyTypeDefaultDescription
pid_filestring"./nostrfy.pid"PID file path
log_filestring"./nostrfy.log"Log file path; created owner-only (0600) — it carries client IPs and pubkeys
stats_filestring"./nostrfy.stats.json"Statistics file path; the snapshot is owner-only (0600) and a public subset is served at /relay/stats
stats_interval_secsinteger5Statistics write interval (seconds)
max_log_size_bytesinteger52428800 (50 MB)Log rotation size (0 = no rotation)
max_log_filesinteger5Rotated log generations to keep

Paths are resolved against the config file's directory, so they stay valid after the daemon changes its working directory.

/relay/stats needs no token and is readable cross-origin, so it serves a narrower view than the stats file: the host's free disk space and disk-full flag, the failure counters (db_errors, accept_errors, rebuild_failures, log_errors) and the per-reason connection-refusal breakdown are left out — together they would be a storage-capacity oracle for anonymous probers. They stay in the stats file (nostrfy stats prints them) and in /metrics. Note /metrics is unauthenticated too, so isolate it (api_host or the reverse proxy) if the capacity figures must stay private.

Section [access] — access control

KeyTypeDefaultDescription
restrict_relaybooleanfalseOnly allowlisted pubkeys may publish
blocked_kindsarray of integers[]Kinds to reject
allowed_kindsarray of integers[]Kind allowlist; only these kinds are accepted when non-empty
blocked_ipsarray of strings[]IP addresses refused at connection time
method_grantstable: pubkey → array of strings{}NIP-86 method grants for non-admin pubkeys (managed at runtime with assignmethod)

The pubkey allow/deny lists are not config keys — they live in the relay database (LMDB) and are managed at runtime:

sh
nostrfy relay allow npub1...
nostrfy relay deny npub1...
nostrfy relay list
  • restrict_relay = true — only the allowlisted pubkeys may publish, while reading stays open to everyone (any client can still subscribe and fetch).
  • A denied pubkey is always rejected when publishing and never served when reading.
  • method_grants — NIP-86 method grants for non-admin pubkeys (pubkey → method names, e.g. a moderator allowed banevent and listbannedevents). Seeded from the config on the first run, then managed at runtime with NIP-86 assignmethod/unassignmethod (inspected with listmethodassignees). Only moderation and read methods are grantable — permission, role, invite-claim and relay-identity management stay admin-only, and a banned pubkey is refused even with grants. See the management API.

Section [blossom] — Blossom file server

KeyTypeDefaultDescription
hoststring""Hostname for the Blossom server (empty = disabled)
storagestring"local"Backend: "local" (local_path) or "s3" (S3-compatible bucket)
local_pathstring"/var/lib/nostrfy/images"Local storage root for media files
max_upload_bytesinteger20971520 (20 MB)Max media file size
min_free_bytesinteger33554432 (32 MB)Disk space below which uploads are refused
s3_endpointstring""S3-compatible endpoint (e.g. R2)
s3_regionstring""S3 region (R2 uses "auto")
s3_bucketstring""S3 bucket name
s3_access_keystring""S3 access key
s3_secret_keystring""S3 secret key
restrict_uploadsbooleanfalseOnly allowlisted pubkeys may upload

Reloading at runtime (SIGHUP)

Editing the file and sending kill -HUP $(cat nostrfy.pid) reloads the config without a restart. Most settings take effect immediately; a few are fixed at startup:

Applies on SIGHUPRequires restart
relay.name, description, pubkey, contact, icon, post_policy, public_urlrelay.private_key
reject_ephemeral, enabled_git, enabled_nip78_authrelay.livekit_*, enabled_nips / disabled_nips
most of [limits], landing_page_file (read per request)api_host, trusted_proxies, metrics_enabled, ws_paths, assets_dir, database.*, daemon sizes, limit caps, blossom.*

[access] is not applied by a reload — the lists are seeded once at startup and then managed at runtime via NIP-86. The log warns when a restart-required setting changed, and a few startup-captured settings are not checked by the reload.

Common mistakes
MistakeFix
public_url unsetset wss://...
host left 127.0.0.1external clients can't connect
private_key unset with NIP-29run nostrfy genkey + restart
restrict_relay true with empty allowlisteveryone locked out
changing restart-only keys and only SIGHUPinguse nostrfy restart