故障排除
你最可能遇到的错误 — 端口、权限、TLS、缺少 NIP、发布和超时 — 附分步修复方法。
首先检查三件事:
nostrfy check校验你的配置(大多数错误是配置失误)。tail -f nostrfy.log显示日志 — 原因几乎总在那里。nostrfy restart干净地重启守护进程。
无法启动
error: cannot bind to 0.0.0.0:80: Permission denied
原因:80 端口只能由 root 绑定。
修复:用 sudo 运行,或将端口改为 8080 之类。
# 在配置文件中将 port = 8080 改掉,然后:
nostrfy --config nostrfy.toml starterror: cannot bind to ...: Address already in use
原因:另一个进程(旧的 nostrfy 或不同的服务器)已经占用了 该端口。
修复:
ss -tlnp | grep :8080# 如果 nostrfy 正在运行,重启它
nostrfy --config nostrfy.toml restartalready running (pid 1234); use 'nostrfy stop' or 'nostrfy restart'
原因:nostrfy 已在运行;start 拒绝启动第二个
实例。
修复:使用 nostrfy restart,或直接使用正在运行的实例。
nostrfy stop 卡住 / did not stop in time
原因:守护进程卡住或无响应。
修复:
ps aux | grep nostrfy
kill -9 <PID>
# 如果存在陈旧的 pid 文件,删除它
rm -f nostrfy.piderror: invalid nostrfy.toml: TOML parse error
原因:配置文件不是有效的 TOML。常见错误:忘记给字符串加引号, 或同一个键写了两次。
修复:错误消息包含行号。检查并修复该行。
# 正确示例
name = "my relay" # 字符串用 " 引起来
port = 8080 # 数字直接写
enabled_nips = [1, 50] # 列表用 [ ] 包裹error: cannot read nostrfy.toml: No such file or directory
原因:配置文件不存在。
修复:
nostrfy --config nostrfy.toml initerror: relay.private_key is not a valid secp256k1 secret key
原因:relay.private_key 不是有效的 64 字符十六进制密钥。
修复:运行 nostrfy genkey 生成正确的密钥(或设置 private_key = "")。
启动时日志中有大量警告
[WARN] 日志行告诉你配置问题。主要的:
| 警告 | 含义和修复 |
|---|---|
relay.public_url is empty and server.host is "0.0.0.0"... | 未设置 public_url — NIP-42 认证、NIP-62 消除和 NIP-98 管理认证
将无法工作。设置 wss://your-public-url。 |
relay.private_key is empty while NIP-29 is enabled... | 群组需要密钥。运行 nostrfy genkey。 |
unknown config key [relay].software is ignored | 配置中有未使用的遗留键(或拼写错误)。检查键名。 |
unknown config section [serve] is ignored | 段名拼写错误(例如 [serve] 而不是 [server])。
修复它。 |
relay.require_auth is true but relay.send_auth_challenge is false... | 这个组合会把所有人锁在外面。更改其中之一。 |
relay.require_pow = 64 ... practically unmineable | PoW 要求太高,没人能发布。降低 require_pow。 |
无法连接或行为异常
客户端收到 connection refused
原因:中继未运行,或防火墙阻止了端口。
修复:
curl http://127.0.0.1:8080/health
# 从外部(使用服务器的 IP/端口)
curl http://YOUR_SERVER_IP:8080/health
# 检查防火墙(示例:ufw)
sudo ufw status
# 如需要,开放端口
sudo ufw allow 8080外部客户端无法连接,本地可以
原因:server.host 仍是 127.0.0.1(默认),
它只接受本地连接。
修复:在配置中设置 host = "0.0.0.0" 并重启。
无法通过 Cloudflare Tunnel 连接
使用 Cloudflare Tunnel 时:
- 中继运行普通 HTTP;Cloudflare 终止 TLS,因此客户端使用
wss://。在中继上设置public_url = "wss://..."(这使 NIP-42 认证正常工作)。 - Cloudflare 会添加
X-Forwarded-Proto头。nostrfy 对ws/wss/http/https值一视同仁,因此通常 无需额外配置。
error: message too large 且连接关闭
原因:单条消息超过 max_ws_message_bytes(默认 1 MB)。
修复:如果需要更大的事件,提高 limits.max_ws_message_bytes — 但
也要检查客户端自己的限制。
too many subscriptions / too many filters 错误
原因:达到了每连接上限(订阅默认 20,过滤器 默认 20)。
修复:提高 limits.max_subscriptions / limits.max_filters(并检查客户端设置)。
负载下新连接被拒绝
原因:达到了 max_connections(默认 10000)、触发了按 IP 上限
(max_connections_per_ip,默认 64),或每秒连接速率限制
(max_connections_per_sec_per_ip)拒绝了突发。这些上限适用于每个连接 —
WebSocket 和普通 HTTP 一样。
修复:检查并调整设置。max_connections_per_ip = 0 禁用
按 IP 上限;max_connections_per_sec_per_ip = 0 禁用速率限制。这三项
设置需要重启。
连接一段时间后断开
原因:如果设置了 ws_idle_timeout_secs,空闲连接会被关闭。
健康的客户端会用 PONG 回应中继的 PING 并保持连接;只有死掉的对端会被清理。
修复:这是有意为之 — 默认 300 秒。设置 ws_idle_timeout_secs = 0 完全禁用它。
订阅以 CLOSED ... response too large 结束
原因:一个 REQ 的已存储事件超过了 max_req_response_bytes (默认 32 MiB)。只在事件非常大或过滤器非常宽时发生。
修复:缩小过滤器(更严格的 since/until、更低的 limit)或提高 max_req_response_bytes(0 禁用预算)。
NIP-11 supported_nips 列表中缺少某个 NIP
原因:公布的列表是动态的 — 当某个 NIP 定义的所有类型都被
拒绝时它会被隐藏:它们都在 blocked_kinds 中、都不在 allowed_kinds 中,或它们是 reject_ephemeral 拒绝的临时类型。
NIP-29/43/66 还需要 relay.private_key,NIP-86 需要 rpc.management_token 或 rpc.admin_pubkey。
修复:检查活动的访问列表 — NIP-86 listallowedkinds 显示
类型白名单,GET / 立即显示生效的 supported_nips。
移除屏蔽的类型或 reject_ephemeral 设置。
发布时的错误
发布失败时,OK 消息的第 4 个元素解释原因。常见的:
| 错误 | 含义和修复 |
|---|---|
invalid: signature verification failed | 事件签名无效(可能是客户端密钥损坏)。 |
invalid: content too large | 内容超过 max_content_bytes(默认 64K 字符)。缩短它或
提高限制。 |
invalid: too many tags | 标签数超过 max_tags(默认 2000)。 |
invalid: event creation date is in the future | 时间戳过于超前(超过 max_created_at_future_secs)。 |
mute: event contains secret key material | 内容或标签包含类似 nsec 的字符串。 切勿发布密钥。移除该字符串后事件会被接受。 |
duplicate: event already stored | 同一事件已存储(正常)。 |
blocked: pubkey not allowed | 该公钥被封禁(banpubkey)或在白名单之外。 |
blocked: kind not allowed | 此类型被禁止。 |
rate-limited: too many events | 该公钥超过了 max_events_per_min_per_pubkey(滑动 60 秒
窗口)。等一分钟再试,或提高/禁用该限制。 |
blocked: event has been banned | 该事件 id 被封禁。 |
blocked: event has been deleted | 重新发布已删除的事件。 |
auth-required: ... | 需要认证(当 relay.require_auth 开启时)。 |
restricted: your account is too new | 账户创建时间在 new_pubkey_min_age_secs 之内。等待并重试。 |
restricted: unknown group | 群组不存在(先创建它)。 |
restricted: this group is closed | 群组是 closed;没有邀请码的加入请求不会被处理。 |
Blossom 文件服务器
上传失败并返回 401
上传授权事件(kind 24242)被拒绝。检查:
- 令牌的
expiration标签存在且设置为未来的 unix 时间戳, - 对于 upload/media/delete,令牌带有包含 blob 的 sha256 的
x标签, server标签(如果存在)正好是配置的blossom.host(仅主机名,无 scheme/path),- 令牌是在最近 10 分钟内签名的(防止重放的新鲜度窗口),
- 且签名密钥是上传者自己的。
上传失败并返回 403
设置了 blossom.restrict_uploads = true 且该公钥不在白名单中 — 用 nostrfy blossom allow npub1... 添加它(守护进程会自动重载)。如果列表看起来
不对,nostrfy blossom list 会显示它。
上传失败并返回 409
客户端发送的 X-SHA-256 头与实际请求体不匹配(
声明的哈希是在不同字节上计算的 — 例如文件在哈希和发送之间发生了变化)。
客户端可以完全省略该头。
媒体主机上的 GET / 返回 NIP-11 文档
请求没有带着 Blossom Host 头到达中继。在反向代理中将 media.example.com (或 blossom.host 设置的值)指向同一端口,然后 nostrfy restart。
blob 在上传后立即 404
文件以其 SHA-256 内容寻址:使用上传响应中返回的确切哈希获取它
(/<sha256> 或 /<sha256>.<ext>)。不匹配意味着
客户端请求的哈希与它发送的字节不同。
搜索、群组和认证
搜索返回 0 条结果 / 意外结果
nostrfy 搜索匹配全词。注意:
search = "rust"匹配包含单词 "rust" 的事件,但"ru"不会作为子串匹配 "rust"。- 只搜索事件内容中的单词。
- 如果
search_index = false,搜索仍然可用但更慢。 - 如果 NIP-50 被禁用(
disabled_nips = [50]),search被忽略 (会发送 NOTICE)。
群组元数据(39000-39005)没有生成
原因:未设置 relay.private_key。群组快照由
中继自己的密钥签名,因此没有它就不会生成任何东西。
修复:
nostrfy --config nostrfy.toml genkey
nostrfy --config nostrfy.toml restartrestricted: unknown group 拒绝群组事件
原因:群组不存在。在 NIP-29 中,管理事件和加入请求 (9021)不能在群组创建(kind 9007)之前针对它。
修复:先用一个 9007 事件创建群组。
restricted: you are not an admin of this group
原因:管理操作(添加成员等)需要管理员(带角色的成员)。 创建者是管理员。
修复:请管理员授予你角色,或创建你自己的群组。
restricted: this group is closed
原因:群组是 closed;没有邀请码的加入请求
不会被自动批准。
修复:向管理员索取邀请码(9009)并用 code 标签加入。
不小心离开了群组,或群组没有管理员
原因:NIP-29 离开请求(kind 9022)对任何成员都被处理 — 包括 群组的最后一位管理员,他离开后不会留下管理员。没有管理员,就没人能发送管理事件 (9000/9001/9002/9008)了。
修复:用中继自己的密钥(relay.private_key,
即 NIP-11 self 公布的公钥)签署一条管理事件。根据 NIP-29,管理事件可以来自
"中继主密钥或……群组管理员",因此即使群组没有管理员,中继也接受由自己的密钥签名的群组管理事件。
例如,用 kind:9000 恢复一位管理员:
{
"kind": 9000,
"pubkey": "<relay self pubkey>",
"tags": [["h", "<group-id>"], ["p", "<member-hex>", "admin"]]
}用中继密钥签名并发布它。或者,用中继签名的 kind:9008 删除群组(其存储的事件被清除),然后用 kind:9007 重新创建。
此恢复需要配置 relay.private_key。
受保护事件被 auth-required 拒绝
原因:NIP-70 受保护事件(带 - 标签)只能由
已认证的作者在同一连接上发布。
修复:发布前在客户端启用 NIP-42 认证。
AUTH(NIP-42)返回 false
常见原因:
- 未设置或错误设置
relay.public_url— AUTH 事件的relay标签与 中继的 URL 不匹配。设置wss://...并重启。 - 挑战过期 — 你在不同的连接上发送了 AUTH,或复用了旧的挑战。
- 客户端时钟偏差 — AUTH 事件的
created_at必须在 现在的 ±10 分钟内。
NIP-86 管理 API 返回 401 unauthorized
原因:凭据缺失或错误。
修复:
- 设置
management_token并发送Authorization: Bearer <token>。 - 或设置
admin_pubkey并发送 NIP-98 认证事件(u标签必须与 中继 URL 完全匹配;需要payload标签)。 - 如果两者都未设置,管理 API 完全禁用。
NIP-98 认证事件因 scheme 或端口不同被拒绝
NIP-98 规范说 u 标签必须与绝对请求 URL 完全相同,因此
nostrfy 从 relay.public_url 推导期望的 URL:其 authority 加上
从 WebSocket scheme 映射的 HTTP scheme(wss:// → https://, ws:// → http://,去掉 nostr+)。没有 public_url 时,中继期望它提供的普通 http://host:port。带有
其他 scheme、不同/省略端口,或不同路径或查询的标签会被拒绝 — 将 relay.public_url 设置为客户端签名的公开地址。每个认证事件也是 一次性的:在其 60 秒
有效窗口内重放同一个 Authorization 头会被拒绝。
数据库和磁盘
database map is full: increase database.max_map_size
原因:达到了 LMDB 内存映射上限(默认 1 TB 虚拟地址空间;实际 磁盘使用随数据增长)— 实际上就是数据库满了。
修复:提高 database.max_map_size 并重启。
disk is full: refusing to commit N events
原因:可用磁盘空间少于 32 MB。写入停止(以保护数据);读取 继续。
修复:释放磁盘空间。一旦有空间,写入会自动恢复。
(df -h /path/to/data)
nostrfy check 报告 map_size must not exceed max_map_size
原因:database.map_size 大于 max_map_size。
修复:将 map_size 设置为等于或低于 max_map_size(默认值
就没问题)。
检查数据库大小
curl http://127.0.0.1:8080/relay/stats
# => "db_size_bytes" 以字节为单位备份 / 移动数据库
所有数据都在 database.path 目录中。 复制前先停止中继(复制活动数据库可能损坏它)。
nostrfy --config nostrfy.toml stop
cp -a ./data ./data-backup
# 使用本地 Blossom 存储时,也要备份 [blossom].local_path。
nostrfy --config nostrfy.toml start守护进程操作
nostrfy stats 显示 nostrfy is not running (no stats file)
原因:统计文件不存在 — 守护进程未运行,或它在几 秒前才启动。
修复:运行 nostrfy start,等几秒,然后重试。
日志无限增长
原因:max_log_size_bytes 为 0(轮转已禁用)。
修复:设置 max_log_size_bytes = 52428800(50 MB)和 max_log_files = 5。轮转自动进行。
重载后配置更改没有生效
原因:你重载(SIGHUP)了启动时固定的设置: private_key、api_host、metrics_enabled、LiveKit 设置,以及
NIP 启用/禁用列表。
修复:使用 nostrfy restart。这种情况下日志包含 "a restart is required"
警告。
中继不断自行死掉
原因:机器重启了,或中继内存不足(OOM)。
修复:
- 检查日志末尾:
tail -50 nostrfy.log。 - 检查机器是否重启:
uptime(非常短的运行时间意味着重启)。 - 检查内存:
free -h。 - 再次启动中继:
nostrfy start。
systemd 无法在 80 端口启动中继
以 root 运行的 systemd 服务可以绑定 80 端口。如果你把 User= 设为普通用户,
要么使用更高的端口(例如 8080),要么向单元添加 AmbientCapabilities=CAP_NET_BIND_SERVICE。
仍未解决?
- 检查日志:
tail -100 nostrfy.log— 它通常会指出直接 原因。 - 重新校验配置:
nostrfy check— 显示警告和错误。 - 收集复现细节:你在做什么、哪个客户端、确切的错误。
- 在项目仓库提问: https://github.com/iqbqioza/nostrfy — 提交 issue 时,附上复现步骤和日志。