Solución de problemas

Los errores más comunes — puertos, permisos, TLS, NIP faltantes, publicación y tiempos de espera — con soluciones paso a paso.

Tres cosas que comprobar primero:

  • nostrfy check valida tu configuración (la mayoría de los errores son errores de configuración).
  • tail -f nostrfy.log muestra el registro — la causa casi siempre está allí.
  • nostrfy restart reinicia el demonio de forma limpia.

No se puede iniciar

error: cannot bind to 0.0.0.0:80: Permission denied

Causa: Solo root puede usar el puerto 80.

Solución: Ejecuta con sudo, o cambia el puerto a uno como 8080.

sh
# Cambia port = 8080 en el archivo de configuración, luego:
nostrfy --config nostrfy.toml start

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

Causa: Otro proceso (un nostrfy antiguo u otro servidor) ya está usando el puerto.

Solución:

sh
ss -tlnp | grep :8080
sh
# Si nostrfy se está ejecutando, reinícialo
nostrfy --config nostrfy.toml restart

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

Causa: nostrfy ya se está ejecutando; start se niega a iniciar una segunda instancia.

Solución: Usa nostrfy restart, o simplemente usa la instancia en ejecución.

nostrfy stop se bloquea / did not stop in time

Causa: El demonio está bloqueado o no responde.

Solución:

sh
ps aux | grep nostrfy
kill -9 <PID>
# Elimina un archivo pid obsoleto si existe
rm -f nostrfy.pid

error: invalid nostrfy.toml: TOML parse error

Causa: El archivo de configuración no es TOML válido. Errores comunes: olvidar las comillas alrededor de una cadena, o escribir dos veces la misma clave.

Solución: El mensaje de error incluye un número de línea. Revisa y corrige esa línea.

toml
# Ejemplos correctos
name = "my relay"        # las cadenas van entre comillas con "
port = 8080              # los números van sin comillas
enabled_nips = [1, 50]   # las listas van entre [ ]

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

Causa: El archivo de configuración no existe.

Solución:

sh
nostrfy --config nostrfy.toml init

error: relay.private_key is not a valid secp256k1 secret key

Causa: relay.private_key no es una clave hexadecimal válida de 64 caracteres.

Solución: Ejecuta nostrfy genkey para generar una clave correcta (o establece private_key = "").

Muchas advertencias en el registro al iniciar

Las líneas de registro [WARN] indican problemas de configuración. Las principales:

AdvertenciaSignificado y solución
relay.public_url is empty and server.host is "0.0.0.0"...public_url no está configurado — la autenticación NIP-42, la eliminación NIP-62 y la autenticación de administrador NIP-98 no funcionarán. Configura wss://your-public-url.
relay.private_key is empty while NIP-29 is enabled...Los grupos necesitan una clave secreta. Ejecuta nostrfy genkey.
unknown config key [relay].software is ignoredUna clave heredada sin usar (o un error tipográfico) en la configuración. Revisa el nombre de la clave.
unknown config section [serve] is ignoredUn error tipográfico en un nombre de sección (p. ej. [serve] en lugar de [server]). Corríjalo.
relay.require_auth is true but relay.send_auth_challenge is false...Esta combinación bloquea a todos. Cambie uno de los dos.
relay.require_pow = 64 ... practically unmineableEl requisito de prueba de trabajo es tan alto que nadie puede publicar. Reduce require_pow.

No se puede conectar o comportamiento extraño

El cliente recibe connection refused

Causa: El relé no se está ejecutando, o un cortafuegos está bloqueando el puerto.

Solución:

sh
curl http://127.0.0.1:8080/health

# Desde fuera (usando la IP/puerto del servidor)
curl http://YOUR_SERVER_IP:8080/health

# Comprueba el cortafuegos (ejemplo: ufw)
sudo ufw status
# Abre el puerto si es necesario
sudo ufw allow 8080

Los clientes externos no pueden conectarse, los locales sí

Causa: server.host sigue siendo 127.0.0.1 (el valor predeterminado), que solo acepta conexiones locales.

Solución: Establece host = "0.0.0.0" en la configuración y reinicia.

No se puede conectar a través de un túnel de Cloudflare

Al usar Cloudflare Tunnel:

  • El relé funciona con HTTP sin cifrar; Cloudflare termina TLS, por lo que los clientes usan wss://. Configura public_url = "wss://..." en el relé (esto hace que funcione la autenticación NIP-42).
  • Cloudflare añade una cabecera X-Forwarded-Proto. nostrfy trata los valores ws/wss/http/https de la misma forma, por lo que normalmente no se necesita configuración adicional.

error: message too large y la conexión se cierra

