ntfy es un servidor de notificaciones push basado en HTTP: para enviar un aviso basta con hacer una petición POST a un topic, y cualquier móvil, navegador o script suscrito a ese topic lo recibe al momento. En este tutorial instalarás ntfy en Ubuntu 24.04 desde su repositorio oficial, lo publicarás detrás de Nginx con un certificado de Let's Encrypt, cerrarás el acceso con usuarios y tokens, y configurarás systemd para que te avise cuando un servicio falle.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, y un usuario no root con privilegios
sudo. ntfy consume muy pocos recursos: 512 MB de RAM son suficientes. - Un subdominio (en esta guía,
ntfy.your_domain) con un registro DNS A que apunte a la IP del servidor. - Nginx instalado y los puertos 80 y 443 abiertos. Si usas UFW:
sudo ufw allow 'Nginx Full'. - La aplicación de ntfy en tu móvil (Android o iOS) para probar la recepción, opcional.
Paso 1: Instalar ntfy desde el repositorio oficial
El autor de ntfy mantiene un repositorio APT con paquetes para amd64, arm64 y armhf. Descarga su clave en /etc/apt/keyrings:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://archive.heckel.io/apt/pubkey.txt | sudo gpg --dearmor -o /etc/apt/keyrings/archive.heckel.io.gpg
Añade el repositorio para la arquitectura de tu servidor e instala el paquete:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/archive.heckel.io.gpg] https://archive.heckel.io/apt debian main" | sudo tee /etc/apt/sources.list.d/archive.heckel.io.list
sudo apt update
sudo apt install ntfy
Comprueba la versión instalada:
dpkg -s ntfy | grep '^Version'
El paquete crea el usuario de sistema ntfy, la unidad ntfy.service y un archivo de configuración de ejemplo en /etc/ntfy/server.yml con todas las opciones comentadas.
Paso 2: Configurar el servidor
Por defecto ntfy escucha en el puerto 80, que en este servidor usará Nginx. Lo configurarás para que escuche solo en 127.0.0.1:2586, guarde los mensajes en disco para que los clientes que se reconectan no los pierdan y deniegue el acceso a cualquier usuario no autenticado.
Crea los directorios de datos con el propietario correcto:
sudo install -d -o ntfy -g ntfy /var/lib/ntfy /var/cache/ntfy
Guarda una copia del archivo de ejemplo y edita la configuración:
sudo cp /etc/ntfy/server.yml /etc/ntfy/server.yml.orig
sudo nano /etc/ntfy/server.yml
Sustituye el contenido por lo siguiente, cambiando ntfy.your_domain:
base-url: "https://ntfy.your_domain"
listen-http: "127.0.0.1:2586"
behind-proxy: true
cache-file: "/var/cache/ntfy/cache.db"
cache-duration: "12h"
auth-file: "/var/lib/ntfy/user.db"
auth-default-access: "deny-all"
attachment-cache-dir: "/var/cache/ntfy/attachments"
attachment-file-size-limit: "15M"
upstream-base-url: "https://ntfy.sh"
Qué hace cada bloque:
base-urles la URL pública del servidor y es obligatoria para los adjuntos y para la aplicación web.behind-proxy: truehace que ntfy lea la IP del cliente de la cabeceraX-Forwarded-Forque envía Nginx, necesario para que los límites por visitante funcionen.cache-fileycache-durationguardan los mensajes 12 horas; también son necesarios para programar mensajes.auth-default-access: "deny-all"obliga a autenticarse para leer o publicar en cualquier topic.upstream-base-urlreenvía un aviso sin contenido antfy.shpara que la aplicación de iOS reciba notificaciones instantáneas de un servidor propio. El texto del mensaje nunca sale de tu servidor. Si no usas iOS, puedes quitar esta línea.
Habilita y arranca el servicio:
sudo systemctl enable --now ntfy
Verifica que está activo y responde en local:
sudo systemctl status ntfy --no-pager
curl -s http://127.0.0.1:2586/v1/health
{"healthy":true}
Si el servicio no arranca, sudo journalctl -u ntfy -n 50 --no-pager muestra el error, normalmente una clave mal escrita en server.yml.
Paso 3: Publicar ntfy con Nginx y HTTPS
ntfy mantiene conexiones abiertas (HTTP streaming y WebSocket) con los clientes suscritos, así que Nginx no debe almacenar las respuestas en búfer y debe permitir conexiones largas. Crea el sitio:
sudo nano /etc/nginx/sites-available/ntfy
server {
listen 80;
listen [::]:80;
server_name ntfy.your_domain;
location / {
proxy_pass http://127.0.0.1:2586;
proxy_http_version 1.1;
proxy_buffering off;
proxy_request_buffering off;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 3m;
proxy_send_timeout 3m;
proxy_read_timeout 3m;
client_max_body_size 20M;
}
}
Activa el sitio, valida la configuración y recarga Nginx:
sudo ln -s /etc/nginx/sites-available/ntfy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Obtén el certificado con Certbot. El plugin de Nginx añade la configuración TLS y la redirección de HTTP a HTTPS en el mismo archivo:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d ntfy.your_domain
Comprueba el acceso desde fuera:
curl -s https://ntfy.your_domain/v1/health
{"healthy":true}
Paso 4: Crear usuarios, permisos y tokens
Con deny-all, nadie puede usar el servidor hasta que crees usuarios. Los comandos ntfy user, ntfy access y ntfy token leen la ruta de la base de datos de /etc/ntfy/server.yml, por eso se ejecutan con sudo en el propio servidor.
Crea un administrador, que tiene acceso completo a todos los topics. Te pedirá la contraseña:
sudo ntfy user add --role=admin your_user
Crea un usuario sin privilegios para los scripts y dale permiso solo sobre el topic alertas. Los niveles son read-write (rw), read-only (ro), write-only (wo) y deny:
sudo ntfy user add monitor
sudo ntfy access monitor alertas wo
Para los scripts es mejor un token que una contraseña: se puede revocar sin cambiar la contraseña del usuario. Genera uno para monitor:
sudo ntfy token add monitor
token tk_AgQdq7mVBoFD37zQVN29RhuMzNIz2 created for user monitor, never expires
Revisa los permisos resultantes:
sudo ntfy access
La salida debe mostrar a your_user con acceso de lectura y escritura a todos los topics por su rol de administrador, a monitor con acceso de solo escritura a alertas y al usuario anónimo (*) sin acceso a ningún topic.
Paso 5: Enviar notificaciones
Suscríbete primero al topic para ver los mensajes. En la aplicación móvil, añade el servidor https://ntfy.your_domain con tu usuario administrador en los ajustes de usuarios y suscríbete al topic alertas. También puedes abrir https://ntfy.your_domain/alertas en el navegador e iniciar sesión.
Envía un mensaje con el token de monitor (sustituye your_token). El cuerpo de la petición es el texto de la notificación:
curl -H "Authorization: Bearer your_token" -d "Copia de seguridad terminada" https://ntfy.your_domain/alertas
ntfy responde con el mensaje en JSON y la notificación aparece en los clientes suscritos:
{"id":"hN3kd9Zq2cXy","time":1790331120,"expires":1790374320,"event":"message","topic":"alertas","message":"Copia de seguridad terminada"}
Las cabeceras permiten añadir título, prioridad (de 1, mínima, a 5, máxima), etiquetas (que se muestran como emojis si coinciden con un nombre de emoji) y un enlace que se abre al pulsar la notificación:
curl \
-H "Authorization: Bearer your_token" \
-H "Title: Disco casi lleno en web01" \
-H "Priority: 4" \
-H "Tags: warning,floppy_disk" \
-H "Click: https://grafana.your_domain" \
-d "La particion / esta al 91%" \
https://ntfy.your_domain/alertas
Notalos valores de las cabeceras HTTP deben ser ASCII. Si necesitas tildes o eñes en el título, envía el mensaje como JSON a la raíz del servidor o usa el parámetro
?title=codificado en la URL.
Para programar un mensaje, usa la cabecera In con un intervalo:
curl -H "Authorization: Bearer your_token" -H "In: 30min" -d "Revisar la migracion" https://ntfy.your_domain/alertas
Comprueba que un usuario sin permisos no puede publicar. Una petición sin credenciales debe recibir un 403:
curl -s -o /dev/null -w "%{http_code}\n" -d "prueba" https://ntfy.your_domain/alertas
403
Paso 6: Recibir un aviso cuando falle un servicio
systemd puede lanzar otra unidad cuando una falla, mediante la directiva OnFailure=. Usarás una unidad plantilla que llama a un pequeño script con el nombre del servicio caído.
Guarda la URL y el token en un archivo que solo pueda leer root:
sudo nano /etc/ntfy-alert.env
NTFY_URL=https://ntfy.your_domain/alertas
NTFY_TOKEN=your_token
sudo chmod 600 /etc/ntfy-alert.env
Crea el script que envía el aviso con las últimas líneas del estado del servicio:
sudo nano /usr/local/bin/ntfy-alert-failure
#!/usr/bin/env bash
set -euo pipefail
unit="$1"
status="$(systemctl status --no-pager --lines=10 "$unit" 2>&1 || true)"
curl -fsS -m 10 --retry 3 -o /dev/null \
-H "Authorization: Bearer ${NTFY_TOKEN}" \
-H "Title: Fallo de ${unit} en $(hostname)" \
-H "Priority: 5" \
-H "Tags: rotating_light" \
-d "$status" \
"$NTFY_URL"
sudo chmod 755 /usr/local/bin/ntfy-alert-failure
Crea la unidad plantilla. %i es el nombre que va detrás de la @:
sudo nano /etc/systemd/system/[email protected]
[Unit]
Description=Aviso ntfy por fallo de %i
[Service]
Type=oneshot
EnvironmentFile=/etc/ntfy-alert.env
ExecStart=/usr/local/bin/ntfy-alert-failure %i
Recarga systemd y prueba la unidad a mano. Debe llegarte una notificación con el título Fallo de prueba.service:
sudo systemctl daemon-reload
sudo systemctl start [email protected]
Ahora engánchala a los servicios que quieras vigilar con un archivo de override. Por ejemplo, para Nginx:
sudo systemctl edit nginx
Añade estas líneas en la zona editable del archivo:
[Unit]
OnFailure=ntfy-alert@%n.service
En el override, %n se sustituye por el nombre completo de la unidad (nginx.service). Comprueba que la directiva se ha aplicado:
systemctl show nginx -p OnFailure
La doble extensión es esperable: %n ya incluye .service y el script recibe nginx.service, que es lo que systemctl status necesita.
Solución de problemas
La aplicación muestra "unauthorized" al suscribirse. El usuario configurado en la aplicación no tiene permiso de lectura sobre ese topic. Revisa sudo ntfy access y concede ro o rw al usuario con el que se conecta el móvil.
Las notificaciones llegan con retraso o solo al abrir la aplicación. En Android, desactiva la optimización de batería para ntfy. En iOS, comprueba que upstream-base-url está configurado y que el servidor puede conectar por HTTPS con ntfy.sh.
Nginx devuelve 502. ntfy no está escuchando en 127.0.0.1:2586. Comprueba sudo systemctl status ntfy y que listen-http coincide con el proxy_pass de Nginx.
Conclusión
Tienes un servidor ntfy propio detrás de Nginx con HTTPS, con los topics cerrados por defecto, un token de solo escritura para tus scripts y avisos automáticos cuando falla un servicio de systemd. Como siguientes pasos, puedes usar el mismo token en las alertas de Grafana o Uptime Kuma (ambos tienen integración con ntfy), crear un topic por entorno o por equipo con permisos distintos y añadir /var/lib/ntfy/user.db a tus copias de seguridad.
