Alternativa Self-Hosted a Instatus: Página de Estado Propia
Desplegar una página de estado autoalojada permite controlar completamente la comunicación de incidentes y el uptime de los servicios sin depender de plataformas de terceros como Instatus o Statuspage.io. Existen varias soluciones open source maduras para Linux que cubren desde páginas estáticas hasta sistemas dinámicos con API, notificaciones a suscriptores y ventanas de mantenimiento programado.
Requisitos Previos
- Ubuntu 20.04/22.04 o CentOS/Rocky Linux 8+
- Docker Engine 20.10+ y Docker Compose
- Nginx como proxy inverso con SSL (Let's Encrypt)
- Cuenta en GitHub (para soluciones basadas en GitHub Actions)
- Dominio configurado para la página de estado
- Acceso root o usuario con privilegios sudo
Comparativa de Soluciones
| Solución | Tipo | BD | Notificaciones | API | Dificultad |
|---|---|---|---|---|---|
| cState | Estática (Hugo) | No | No (RSS) | No | Baja |
| Upptime | Estática (GitHub) | No | Sí | GitHub | Baja |
| Gatus | Dinámica (Go) | Opcional | Sí | REST | Media |
| Cachet | Dinámica (PHP) | MySQL/PgSQL | Sí (correo) | REST | Media |
| Statping-ng | Dinámica (Go) | SQLite/PgSQL | Sí (múltiple) | REST | Media |
| Kener | Dinámica (Node) | SQLite | Sí | REST | Media |
Recomendación por caso de uso:
- Presupuesto cero, simple: cState o Upptime
- Monitorización activa integrada: Gatus o Statping-ng
- Gestión de incidentes completa: Cachet
- Alta personalización: Kener
Despliegue con Upptime
Upptime usa GitHub Actions para monitorizar URLs y genera una página de estado estática:
# 1. Crear repositorio desde la plantilla de Upptime
# Visitar: https://github.com/upptime/upptime (Use this template)
# 2. Configurar el archivo .upptimerc.yml en el repositorio
cat > .upptimerc.yml << 'EOF'
# Configuración de Upptime para página de estado
owner: TU-USUARIO-GITHUB
repo: mi-status-page
# Servicios a monitorizar
sites:
- name: Sitio Web Principal
url: https://mi-dominio.com
expectedStatusCodes:
- 200
- 301
- name: API REST
url: https://api.mi-dominio.com/health
expectedStatusCodes:
- 200
- name: Panel de Control
url: https://panel.mi-dominio.com
- name: Servidores de Correo
url: tcp://mail.mi-dominio.com:25
# Configuración de notificaciones
notifications:
- type: slack
channel: "#alertas-infraestructura"
- type: email
to: [email protected]
# Intervalo de comprobación (en minutos, mínimo 5)
# Las comprobaciones se ejecutan via GitHub Actions
# Dominio personalizado para la página de estado
statusWebsiteUrl: https://status.mi-dominio.com
# Idioma de la página
i18n:
header-desc: "Estado en tiempo real de nuestros servicios"
EOF
# 3. Configurar secretos en el repositorio GitHub:
# GH_PAT: Personal Access Token con permisos de escritura
# SLACK_WEBHOOK: URL del webhook de Slack (opcional)
Despliegue con Gatus
Gatus es un monitor de salud altamente configurable con interfaz web integrada:
# Crear directorio de configuración
mkdir -p /opt/gatus && cd /opt/gatus
# Crear configuración principal
cat > config.yaml << 'EOF'
# Configuración de Gatus - Monitor de servicios con página de estado
web:
port: 8080
# Configuración de la interfaz web
ui:
title: "Estado de Servicios | Mi Empresa"
description: "Monitorización en tiempo real de nuestra infraestructura"
logo: "https://mi-dominio.com/logo.png"
link: "https://mi-dominio.com"
buttons:
- name: "Portal de Clientes"
link: "https://panel.mi-dominio.com"
# Almacenamiento de datos históricos
storage:
type: sqlite
path: /data/gatus.db
# Configuración de alertas
alerting:
slack:
webhook-url: "https://hooks.slack.com/services/T.../B.../..."
default-alert:
description: "Alerta de estado de servicio"
send-on-resolved: true
failure-threshold: 2
success-threshold: 2
email:
from: "[email protected]"
username: "[email protected]"
password: "contraseña_smtp"
host: "smtp.mi-dominio.com"
port: 587
to: "[email protected]"
default-alert:
send-on-resolved: true
# Definición de endpoints a monitorizar
endpoints:
- name: Sitio Web Principal
group: "Servicios Web"
url: "https://mi-dominio.com"
interval: 1m
conditions:
- "[STATUS] == 200"
- "[RESPONSE_TIME] < 2000" # Menos de 2 segundos
alerts:
- type: slack
- type: email
- name: API REST - Health
group: "Servicios Web"
url: "https://api.mi-dominio.com/health"
interval: 30s
conditions:
- "[STATUS] == 200"
- "[BODY].status == up"
alerts:
- type: slack
- name: Base de Datos MySQL
group: "Infraestructura"
url: "tcp://db.mi-dominio.com:3306"
interval: 1m
conditions:
- "[CONNECTED] == true"
alerts:
- type: slack
- name: Certificado SSL
group: "Seguridad"
url: "https://mi-dominio.com"
interval: 1h
conditions:
- "[STATUS] == 200"
- "[CERTIFICATE_EXPIRATION] > 14d" # Avisar si expira en menos de 14 días
alerts:
- type: email
EOF
# Crear docker-compose.yml para Gatus
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
gatus:
image: twinproduction/gatus:latest
restart: unless-stopped
ports:
- "8080:8080"
volumes:
- ./config.yaml:/config/config.yaml:ro
- gatus_data:/data
environment:
GATUS_CONFIG_FILE: /config/config.yaml
volumes:
gatus_data:
EOF
# Iniciar Gatus
docker-compose up -d
docker-compose logs -f gatus
Integración con Monitorización Existente
Conectar la página de estado con el stack de monitorización:
# Integración con Prometheus y Alertmanager
# Cuando Alertmanager dispara una alerta, actualizar la página de estado
# Script de integración Alertmanager -> Cachet/Statping
cat > /usr/local/bin/alert-to-status.sh << 'EOF'
#!/bin/bash
# Recibir alertas de Alertmanager y actualizar página de estado
# Leer datos del webhook de Alertmanager (JSON en stdin)
ALERT_DATA=$(cat)
ALERT_NAME=$(echo "$ALERT_DATA" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['alerts'][0]['labels']['alertname'])")
ALERT_STATUS=$(echo "$ALERT_DATA" | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['status'])")
# Mapeo de alertas a componentes de la página de estado
STATPING_URL="http://localhost:8080/api"
TOKEN="TU_TOKEN_STATPING"
case "$ALERT_NAME" in
"WebsiteDown")
SERVICE_ID=1
;;
"DatabaseDown")
SERVICE_ID=2
;;
"APIDown")
SERVICE_ID=3
;;
*)
SERVICE_ID=0
;;
esac
if [ "$ALERT_STATUS" = "firing" ] && [ "$SERVICE_ID" -gt 0 ]; then
# Registrar fallo en Statping
curl -s -X POST "$STATPING_URL/services/${SERVICE_ID}/failures" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"issue\": \"$ALERT_NAME detectado por Alertmanager\"}"
fi
echo "Alerta procesada: $ALERT_NAME ($ALERT_STATUS)"
EOF
chmod +x /usr/local/bin/alert-to-status.sh
Notificaciones a Suscriptores
Sistema de notificaciones para mantener informados a los usuarios:
# Con Cachet: gestión de suscriptores via API
# Los usuarios se suscriben desde la página de estado
# Verificar suscriptores activos
curl -s "https://status.mi-dominio.com/api/v1/subscribers" \
-H "X-Cachet-Token: TU_TOKEN" | python3 -m json.tool
# Con Statping-ng: configurar notificador de correo
# La configuración se realiza desde el panel de administración
# Configuración > Notifiers > Email
# Implementar lista de correo propia con listmonk
# Para casos donde se necesita mayor control de los suscriptores
docker run -d \
--name listmonk \
-p 9000:9000 \
-e LISTMONK_app__address="0.0.0.0:9000" \
-e LISTMONK_db__host="db" \
-e LISTMONK_db__user="listmonk" \
-e LISTMONK_db__password="password" \
listmonk/listmonk:latest
Actualizaciones via API
Automatizar las actualizaciones del estado con scripts:
# Script de actualización automática de estado
cat > /usr/local/bin/update-status-page.sh << 'EOF'
#!/bin/bash
# Actualización automática de la página de estado basada en checks del sistema
# Configuración
CACHET_URL="https://status.mi-dominio.com/api/v1"
CACHET_TOKEN="TU_TOKEN_CACHET"
# Función para actualizar componente
actualizar_componente() {
local COMPONENTE_ID=$1
local NUEVO_ESTADO=$2
# Estados: 1=Operacional, 2=Degradado, 3=Parcial, 4=Mayor
curl -s -X PUT "$CACHET_URL/components/$COMPONENTE_ID" \
-H "X-Cachet-Token: $CACHET_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"status\": $NUEVO_ESTADO}" > /dev/null
}
# Comprobar sitio web (componente ID: 1)
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 https://mi-dominio.com)
if [ "$HTTP_CODE" = "200" ]; then
actualizar_componente 1 1 # Operacional
elif [ "$HTTP_CODE" = "000" ]; then
actualizar_componente 1 4 # Interrupción mayor
else
actualizar_componente 1 2 # Degradado
fi
# Comprobar API (componente ID: 2)
API_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 https://api.mi-dominio.com/health)
if [ "$API_RESPONSE" = "200" ]; then
actualizar_componente 2 1
else
actualizar_componente 2 4
fi
# Comprobar tiempo de respuesta (componente ID: 3)
RESPONSE_TIME=$(curl -s -o /dev/null -w "%{time_total}" --max-time 10 https://mi-dominio.com)
RESPONSE_MS=$(echo "$RESPONSE_TIME * 1000" | bc | cut -d. -f1)
if [ "$RESPONSE_MS" -gt 3000 ]; then
actualizar_componente 3 2 # Degradado por alta latencia
else
actualizar_componente 3 1
fi
EOF
chmod +x /usr/local/bin/update-status-page.sh
# Ejecutar cada 5 minutos
echo "*/5 * * * * root /usr/local/bin/update-status-page.sh" \
| sudo tee /etc/cron.d/status-page-updates
Ventanas de Mantenimiento
Programar mantenimientos con aviso anticipado:
# Crear ventana de mantenimiento en Cachet
curl -X POST "https://status.mi-dominio.com/api/v1/schedules" \
-H "X-Cachet-Token: TU_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Actualización del sistema operativo",
"message": "Actualizaremos el sistema operativo de los servidores principales. Se espera una interrupción de servicio de entre 15 y 30 minutos.",
"status": 1,
"scheduled_at": "2024-04-01 02:00:00",
"completed_at": "2024-04-01 04:00:00"
}'
# En Gatus: configurar períodos de mantenimiento para evitar alertas falsas
# En config.yaml añadir a cada endpoint:
# maintenance:
# start: "02:00"
# duration: 2h
# day: [sunday]
# Script para crear mantenimiento programado automáticamente
cat > /usr/local/bin/schedule-maintenance.sh << 'EOF'
#!/bin/bash
# Programar ventana de mantenimiento en la página de estado
NOMBRE="$1"
INICIO="$2" # Formato: "2024-04-01 02:00:00"
FIN="$3" # Formato: "2024-04-01 04:00:00"
MENSAJE="$4"
curl -X POST "https://status.mi-dominio.com/api/v1/schedules" \
-H "X-Cachet-Token: $CACHET_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"$NOMBRE\",
\"message\": \"$MENSAJE\",
\"status\": 1,
\"scheduled_at\": \"$INICIO\",
\"completed_at\": \"$FIN\"
}"
echo "Mantenimiento programado: $NOMBRE ($INICIO - $FIN)"
EOF
chmod +x /usr/local/bin/schedule-maintenance.sh
Solución de Problemas
La página de estado no refleja el estado real:
# Verificar que el script de actualización se ejecuta
sudo grep "update-status-page" /var/log/syslog | tail -10
# Ejecutar manualmente y ver la salida
sudo bash -x /usr/local/bin/update-status-page.sh
# Verificar conectividad a la API de Cachet
curl -v "https://status.mi-dominio.com/api/v1/ping" \
-H "X-Cachet-Token: TU_TOKEN"
Gatus no arranca con la configuración:
# Validar la sintaxis del archivo de configuración
docker run --rm -v $(pwd)/config.yaml:/config/config.yaml \
twinproduction/gatus:latest --config /config/config.yaml validate
# Ver logs detallados
docker-compose logs gatus
Los correos de notificación llegan a spam:
# Configurar SPF, DKIM y DMARC para el dominio del remitente
# Verificar: mail-tester.com
# Usar proveedores de email transaccional (SendGrid, Mailgun, Postmark)
# para mejorar la tasa de entrega
Conclusión
La elección de una página de estado autoalojada depende principalmente del nivel de funcionalidad requerido y los recursos disponibles: Upptime y cState son perfectos para empezar sin costes operativos, mientras que Gatus ofrece monitorización activa integrada con una interfaz pulida y Cachet proporciona la gestión de incidentes más completa. Cualquiera de estas soluciones, configurada con actualizaciones automáticas via API y notificaciones a suscriptores, supera en valor a los servicios SaaS de pago para la mayoría de casos de uso de hosting y VPS.