Causa: Un solo mensaje supera max_ws_message_bytes (1 MB por defecto).

Solución: Aumenta limits.max_ws_message_bytes si necesitas eventos más grandes — pero comprueba también los límites propios del cliente.

Errores too many subscriptions / too many filters

Causa: Se alcanzaron los límites por conexión (suscripciones 20 por defecto, filtros 20 por defecto).

Solución: Aumenta limits.max_subscriptions / limits.max_filters (y revisa la configuración del cliente).

Las nuevas conexiones se rechazan bajo carga

Causa: Se alcanzó max_connections (10000 por defecto), se activó el límite por IP (max_connections_per_ip, 64 por defecto), o el límite de conexiones por segundo y por IP (max_connections_per_sec_per_ip) rechazó la ráfaga. Los límites se aplican a cada conexión — tanto WebSocket como HTTP sin cifrar.

Solución: Revisa y ajusta la configuración. max_connections_per_ip = 0 desactiva el límite por IP; max_connections_per_sec_per_ip = 0 desactiva el límite de frecuencia. Estos tres ajustes requieren un reinicio.

Las conexiones se cortan después de un tiempo

Causa: Si ws_idle_timeout_secs está configurado, las conexiones inactivas se cierran. Los clientes sanos responden al PING del relé con un PONG y permanecen conectados; solo se eliminan los pares muertos.

Solución: Esto es intencional — el valor predeterminado es 300 segundos. Establece ws_idle_timeout_secs = 0 para desactivarlo por completo.

Una suscripción termina con CLOSED ... response too large

Causa: Los eventos almacenados de un REQ superaron max_req_response_bytes (32 MiB por defecto). Solo ocurre con eventos muy grandes o filtros muy amplios.

Solución: Reduce el filtro (since/until más estrictos, un limit más bajo) o aumenta max_req_response_bytes (0 desactiva el límite).

Falta un NIP en la lista supported_nips de NIP-11

Causa: La lista anunciada es dinámica — un NIP se oculta cuando todos los kinds que define son rechazados: todos están en blocked_kinds, ninguno está en allowed_kinds, o son kinds efímeros rechazados por reject_ephemeral. NIP-29/43/66 además requieren relay.private_key y NIP-86 requiere rpc.management_token o rpc.admin_pubkey.

Solución: Revisa las listas de acceso activas — listallowedkinds de NIP-86 muestra la lista de kinds permitidos, y GET / muestra inmediatamente el supported_nips efectivo. Elimina el kind bloqueante o el ajuste reject_ephemeral.

Errores al publicar

Cuando falla la publicación, el cuarto elemento del mensaje OK explica por qué. Los más comunes:

ErrorSignificado y solución
invalid: signature verification failedLa firma del evento no es válida (posiblemente una clave de cliente defectuosa).
invalid: content too largeEl contenido supera max_content_bytes (64K caracteres por defecto). Acórtalo o aumenta el límite.
invalid: too many tagsMás etiquetas que max_tags (2000 por defecto).
invalid: event creation date is in the futureMarca de tiempo demasiado lejana en el futuro (más allá de max_created_at_future_secs).
mute: event contains secret key materialEl contenido o las etiquetas contienen una cadena con aspecto de nsec. Nunca publiques claves secretas. Elimina la cadena y el evento será aceptado.
duplicate: event already storedEl mismo evento ya está almacenado (normal).
blocked: pubkey not allowedLa clave pública está prohibida (banpubkey) o fuera de la lista de permitidos.
blocked: kind not allowedEste kind no está permitido.
rate-limited: too many eventsLa clave pública superó max_events_per_min_per_pubkey (ventana deslizante de 60 segundos). Espera un minuto y reintenta, o aumenta/desactiva el límite.
blocked: event has been bannedEl id del evento está prohibido.
blocked: event has been deletedRepublicación de un evento eliminado.
auth-required: ...Se requiere autenticación (cuando relay.require_auth está activado).
restricted: your account is too newLa cuenta se creó dentro de new_pubkey_min_age_secs. Espera y reintenta.
restricted: unknown groupEl grupo no existe (créalo primero).
restricted: this group is closedEl grupo está closed; las solicitudes de unión sin código de invitación no se aceptan.

Servidor de archivos Blossom

La subida falla con 401

El evento de autorización de subida (kind 24242) fue rechazado. Comprueba que:

  • el tag expiration del token está presente y establecido a una marca de tiempo unix en el futuro,
  • para upload/media/delete el token lleva un tag x con el sha256 del blob,
  • el tag server (cuando está presente) nombra exactamente el blossom.host configurado (solo nombre de host, sin esquema/ruta),
  • el token se firmó en los últimos 10 minutos (ventana de frescura contra repetición),
  • y la clave de firma es la propia del remitente.

