疑難排解

你最可能遇到的錯誤 — 連接埠、權限、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 之類。

sh
# 在設定檔中將 port = 8080 改掉,然後:
nostrfy --config nostrfy.toml start

error: cannot bind to ...: Address already in use

原因:另一個處理程序(舊的 nostrfy 或不同的伺服器)已經佔用了 該連接埠。

修正:

sh
ss -tlnp | grep :8080
sh
# 如果 nostrfy 正在執行,重新啟動它
nostrfy --config nostrfy.toml restart

already running (pid 1234); use 'nostrfy stop' or 'nostrfy restart'

原因:nostrfy 已在執行;start 拒絕啟動第二個 執行個體。

修正:使用 nostrfy restart,或直接使用正在執行的執行個體。

nostrfy stop 卡住 / did not stop in time

原因:常駐程式卡住或無回應。

修正:

sh
ps aux | grep nostrfy
kill -9 <PID>
# 如果存在過期的 pid 檔案,刪除它
rm -f nostrfy.pid

error: invalid nostrfy.toml: TOML parse error

原因:設定檔不是有效的 TOML。常見錯誤:忘記給字串加引號, 或同一個鍵寫了兩次。

修正:錯誤訊息包含行號。檢查並修正該行。

toml
# 正確範例
name = "my relay"        # 字串用 " 包起來
port = 8080              # 數字直接寫
enabled_nips = [1, 50]   # 清單用 [ ] 包裹

error: cannot read nostrfy.toml: No such file or directory

原因:設定檔不存在。

修正:

sh
nostrfy --config nostrfy.toml init

error: 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 unmineablePoW 要求太高,沒人能發布。降低 require_pow。

無法連線或行為異常

用戶端收到 connection refused

原因:中繼未執行,或防火牆封鎖了連接埠。

修正:

sh
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。群組快照由 中繼自己的金鑰簽章,因此沒有它就不會產生任何東西。

修正:

sh
nostrfy --config nostrfy.toml genkey
nostrfy --config nostrfy.toml restart

restricted: 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 還原一位管理員:

json
{
  "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

常見原因:

  1. 未設定或錯誤設定 relay.public_url — AUTH 事件的 relay 標籤與 中繼的 URL 不符。設定 wss://... 並重新啟動。
  2. 挑戰過期 — 你在不同的連線上傳送了 AUTH,或重用了舊的挑戰。
  3. 用戶端時鐘偏差 — 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(預設值 就沒問題)。

檢查資料庫大小

sh
curl http://127.0.0.1:8080/relay/stats
# => "db_size_bytes" 以位元組為單位

備份 / 移動資料庫

所有資料都在 database.path 目錄中。 複製前先停止中繼(複製使用中的資料庫可能損壞它)。

sh
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)。

修正:

  1. 檢查日誌末尾:tail -50 nostrfy.log。
  2. 檢查機器是否重新啟動:uptime(非常短的運行時間意味著重新啟動)。
  3. 檢查記憶體:free -h。
  4. 再次啟動中繼:nostrfy start。
提示
要在開機時自動啟動 nostrfy,將它註冊為 systemd 服務,以中繼的啟動 指令作為 ExecStart。

systemd 無法在 80 連接埠啟動中繼

以 root 執行的 systemd 服務可以綁定 80 連接埠。如果你把 User= 設為一般使用者, 要嘛使用更高的連接埠(例如 8080),要嘛向單元加入 AmbientCapabilities=CAP_NET_BIND_SERVICE。

仍未解決?

  1. 檢查日誌:tail -100 nostrfy.log — 它通常會指出直接 原因。
  2. 重新驗證設定:nostrfy check — 顯示警告與錯誤。
  3. 收集重現細節:你在做什麼、哪個用戶端、確切的錯誤。
  4. 在專案倉庫提問: https://github.com/iqbqioza/nostrfy — 提交 issue 時,附上重現步驟與日誌。