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ónTipoBDNotificacionesAPIDificultad
cStateEstática (Hugo)NoNo (RSS)NoBaja
UpptimeEstática (GitHub)NoGitHubBaja
GatusDinámica (Go)OpcionalRESTMedia
CachetDinámica (PHP)MySQL/PgSQLSí (correo)RESTMedia
Statping-ngDinámica (Go)SQLite/PgSQLSí (múltiple)RESTMedia
KenerDinámica (Node)SQLiteRESTMedia

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.