La subida falla con 403

blossom.restrict_uploads = true está configurado y la clave pública no está en la lista de permitidos — añádela con nostrfy blossom allow npub1... (el demonio recarga automáticamente). Si la lista parece incorrecta, nostrfy blossom list la muestra.

La subida falla con 409

El cliente envió una cabecera X-SHA-256 que no coincide con el cuerpo real de la solicitud (el hash declarado se calculó sobre bytes diferentes — p. ej. el archivo cambió entre el cálculo y el envío). Los clientes pueden omitir la cabecera por completo.

GET / en el host de medios sirve el documento NIP-11

La solicitud no llegó al relé con la cabecera Blossom Host. Apunta media.example.com (o el valor configurado para blossom.host) al mismo puerto en el proxy inverso, luego nostrfy restart.

Un blob devuelve 404 justo después de la subida

El archivo se direcciona por contenido mediante su SHA-256: obténlo mediante el hash exacto devuelto en la respuesta de subida (/<sha256> o /<sha256>.<ext>). Una discordancia significa que el cliente solicitó un hash diferente de los bytes enviados.

Búsqueda, grupos y autenticación

La búsqueda devuelve 0 resultados / resultados inesperados

La búsqueda de nostrfy coincide con palabras completas. Ten en cuenta que:

  • search = "rust" coincide con eventos que contienen la palabra «rust», pero "ru" NO coincide con «rust» como subcadena.
  • Solo se buscan palabras en el contenido del evento.
  • Si search_index = false, la búsqueda sigue funcionando pero es más lenta.
  • Si NIP-50 está desactivado (disabled_nips = [50]), search se ignora (se envía un NOTICE).

Los metadatos de grupo (39000-39005) no se generan

Causa: relay.private_key no está configurado. Las instantáneas de grupo las firma la propia clave del relé, por lo que sin ella no se genera nada.

Solución:

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

restricted: unknown group rechaza eventos de grupo

Causa: El grupo no existe. En NIP-29, los eventos de moderación y las solicitudes de unión (9021) no pueden dirigirse a un grupo antes de que se cree (kind 9007).

Solución: Crea primero el grupo con un evento 9007.

restricted: you are not an admin of this group

Causa: La moderación (añadir miembros, etc.) requiere un administrador (un miembro con un rol). El creador es administrador.

Solución: Pide a un administrador que te otorgue un rol, o crea tu propio grupo.

restricted: this group is closed

Causa: El grupo está closed; las solicitudes de unión sin código de invitación no se aprueban automáticamente.

Solución: Pide a un administrador un código de invitación (9009) y únete con un tag code.

Salió accidentalmente de un grupo, o el grupo no tiene administradores

Causa: Las solicitudes de salida NIP-29 (kind 9022) se aceptan para cualquier miembro — incluido el último administrador del grupo, que no deja administradores. Sin administrador, nadie puede enviar eventos de moderación (9000/9001/9002/9008).

Solución: Firma un evento de moderación con la propia clave del relé (relay.private_key, la clave pública anunciada como self de NIP-11). Según NIP-29, los eventos de moderación pueden venir de «la clave maestra del relé o ... los administradores del grupo», por lo que el relé acepta moderación de grupo firmada por su propia clave incluso cuando el grupo no tiene administradores. Por ejemplo, restaura un administrador con un kind:9000:

json
{
  "kind": 9000,
  "pubkey": "<relay self pubkey>",
  "tags": [["h", "<group-id>"], ["p", "<member-hex>", "admin"]]
}

Fírmalo y publícalo con la clave del relé. Como alternativa, elimina el grupo con un kind:9008 firmado por el relé (sus eventos almacenados se purgan) y vuelve a crearlo con kind:9007. Esta recuperación necesita que relay.private_key esté configurado.

Los eventos protegidos se rechazan con auth-required

Causa: Los eventos protegidos NIP-70 (con un tag -) solo pueden ser publicados por el autor autenticado en la misma conexión.

Solución: Activa la autenticación NIP-42 en el cliente antes de publicar.

AUTH (NIP-42) devuelve false

Causas comunes:

  1. relay.public_url no está configurado o es incorrecto — el tag relay del evento AUTH no coincide con la URL del relé. Configura wss://... y reinicia.
  2. Desafío obsoleto — enviaste AUTH en otra conexión, o reutilizaste un desafío antiguo.
  3. El reloj del cliente está desajustado — el created_at del evento AUTH debe estar dentro de ±10 minutos de la hora actual.

La API de gestión NIP-86 devuelve 401 unauthorized

