Running the relay

Starting and stopping, logs, statistics, hot reloads, multiple instances and large-scale tuning.

Starting and stopping

Start the relay as a background daemon:

sh
nostrfy --config nostrfy.toml start

=> nostrfy started (pid 12345)

Or run it in the foreground, in the terminal:

sh
nostrfy --config nostrfy.toml start --foreground

Stop it with:

sh
nostrfy --config nostrfy.toml stop

Verify it is up:

sh
curl http://127.0.0.1:8080/health

=> {"status":"ok"}

Logs and statistics

The daemon writes to daemon.log_file. When the file grows past max_log_size_bytes, it rotates automatically (nostrfy.log.1, .2, ... up to max_log_files generations):

sh
tail -f nostrfy.log

The log level is controlled by the RUST_LOG environment variable (e.g. RUST_LOG=debug).

Statistics

Live statistics from the CLI:

sh
nostrfy stats

Or over HTTP:

sh
curl http://127.0.0.1:8080/relay/stats

Shows connections, events accepted/rejected, DB size, and more.

Prometheus metrics

sh
curl http://127.0.0.1:8080/metrics

Hot reload (SIGHUP)

After editing the config file, reload it without a restart:

sh
kill -HUP $(cat nostrfy.pid)
Applies on reloadRequires restart
relay identity, public_urlprivate_key
most [limits], reject_ephemeral, enabled_git, enabled_nip78_authapi_host, metrics_enabled, LiveKit settings
enabled_nips / disabled_nips, server.host / port / ws_paths
database.* incl. search_index, blossom.*

The log warns when a restart-required setting changed.

Running multiple instances

nostrfy supports several independent relays on one server (different ports). Each instance needs its own server.port, [daemon] pid_file/log_file/stats_file (shared values make the second instance refuse to start with already running), database.path, and — when used — its own api_host / blossom.host:

toml
[server]
port = 8080

[database]
path = "/var/lib/nostrfy-a"

[daemon]
pid_file = "/var/run/nostrfy-a.pid"
log_file = "/var/log/nostrfy-a.log"
stats_file = "/var/lib/nostrfy-a/stats.json"

[server]
port = 8081

[database]
path = "/var/lib/nostrfy-b"

[daemon]
pid_file = "/var/run/nostrfy-b.pid"
log_file = "/var/log/nostrfy-b.log"
stats_file = "/var/lib/nostrfy-b/stats.json"

Each instance is managed with its own config: nostrfy --config /etc/nostrfy/a.toml start etc.

Large-scale deployments

The relay is designed to scale to hundreds of thousands of connections on a single host — live delivery wakes only the subscribers that can match an event, and per-connection memory is kept small. Pushing into the millions requires host-level tuning:

SettingValueWhy
ulimit -n / systemd LimitNOFILE≥ 2× the target connections (+1000)every connection holds an fd
net.core.somaxconn≥ 1024pending accept queue for connection bursts
net.ipv4.tcp_fin_timeoutlow (e.g. 10)reclaims TIME_WAIT sockets faster
vm.overcommit_memory1 or 2the LMDB map is a large sparse virtual reservation

On FreeBSD the same knobs are kern.maxfiles / kern.maxfilesperproc plus ulimit -n, and kern.ipc.somaxconn replaces net.core.somaxconn. Per-connection kernel memory is ~80 KiB and user space ~10 KiB, so a million connections need roughly 90 GiB of kernel + user memory on top of the database.

Throughput (events per second)

Event ingestion is bound by two costs: the Schnorr signature check (about 30-50 µs per event) and the synchronous disk flush the LMDB writer performs after every commit batch. Both are tunable in nostrfy.toml:

SettingWhy
database.disabled_fsync = truecommits into the OS page cache (microseconds); a power loss loses only the writes since the last flush — start here
CPU cores ≥ 8 vCPUthe batch EVENT path verifies signatures in parallel across cores
database.search_index = falsedrops the NIP-50 word index write per event for write-heavy instances

The parallel signature check verifies every signature in a pending batch at once on a pool of worker threads (capped at 8; batches under 16 events verify inline). Each event's cheap checks still run first, so the reject reason text is identical to the sequential path — only the Schnorr work is spread across cores. A single-threaded build stays sequential.