Causa: Credenciales faltantes o incorrectas.

Solución:

  • Configura management_token y envía Authorization: Bearer <token>.
  • O configura admin_pubkey y envía un evento de autenticación NIP-98 (el tag u debe coincidir exactamente con la URL del relé; se requiere un tag payload).
  • Si ninguno está configurado, la API de gestión está desactivada por completo.

Los eventos de autenticación NIP-98 se rechazan por un esquema o puerto diferente

La especificación NIP-98 dice que el tag u debe ser exactamente igual que la URL absoluta de la solicitud, por lo que nostrfy deriva la URL esperada de relay.public_url: su autoridad más el esquema HTTP asignado desde el esquema WebSocket (wss:// → https://, ws:// → http://, nostr+ eliminado). Sin public_url, el relé espera el http://host:port sin cifrar que sirve. Un tag con otro esquema, un puerto diferente/omitido, o una ruta o consulta diferentes se rechaza — configura relay.public_url con la dirección pública que firman los clientes. Cada evento de autenticación también es de un solo uso: repetir la misma cabecera Authorization dentro de su ventana de validez de 60 segundos se rechaza.

Base de datos y disco

database map is full: increase database.max_map_size

Causa: Se alcanzó el límite del mapa de memoria LMDB (1 TB de espacio de direcciones virtuales por defecto; el uso real de disco crece con los datos) — en la práctica, la base de datos está llena.

Solución: Aumenta database.max_map_size y reinicia.

disk is full: refusing to commit N events

Causa: Menos de 32 MB de espacio libre en disco. Las escrituras se detienen (para proteger los datos); las lecturas continúan.

Solución: Libera espacio en disco. Las escrituras se reanudan automáticamente cuando haya espacio disponible. (df -h /path/to/data)

nostrfy check informa map_size must not exceed max_map_size

Causa: database.map_size es mayor que max_map_size.

Solución: Establece map_size igual o por debajo de max_map_size (los valores predeterminados están bien).

Comprobar el tamaño de la base de datos

sh
curl http://127.0.0.1:8080/relay/stats
# => "db_size_bytes" en bytes

Respaldar / mover la base de datos

Todos los datos están en el directorio database.path. Detén el relé antes de copiar (copiar una base de datos activa puede corromperla).

sh
nostrfy --config nostrfy.toml stop
cp -a ./data ./data-backup
# Respalda también [blossom].local_path si usas almacenamiento local Blossom.
nostrfy --config nostrfy.toml start

Funcionamiento del demonio

nostrfy stats dice nostrfy is not running (no stats file)

Causa: El archivo de estadísticas no existe — el demonio no se está ejecutando, o se inició hace menos de unos segundos.

Solución: Ejecuta nostrfy start, espera unos segundos e inténtalo de nuevo.

El registro crece sin límite

Causa: max_log_size_bytes es 0 (rotación desactivada).

Solución: Establece max_log_size_bytes = 52428800 (50 MB) y max_log_files = 5. La rotación es automática.

Los cambios en la configuración no surten efecto tras la recarga

Causa: Recargaste (SIGHUP) ajustes que son fijos al inicio: private_key, api_host, metrics_enabled, los ajustes de LiveKit y las listas de activación/desactivación de NIP.

Solución: Usa nostrfy restart. El registro contiene una advertencia «a restart is required» en este caso.

El relé sigue deteniéndose solo

Causa: La máquina se reinició, o el relé se quedó sin memoria (OOM).

Solución:

  1. Revisa el final del registro: tail -50 nostrfy.log.
  2. Comprueba si la máquina se reinició: uptime (un tiempo de actividad muy corto significa un reinicio).
  3. Comprueba la memoria: free -h.
  4. Inicia el relé de nuevo: nostrfy start.
Consejo
Para iniciar nostrfy automáticamente al arrancar, regístralo como servicio systemd con el comando de inicio del relé como ExecStart.

systemd no puede iniciar el relé en el puerto 80

Un servicio systemd ejecutado como root puede usar el puerto 80. Si estableces User= a un usuario normal, usa un puerto más alto (p. ej. 8080) o añade AmbientCapabilities=CAP_NET_BIND_SERVICE a la unidad.

¿Aún no se ha resuelto?

  1. Revisa el registro: tail -100 nostrfy.log — normalmente indica la causa directa.
  2. Vuelve a validar la configuración: nostrfy check — muestra advertencias y errores.
  3. Recopila los detalles de reproducción: qué estabas haciendo, qué cliente, qué error exacto.
  4. Pregunta en el repositorio del proyecto: https://github.com/iqbqioza/nostrfy — al abrir un issue, incluye los pasos de reproducción y el registro.