FAQ

Esta es la guía de instalación, configuración y mantenimiento de Arcivéo Security Monitor. Las secciones están agrupadas: visión general, despliegue del panel, conexión de herramientas de seguridad, módulos integrados y diagnóstico. Los comandos se pueden copiar con el botón de la derecha.

Por dónde empezar

01. Instalación del panel: elija un método

La instalación del panel está en páginas paso a paso independientes. Elija un método:

Si no está seguro, elija la automática. Esta guía sigue siendo la fuente única sobre SSL, herramientas, cron y diagnóstico: las páginas de instalación remiten a sus secciones sin duplicar nada.

Resumen

02. Qué es Arcivéo Security Monitor

Arcivéo Security Monitor es un panel de seguridad del servidor. Recopila datos de las herramientas instaladas (Fail2ban, UFW, Lynis, ModSecurity, AIDE, ClamAV, Auditd, CrowdSec, Suricata, Falco, etc.) y los muestra en una interfaz unificada con panel de control, mapa de ataques y páginas detalladas de cada herramienta.

El monitor no es un medio de protección activo: no bloquea ataques por sí mismo. Su función es agregar la información de las herramientas ya en funcionamiento y presentarla de forma cómoda.

03. Cómo funciona el monitor en el servidor

El monitor funciona solo de forma local: debe instalarse en el mismo servidor que supervisa. No usa SSH ni API remota.

Todos los comandos (fail2ban-client, ufw status, ipset list, etc.) los ejecuta el panel con el usuario del servidor web (normalmente www-data; en paneles de hosting, la cuenta del sitio) con un conjunto reducido de permisos sudo: solo sobre utilidades concretas, sin acceso root general. Los resultados se analizan y se muestran en el navegador.

Dos ritmos de trabajo. Las páginas del panel consultan las herramientas en el momento de abrirlas: usted siempre ve el estado actual. Además, hay tareas en segundo plano que se ejecutan según un horario (cron):

  • cada 5 minutos — una instantánea de la carga para la página «Rendimiento»;
  • cada 5 minutos — el sensor de eventos de seguridad: nuevos baneos, alertas de IDS, una protección detenida, un puerto abierto (sección «Eventos de seguridad»);
  • una vez al día — el informe de seguridad a Telegram y al correo, además de las comprobaciones pesadas (Lynis, AIDE, ClamAV, debsums, Logwatch);
  • cada 15 minutos — el resumen del servidor al área personal, si está activado.

La lista completa de tareas está en la sección «Todas las tareas de cron en un solo lugar».

Para varios servidores, instale el monitor en cada uno por separado, con un dominio propio. Para verlos en una sola lista, active el resumen de servidores (sección «Resumen de servidores en el área personal»).

04. Cómo se calcula la Puntuación de seguridad

La puntuación parte del máximo y baja por cada problema detectado:

  • UFW inactivo — −30
  • Fail2ban no iniciado (sin jail activas) — −25
  • Sin claves WebAuthn — −15
  • Lynis hardening index < 60 — −20; 60–79 — −10
  • IPset ipsum no cargado — −10
  • Amenazas detectadas por ClamAV — −20
  • Cambios de archivos en AIDE — −15
  • SSL caducado — −30, caduca en <14 días — −15, <30 días — −5
  • CrowdSec instalado, pero no iniciado — −5
  • Suricata instalada, pero no iniciada — −5
  • SGBD/caché (MySQL, PostgreSQL, Redis…) accesibles desde el exterior — −10
  • Acceso root por SSH permitido (PermitRootLogin yes) — −20
  • Acceso con contraseña por SSH permitido (PasswordAuthentication yes) — −10
  • Ajustes menores de SSH: X11Forwarding activado, MaxAuthTries mayor que 3 o sshd_config legible por todas las cuentas — −5
  • Endurecimiento del servidor web: las respuestas revelan la versión del servidor web o de PHP, el método TRACE está activado o falta la cabecera Strict-Transport-Security o X-Frame-Options — −5
  • Actualizaciones de seguridad pendientes de instalar — −5

Resultado: 80+ = Protegido, 60–79 = Atención, <60 = En riesgo.

Las deducciones por ClamAV, AIDE, CrowdSec y Suricata solo se aplican si la herramienta está instalada. Lynis y AIDE sin la base inicializada se muestran como «sin datos» y no reducen la puntuación. El número de ataques de hoy se muestra en el panel, pero no afecta a la Puntuación de seguridad. El endurecimiento del servidor web no se cuenta si ningún sitio respondió a la comprobación.

Ajustes y licencia

05. WebAuthn — autenticación de doble factor

WebAuthn es un estándar de autenticación sin contraseña mediante una clave física. Es compatible con YubiKey, Touch ID, Face ID, Windows Hello y Passkey.

Tras iniciar sesión con la contraseña, el sistema solicita confirmación mediante la clave registrada. Aunque la contraseña se filtre, sin la clave física o la biometría es imposible acceder.

Para configurarlo, abra Claves WebAuthn en el menú lateral y pulse «Registrar clave». Registre dos claves a la vez: si la única clave se pierde o se estropea, no será posible acceder al panel con ella.

WebAuthn solo funciona a través de HTTPS. En una conexión HTTP no están disponibles el registro ni el inicio de sesión con clave.

06. Notificaciones: Telegram, Email, webhook y push

Hay cuatro canales: Telegram, Email, webhook y push en el navegador. Son independientes: active los que quiera, incluso los cuatro. Por esos mismos canales se envían el informe de seguridad diario, las alertas de carga y los hallazgos del sensor de eventos; se configuran una sola vez aquí, en «Ajustes».

Telegram. Necesita el token del bot y el chat id:

  1. En Telegram escriba a @BotFather → /newbot → obtendrá un token con el formato 123456:ABC....
  2. Escriba cualquier mensaje a su nuevo bot (para que pueda responderle).
  3. Averigüe su chat id: escriba al bot @userinfobot, o abra https://api.telegram.org/bot<TOKEN>/getUpdates y busque "chat":{"id":...}.
  4. Introduzca el token y el chat id en «Ajustes» → Telegram y pulse «Guardar y enviar prueba».

Email. Dos métodos a elegir en «Ajustes» → Email:

  • SMTP — host, puerto (465/SSL o 587/TLS), usuario y contraseña de su buzón de correo;
  • Resend — API moderna: indique la clave API (re_...) y un dominio de remitente verificado.

Webhook. Una sola dirección que acepte POST cubre Slack, Discord, Mattermost, n8n y su propio manejador. «Ajustes» → Webhook: pegue la dirección y active el interruptor.

  • a Slack y Mattermost se envía {"text": …}, a Discord {"content": …} (Discord rechaza los mensajes de más de 2000 caracteres, por lo que el texto se recorta);
  • cualquier otra dirección recibe JSON estructurado: text, event, severity, source, count, host, ts, sin necesidad de volver a analizar el texto del mensaje.

Notificaciones push. El mensaje llega directamente al navegador y, si el panel está añadido a la pantalla de inicio del teléfono, como una notificación de aplicación normal, sin bot ni servidor de correo de por medio. «Ajustes» → Notificaciones push: active el interruptor y pulse «Conectar este dispositivo» en cada dispositivo que deba recibir notificaciones. El contador «Dispositivos conectados» indica cuántos hay.

El push solo funciona por HTTPS (igual que WebAuthn) y el permiso hace falta dos veces: primero en el navegador —la solicitud aparece al pulsar «Conectar este dispositivo»— y después en el propio sistema operativo. Si en Windows está desactivado el interruptor general Configuración → Sistema → Notificaciones, el navegador concede el permiso, el panel informa del envío y en la pantalla no aparece nada. Lo mismo ocurre con el modo «No molestar» y con las notificaciones del navegador desactivadas en macOS (Ajustes del Sistema → Notificaciones).
Cada canal tiene su propio botón «Enviar prueba», que lo comprueba de inmediato. El horario del informe automático se gestiona con cron (sección «Todas las tareas de cron»): cron lanza el envío y los canales se toman de los ajustes.

Estado del informe: «ATENCIÓN» u «OK». El título pasa a «ATENCIÓN» solo ante un problema real o una acción pendiente: se detecta una amenaza de ClamAV, cambios de archivos en AIDE, eventos críticos de Falco (Emergency/Alert/Critical en las últimas 24 h), un servicio caído en Monit, se requiere reinicio, caduca el SSL (≤14 días) o hay actualizaciones de seguridad pendientes. El ruido de fondo —intentos de fuerza bruta SSH de bots, IP baneadas por fail2ban, alertas de Suricata, advertencias de Lynis y peticiones ya bloqueadas por ModSecurity— no eleva el estado, por lo que esas cifras en el informe no significan «ATENCIÓN» por sí solas.

07. Licencia — introducción y activación

Los módulos de monitorización detallados (Lynis, UFW, ModSecurity, mapa de ataques, AIDE, ClamAV, etc.) se activan con una licencia vigente. Sin ella, el panel, los ajustes y la cuenta funcionan, pero los módulos muestran la tarjeta «Se requiere licencia».

Tras la compra en el área personal, dispone de un código de activación del tipo ARCIVEO-XXXX-XXXX-XXXX-XXXX. Debe «activarlo» para el dominio de su panel: esto convierte el código en un archivo de licencia firmado (bloque [license]) que usted pega en el panel.

Cómo activar (3 pasos):

  1. Obtenga el código de activación. Área personal my.arciveo.com → sección «Licencias» / «Activación de licencia»: copie el código ARCIVEO-….
  2. Active el código para su dominio. En la misma área personal, abra «Activación de licencia» e introduzca: el código de activación, su correo electrónico y el dominio del panel (la dirección por la que se abre el monitor, p. ej. monitor.example.com). Pulse activar: el sistema generará un archivo de licencia vinculado a ese dominio y lo mostrará en un campo con el botón «Copiar».
  3. Pegue la clave en el panel. Copie todo el texto de la licencia → en el panel abra «Ajustes» → bloque «Licencia», péguela y pulse «Guardar». Los módulos se desbloquean de inmediato.

El panel verifica la clave criptográficamente: la firma, la vinculación al dominio y la vigencia.

El dominio de la activación debe coincidir exactamente con la dirección del panel. Tómelo de la constante APP_URL en config.php e introduzca solo el nombre del host, sin https:// ni el prefijo www. La activación es de un solo uso: el código se convierte en una licencia para el dominio introducido y no se puede reactivar; si se equivoca en el dominio, la clave no servirá para su panel y el código quedará gastado. Por eso, introduzca el dominio con atención.
Si la vigencia expira o cambia el dominio, aparecerá una advertencia en la cabecera del panel. La licencia queda vinculada al dominio de forma permanente y no se transfiere a otro dominio: para un nuevo periodo o un nuevo dominio se necesita una clave nueva (se compra en el área personal y se activa una sola vez).

08. Archivo config.php — todos los ajustes del panel

Todos los parámetros principales del panel se definen en un único archivo config.php en la raíz (junto a la carpeta public/) mediante constantes define() normales. El archivo se crea durante la instalación; rara vez hay que editarlo a mano, sobre todo al cambiar de dominio, migrar o conectar a otra base de datos. Tras cualquier cambio, reinicie PHP-FPM (de lo contrario, debido a OPcache, los cambios no se aplicarán).

Introduzca sus valores en los lugares resaltados; deje el resto tal cual:

// --- Base de datos --- define('DB_HOST', 'localhost'); // dejar define('DB_NAME', 'db_name'); // lo que definió al crear la BD define('DB_USER', 'user'); // lo que definió al crear la BD define('DB_PASS', 'db_password'); // lo que definió al crear la BD define('DB_CHARSET', 'utf8mb4'); // dejar // --- Aplicación --- define('APP_URL', 'https://monitor.example.com'); // dirección del panel, sin barra al final define('TIMEZONE', 'Europe/Madrid'); // su zona horaria // --- Tiempo de sesión --- define('SESSION_LIFETIME', 28800); // inactividad hasta un nuevo inicio de sesión, seg (28800 = 8 h)

Base de datos. Credenciales de conexión a MySQL/MariaDB:

  • DB_HOST — host del SGBD, casi siempre localhost;
  • DB_NAME — nombre de la base de datos del panel;
  • DB_USER — usuario de la BD (acceso solo a su propia base);
  • DB_PASS — contraseña de este usuario;
  • DB_CHARSET — codificación de la conexión, deje utf8mb4.

Aplicación.

  • APP_URL — dirección completa del panel (p. ej. https://monitor.example.com). Debe coincidir con el dominio en el que se activó la licencia; de lo contrario, la clave será rechazada (véase la sección «Licencia»);
  • TIMEZONE — zona horaria de PHP: afecta solo a cómo el panel muestra las fechas y las horas. No afecta a la hora de ejecución de las tareas cron; allí rige la zona horaria del sistema (véase «Todas las tareas cron»).

Tiempo de sesión. SESSION_LIFETIME — tiempo de espera de inactividad de la sesión en segundos (deslizante: se renueva con la actividad). Por defecto 28800 = 8 horas; tras ese tiempo de inactividad el panel pedirá iniciar sesión de nuevo. Por ejemplo, 3600 = 1 hora, 86400 = un día.

Registro de errores. Los errores nunca se muestran a los visitantes, sino que se escriben en logs/php_errors.log — se ven en la página «Registros de la aplicación». Estas líneas (display_errors=0, log_errors=1, la ruta error_log) normalmente no hay que cambiarlas: los ajustes están definidos en el propio archivo y no dependen de php.ini.

config.php es un archivo secreto. Contiene la contraseña de la BD. Se encuentra en la raíz del panel (junto a public/), y este panel tiene como raíz web (DocumentRoot) precisamente la raíz del panel, no public/. El archivo en sí no se «filtra»: en el .htaccess de la raíz hay una prohibición explícita para él (Require all denied) — el servidor devuelve 403. Incluso sin esa regla el código fuente no se filtraría: es PHP — el servidor lo ejecuta, no lo entrega como texto. Por si acaso: no lo publique en repositorios públicos ni lo envíe al soporte con la contraseña real. Los permisos del archivo son 640.
Al migrar o restaurar el acceso, este archivo es la principal fuente de credenciales: el nombre de la BD, el usuario y la contraseña se toman precisamente de aquí (véanse las secciones «Actualización y migración del panel» y «Restauración del acceso»).

09. API de solo lectura (JSON y Prometheus)

El panel está protegido por el inicio de sesión con contraseña y, si está activado, por el filtro de IP. A un programa no le sirve ninguna de las dos cosas, así que los sistemas externos tienen su propia entrada: dos direcciones de solo lectura protegidas por un token. El token no cambia nada en el servidor: con él solo se pueden obtener indicadores.

Activación: «Ajustes» → «API de solo lectura»: el interruptor «Activar la API»; el token se crea automáticamente al guardar por primera vez. El botón «Token nuevo» emite otro y en ese mismo momento deja fuera a todo el que usara el anterior.

  • /api/status.php — JSON: puntuación de seguridad, estado de los módulos, la última medición de carga con sus procesos principales y los eventos de seguridad de las últimas 24 horas;
  • /api/metrics.php — las mismas cifras en el formato de texto de Prometheus (métricas arciveo_*: arciveo_security_score, arciveo_fail2ban_banned, arciveo_cpu_busy_pct, etc.).
curl -H "Authorization: Bearer <TOKEN>" https://monitor.example.com/api/status.php curl -H "Authorization: Bearer <TOKEN>" https://monitor.example.com/api/metrics.php

Si su cliente no sabe poner una cabecera, el token también se acepta como parámetro: ?token=…. Tenga en cuenta que de esa forma acaba en el registro de accesos del servidor web, por lo que es preferible la cabecera.

Prometheus. Un bloque listo para scrape_configs:

scrape_configs: - job_name: arciveo metrics_path: /api/metrics.php scheme: https authorization: credentials: <TOKEN> static_configs: - targets: ['monitor.example.com']
La respuesta se guarda en caché 5 minutos: detrás de esas cifras está la misma consulta pesada a las herramientas que hay detrás del panel. Consultar a menudo no le cuesta nada al servidor —los valores simplemente se repiten hasta la siguiente actualización—, pero un intervalo menor de un minuto tampoco tiene sentido.
401: el token es incorrecto o no se ha enviado. 404 con el texto api disabled: la API está desactivada en los ajustes. Las peticiones rechazadas se escriben en el registro de la aplicación junto con la IP del remitente, así que un intento de adivinar el token se ve en la página «Registros de la aplicación».

10. Resumen de servidores en el área personal

El panel se instala en cada servidor por separado y responde muy bien a la pregunta «cómo va esta máquina». A la pregunta «cómo van todas mis máquinas» no responde: para eso cada panel puede enviar un resumen breve al área personal, donde los servidores aparecen en una sola lista.

Qué tomar del área personal my.arciveo.com → sección «Mis servidores»: allí se emite un token (con la forma fk_…) y se indica la dirección del receptor https://my.arciveo.com/api/fleet.php. El token es uno por cuenta: el mismo para todos sus servidores.

Qué configurar en el panel: «Ajustes» → «Resumen de servidores»: dirección del receptor, token, nombre del servidor (cómo aparecerá en la lista; si lo deja vacío se usa el dominio del panel) y el interruptor «Enviar el resumen». El botón «Enviar ahora» comprueba la conexión al instante, sin esperar al horario.

Después el resumen sale por cron, cada 15 minutos:

# cada 15 minutos — resumen del servidor al área personal 3,18,33,48 * * * * /usr/local/bin/fleet-push-all.sh >> /path/to/monitor/logs/cron.log 2>&1

Qué se envía exactamente: nombre y dominio del servidor, sistema operativo, tiempo de funcionamiento, versión de PHP; la puntuación de seguridad; el estado de los módulos (UFW, número de jails de Fail2ban, baneos de hoy, si CrowdSec, Suricata y Falco están en marcha, amenazas de ClamAV, cambios de AIDE, índice de Lynis, actualizaciones pendientes, si hace falta reiniciar, días hasta la caducidad del SSL, número de puertos abiertos peligrosos); la última medición de carga (load, CPU, RAM, disco, inodes); los contadores de eventos de las últimas 24 horas y el último evento crítico. El resumen no contiene registros, ni contraseñas, ni contenido de archivos.

Envía el servidor; el área personal no pregunta. Por eso no hay que abrir ninguna entrada a su red desde fuera, y un panel cerrado con filtro de IP también informa. El nodo se reconoce por el dominio del panel, de modo que la lista no se duplica tras un reinicio o un cambio de IP.
Si un servidor aparece como silencioso en el área personal, es que el resumen lleva tiempo sin llegar. Compruebe por orden: el interruptor «Enviar el resumen», la línea de cron (sudo crontab -l) y qué responde el botón «Enviar ahora». Los errores de envío se escriben en el registro de la aplicación con la marca FLEET: allí se ve si el token fue rechazado (HTTP 401) o si no llegó a establecerse la conexión.

Herramientas de seguridad

11. Cortafuegos UFW

UFW (Uncomplicated Firewall) es una interfaz sencilla para nftables/iptables. Cierra todos los puertos entrantes salvo los permitidos explícitamente. La página «Cortafuegos UFW» muestra el estado y las reglas.

sudo apt install ufw # Permitir SSH (¡obligatorio ANTES de activar!) y web sudo ufw allow OpenSSH sudo ufw allow 80,443/tcp # Cerrar la BD desde el exterior (acceso solo local) sudo ufw deny 3306 # Activar y comprobar sudo ufw enable sudo ufw status verbose
Antes de ufw enable, permita obligatoriamente SSH (ufw allow OpenSSH), de lo contrario perderá el acceso al servidor.
La «Exposición externa» del panel tiene en cuenta UFW: un puerto cerrado por una regla deny no se considera accesible desde el exterior.
Skipping adding existing rule no es un error. Así indica UFW que esa misma regla ya existe y no la vuelve a añadir. Al reejecutar la configuración automática (que es idempotente), este es un mensaje normal: no hace falta reaccionar.

12. Instalación de Fail2ban

Bloquea automáticamente la IP tras superar el número de intentos de inicio de sesión fallidos. Analiza los registros de SSH, nginx, Apache y otros servicios.

sudo apt install fail2ban sudo systemctl enable --now fail2ban # Comprobar estado: sudo fail2ban-client status
La configuración de trabajo (jail.local con decenas de jails y baneo automático desde ipsum) está en la siguiente sección.

13. Configuración operativa de Fail2ban + ipsum

La instalación básica está arriba. Aquí tiene una configuración operativa que genera decenas de jails activos y miles de bloqueos: ajustes generales, jails clave y baneo automático de IP maliciosas de la lista ipsum.

El archivo /etc/fail2ban/jail.local — ajustes generales y jails esenciales:

[DEFAULT] bantime = 1w findtime = 900 maxretry = 3 backend = systemd banaction = nftables-multiport ignoreip = 127.0.0.1/8 ::1 <YOUR_IP> <TRUSTED_NETS> # Baneo progresivo: cada repetición dura más bantime.increment = true bantime.factor = 2 bantime.maxtime = 5w bantime.rndtime = 300 [sshd] enabled = true maxretry = 5 bantime = -1 # baneo permanente por fuerza bruta de SSH findtime = 3600 # Reincidentes: quien acumula varios baneos queda baneado para siempre [recidive] enabled = true logpath = /var/log/fail2ban.log banaction = %(banaction_allports)s bantime = -1 findtime = 86400 maxretry = 2 [http-get-dos] enabled = true maxretry = 100 findtime = 300 bantime = 1w # Servicios web (apache-*, nginx-*, php-url-fopen, phpmyadmin-syslog): [nginx-http-auth] enabled = true port = http,https [apache-badbots] enabled = true port = http,https # … y el resto de jails por servicio (dovecot, exim, postfix-sasl, # mysqld-auth, vsftpd, portscan, pam-generic) — enabled = true
En ignoreip anote obligatoriamente su propia IP y las redes de confianza; de lo contrario, podría banearse a sí mismo. Tras los cambios: sudo fail2ban-client reload.

Carga automática de la lista de bloqueo ipsum — en el cron de root (sudo crontab -e): el level 1 (más de 100 mil IP) se carga en el conjunto ipsum, que se filtra en el cortafuegos (más detalles en la sección «Lista de bloqueo IPset»):

# 04:00 — actualización del ipset ipsum (level 1, máxima cobertura): 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1
El conjunto debe llamarse ipsum: es exactamente el que lee el panel (tarjeta «IPset ipsum»). Niveles: levels/1.txt — máxima cobertura, levels/3.txt — más preciso (3 o más fuentes).

Por qué el «Monitor de seguridad» se divide en dos zonas. La protección funciona en dos niveles, y el panel no los mezcla:

  • Ataques reales (reactivo) — todo lo que capturó fail2ban: intentos reales de intrusión (jails sshd, apache-*, nginx-*, etc.) y reincidentes graves (jail recidive — los que ya han sido baneados varias veces). Son IP que realmente intentaron entrar en su servidor: aparecen en el mapa de ataques y en la «Línea de tiempo».
  • Bloqueo preventivo (proactivo) — la lista de bloqueo pública de IP maliciosas conocidas ipset ipsum, filtrada en el cortafuegos mediante la regla DROP. En su mayoría, estas direcciones ni siquiera llegaron a tocar su servidor: se cortan por adelantado; el contador «IPset ipsum» muestra cuántas se filtraron de forma preventiva.

La diferencia es sencilla: reactivo — «estos atacaron y fueron baneados», preventivo — «estos fueron bloqueados antes de intentarlo». Antes se forzaba artificialmente en recidive la list-3 de ipsum (de ahí la antigua división de «recidive por lista»); ahora recidive contiene solo reincidentes reales, y el bloqueo preventivo está íntegramente en el cortafuegos.

14. Lista de bloqueo IPset (ipsum)

ipsum es una lista pública de IP maliciosas que se actualiza a diario. El monitor muestra el número de direcciones cargadas en el panel y en el mapa de ataques, y lo tiene en cuenta en la Puntuación de seguridad (−10 si el conjunto no está cargado).

La opción mínima sin fail2ban es un conjunto ipsum independiente con bloqueo mediante iptables:

# Crear el conjunto (una sola vez): sudo ipset create ipsum hash:ip # Script de actualización /usr/local/bin/update-ipsum.sh: #!/bin/bash ipset flush ipsum for ip in $(curl -s https://raw.githubusercontent.com/stamparm/ipsum/master/levels/3.txt); do ipset add ipsum "$ip" 2>/dev/null done iptables -C INPUT -m set --match-set ipsum src -j DROP 2>/dev/null \ || iptables -I INPUT -m set --match-set ipsum src -j DROP # Cron (sudo crontab -e, a diario a las 4:00): 0 4 * * * /usr/local/bin/update-ipsum.sh
La opción avanzada con fail2ban-recidive está en la sección «Configuración de trabajo Fail2ban + ipsum».
ipset vive en memoria y se pierde al reiniciar. Un simple cron diario dejará el conjunto vacío desde el reinicio hasta la siguiente ejecución (el panel mostrará 0). Cargue el conjunto también al arrancar: lleve la carga a un script y engánchelo a @reboot. De paso, el comando create … -exist fija el límite maxelem 300000 (por defecto 65536 — el level 1 no cabe y aparecerá «Hash is full»):
# /usr/local/bin/load-ipsum.sh #!/bin/bash curl -s https://raw.githubusercontent.com/stamparm/ipsum/master/levels/1.txt \ | grep -v '^#' | sed 's/^/add ipsum /' \ | (echo "create ipsum hash:ip hashsize 131072 maxelem 300000 -exist"; echo "flush ipsum"; cat) \ | ipset restore -exist # sudo crontab -e — a diario a las 04:00 Y en cada arranque: 0 4 * * * /usr/local/bin/load-ipsum.sh @reboot sleep 60 && /usr/local/bin/load-ipsum.sh
Cómo funciona en la instalación automática. El script carga la lista completa level 1 (más de 100 000 IP) en el conjunto ipsum y, si el cortafuegos lo gestiona el instalador (VPS nuevo — perfiles «Completa»/«Ligera»), conecta el conjunto a UFW con una regla DROP: el tráfico de esas IP se bloquea realmente. La regla se coloca después de ESTABLISHED,RELATED, por lo que las conexiones actuales (incluido su SSH) no se cortan — solo se cortan las nuevas conexiones de la lista. El conjunto se restaura al cargar el servicio ipsum-load.service antes del cortafuegos (de lo contrario UFW no habría arrancado), y se actualiza con cron a las 04:00. En un servidor ya configurado (panel, cortafuegos propio) el instalador no toca el cortafuegos — allí ipsum queda como una lista para el panel y el mapa de ataques, y la regla DROP se añade a mano si se desea (la opción mínima con iptables … --match-set ipsum … -j DROP está arriba). Con la instalación automática no hay que hacer nada a mano.

15. Instalación de CrowdSec

Reemplazo moderno de Fail2ban con inteligencia de amenazas colectiva: bloqueos de la comunidad más reglas propias. Requiere un bouncer aparte para aplicar los bloqueos al cortafuegos.

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash sudo apt install crowdsec sudo systemctl enable --now crowdsec # Bouncer para iptables/nftables: sudo apt install crowdsec-firewall-bouncer-iptables # Comprobar el estado: sudo systemctl status crowdsec sudo cscli decisions list sudo cscli bouncers list
El estado «No iniciado» en el panel = el servicio está instalado, pero no está activo (el monitor lo comprueba mediante systemctl is-active crowdsec). Para activarlo: sudo systemctl enable --now crowdsec; si falla, consulte sudo journalctl -u crowdsec -n 30. La misma regla aplica a cualquier servicio en estado «No iniciado» (Suricata, Falco, Monit, MySQL).
«0 escenarios» o «0 bouncers» en el panel. CrowdSec viene casi vacío de fábrica: sin colecciones no detecta nada, y sin un bouncer registrado los bloqueos no se aplican al cortafuegos. Instale las colecciones básicas y asegúrese de que el bouncer aparece en la lista:
# Colecciones básicas (Linux + SSH + servidor web): sudo cscli collections install crowdsecurity/linux crowdsecurity/sshd crowdsecurity/base-http-scenarios sudo systemctl reload crowdsec # El bouncer debe aparecer en la lista y con estado de conexión activa: sudo cscli bouncers list
En el registro del bouncer aparece stream halted / los bloqueos no se aplican. Es una clave de api huérfana: el bouncer se eliminó de cscli bouncers list, pero su clave antigua quedó en /etc/crowdsec/bouncers/*.yaml. Vuelva a registrar el bouncer e indique la clave nueva:
sudo cscli bouncers add fw-bouncer # generará una nueva api_key # escriba esta clave en api_key: en /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml sudo systemctl restart crowdsec-firewall-bouncer
La instalación automática (perfil «Protección completa») instala las colecciones y registra el firewall-bouncer por sí sola; hacerlo a mano solo es necesario en una instalación manual o tras una intervención manual en CrowdSec.

16. Instalación de AIDE

AIDE (Advanced Intrusion Detection Environment) toma una instantánea del sistema de archivos y en cada comprobación informa de los cambios en /etc, /bin, /usr. Tras la instalación es obligatorio inicializar la base (aideinit).

sudo apt install aide # Inicialización de la base (5–15 minutos): sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db # Ubuntu 24.04: el directorio /var/lib/aide se crea en modo 700 (propietario _aide), # y el panel (www-data) no ve la base → muestra «No inicializada». # Abrir el directorio para el paso (los archivos de la base quedan en 600): sudo chmod 755 /var/lib/aide # Primera comprobación CON ESCRITURA en el registro que lee el monitor. # En Ubuntu/Debian aide requiere un --config explícito (si no, «missing configuration»; # el binario aide.wrapper no se incluye en las versiones nuevas): sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
Durante aideinit la terminal permanece 5–15 minutos en la línea Running aide --init... — es normal (hasheo de todo el sistema de archivos, carga sobre el disco). No interrumpa con Ctrl+C. Si el proceso «se cuelga» pero no escribe nada, es posible que esté esperando una respuesta a una petición oculta Overwrite existing aide.db.new [Yn]? (pulse Y). Compruebe la actividad desde otra sesión: pgrep -af aide.
Error de aideinit: «21_aide_spamassassin … printf: invalid number» (return code 20) — un fallo conocido del fragmento de configuración de AIDE en Ubuntu 22.04. La base no se crea. Retire el fragmento defectuoso y vuelva a intentarlo:
sudo mv /etc/aide/aide.conf.d/21_aide_spamassassin /etc/aide/21_aide_spamassassin.disabled sudo aideinit sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Estados en el panel. «No inicializada» = el panel no ve el archivo de la base: o bien aideinit no se ha ejecutado, o bien (Ubuntu 24.04) el directorio /var/lib/aide se creó en modo 700 y es inaccesible para www-data — se soluciona con sudo chmod 755 /var/lib/aide (véase el bloque anterior). «Sin comprobaciones» = la base existe, pero aún no se ha realizado ninguna comprobación — no es un error. El monitor lee los resultados de /var/log/aide/aide.log.
Comprobación periódica → registro para el panel. El /etc/cron.daily/aide estándar en las nuevas Ubuntu/Debian puede no escribir /var/log/aide/aide.log en el formato necesario (y aide.wrapper ya no está en ellas). Es más fiable añadir su propio cron con un --config explícito — escribe el registro como root en modo 644, y el monitor lo lee sin grupos adicionales:
# sudo crontab -e — comprobación diaria a las 02:00: 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # Ejecutar ahora, sin esperar a la programación: sudo bash -c 'aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1'
La instalación automática ya hace todo esto: chmod 755 /var/lib/aide y el cron de comprobación a las 02:00 — no hace falta nada manual.
Realice la primera inicialización en un servidor limpio — antes de instalar aplicaciones web. Tras cambios legítimos, vuelva a crear la base: sudo aideinit && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db.

17. Instalación de ClamAV

Escáner antivirus para Linux. Especialmente útil para revisar /var/www en busca de shells PHP y código malicioso.

sudo apt install clamav clamav-daemon sudo systemctl enable --now clamav-daemon # Actualizar la base de firmas: sudo freshclam # Escanear una carpeta manualmente: sudo clamscan -r /var/www --infected
¿El demonio clamd muestra «Inactivo» tras enable --now? Tres causas típicas:

1. En la configuración quedó la línea Example: clamd se niega a iniciar mientras esté presente:

sudo sed -i '/^Example/d' /etc/clamav/clamd.conf sudo systemctl restart clamav-daemon

2. No se ha descargado la base de firmas: clamd no arranca sin ella:

sudo systemctl stop clamav-freshclam sudo freshclam sudo systemctl start clamav-freshclam sudo systemctl restart clamav-daemon

3. Simplemente está cargando: clamd carga en memoria unos 8 millones de firmas en 30–60 s. Espere y compruebe: systemctl is-active clamav-daemon (estado activating → aún cargando).

Diagnóstico: sudo journalctl -u clamav-daemon -n 30 --no-pager.
¿En el panel aparece «Archivos analizados: 0» / «Último análisis: —»? El demonio clamd solo mantiene las firmas en memoria; por sí mismo no analiza nada según una programación. El panel muestra los resultados del análisis programado, por lo que se necesita un cron que analice y escriba un registro. La instalación automática coloca el envoltorio /usr/local/bin/clamav-scan.sh y un cron a las 01:30; tras la primera ejecución se rellenarán «Archivos analizados» y «Último análisis». Para ejecutarlo de inmediato, sin esperar a la programación: sudo /usr/local/bin/clamav-scan.sh.

18. Instalación de Linux Malware Detect (maldet)

Linux Malware Detect (LMD) — escáner de malware para amenazas web: shells PHP, backdoors web, descargadores. Usa el motor de ClamAV y lo complementa con sus propias firmas.

wget https://www.rfxn.com/downloads/maldetect-current.tar.gz tar -xzf maldetect-current.tar.gz dir=$(ls -d maldetect-*/ | head -1) && cd "$dir" && sudo bash install.sh && cd ~ rm -rf maldetect-* maldetect-current.tar.gz # Actualizar firmas: sudo maldet -u # Escanear /var/www: sudo maldet -a /var/www
LMD y ClamAV funcionan bien juntos. Último informe: maldet --report.
Durante la instalación puede aparecer la línea update-rc.d: error: unable to read /etc/init.d/maldet: es inofensiva. maldet no usa init.d; la actualización de firmas y los análisis se ejecutan mediante /etc/cron.daily/maldet. Si más abajo aparece installation completed, todo se instaló correctamente.
¿En la página LMD aparece como «No instalada» aunque está instalado? maldet no se instala mediante apt, sino en /usr/local/maldetect, y con open_basedir activado su presencia se comprueba mediante shell; consulte la sección «La página está vacía aunque hay datos en el servidor».

19. Instalación de Suricata

Sistema de detección de intrusiones en la red: analiza el tráfico a nivel de paquetes y conoce miles de firmas de ataques. Complementa a ModSecurity (que actúa a nivel HTTP, mientras que Suricata lo hace a nivel TCP/IP).

sudo add-apt-repository ppa:oisf/suricata-stable sudo apt update && sudo apt install suricata # Descargar las reglas actualizadas: sudo suricata-update sudo systemctl enable --now suricata
¿Suricata está «Activo» pero el panel no muestra alertas / el número de eventos es 0? Suricata escribe en /var/log/suricata/eve.json como root con permisos 750 en el directorio, y el servidor web (www-data) no puede leerlo. Habilite el paso al directorio: los archivos internos siguen protegidos:
sudo chmod o+rx /var/log/suricata
La instalación automática lo hace por usted; no es necesario hacerlo manualmente.

20. Instalación de Falco

Intercepta las llamadas al sistema mediante eBPF/kernel module y detecta anomalías en tiempo real: shell desde nginx, lectura de /etc/passwd por un proceso web, escritura en /bin, etc.

curl -fsSL https://falco.org/repo/falcosecurity-packages.asc > /tmp/falco.asc gpg --dearmor < /tmp/falco.asc | sudo tee /usr/share/keyrings/falco-archive-keyring.gpg > /dev/null sudo chmod 644 /usr/share/keyrings/falco-archive-keyring.gpg && rm /tmp/falco.asc echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" \ | sudo tee /etc/apt/sources.list.d/falcosecurity.list sudo apt update && sudo apt install falco sudo systemctl enable --now falco
El monitor lee los eventos de Falco mediante journalctl -u falco (sin sudo, a través del grupo systemd-journal). Asegúrese de que www-data pertenezca a ese grupo — consulte «Configuración de sudo» (punto 2) en la página de instalación manual.
«0 eventos en 24 horas» es lo normal, no un error. Falco es event-driven: permanece en silencio mientras todo va bien y solo registra un evento ante una anomalía (shell desde un proceso web, lectura de /etc/passwd, escritura en directorios del sistema). Cero eventos críticos en un día en un servidor tranquilo es un estado saludable.
Para el panel es más fiable la salida a archivo. La lectura mediante journalctl requiere permisos sobre el registro; para que el panel vea los eventos de forma estable, la instalación automática activa en Falco file_output → /var/log/falco/falco.log y asigna al servicio UMask=0022 (el log lo lee el servidor web). En una instalación nueva no es necesario configurarlo manualmente.

21. Instalación de ModSecurity (WAF)

ModSecurity — cortafuegos web (WAF) para Apache o Nginx. Bloquea ataques a nivel de aplicación: inyecciones SQL, XSS, salto de rutas, escáneres.

# Apache: sudo apt install libapache2-mod-security2 sudo a2enmod security2 # Conjunto de reglas OWASP Core Rule Set: sudo apt install modsecurity-crs # OBLIGATORIO: sin este archivo el motor de reglas está desactivado sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf sudo apache2ctl configtest && sudo systemctl reload apache2 # Comprobación: debe devolver 403 curl -s -o /dev/null -w '%{http_code}\n' "https://monitor.example.com/?id=1%20UNION%20SELECT%201,2--"
Instalar el paquete por sí solo no protege nada. Apache incluye las configuraciones con la línea IncludeOptional /etc/modsecurity/*.conf, y el paquete solo coloca modsecurity.conf-recommended, que no coincide con la máscara *.conf. Si no lo copia a modsecurity.conf, SecRuleEngine permanece en Off: el módulo está cargado, las reglas CRS están cargadas, pero el tráfico no se inspecciona y no se crea el registro de auditoría. El modo intermedio DetectionOnly solo anota los eventos en el registro sin bloquear las solicitudes; el panel lo muestra en amarillo.

Acceso del panel al registro de auditoría. El registro /var/log/apache2/modsec_audit.log pertenece a root (permisos 640) y el usuario web no puede leerlo. El panel obtiene los datos mediante un envoltorio; créelo:

sudo tee /usr/local/bin/monitor-modsec >/dev/null <<'EOF' #!/bin/sh echo "ENGINE=$(grep -hE '^SecRuleEngine[[:space:]]+' /etc/modsecurity/*.conf 2>/dev/null | tail -1 | awk '{print $2}')" echo "---LOG---" tail -n 3000 /var/log/apache2/modsec_audit.log 2>/dev/null echo "---RULES---" for f in /etc/modsecurity/crs/rules/*.conf /usr/share/modsecurity-crs/rules/*.conf /etc/modsecurity/custom-rules.conf; do [ -f "$f" ] && { echo "===FILE:$(basename "$f")==="; cat "$f"; } done EOF sudo chown root:root /usr/local/bin/monitor-modsec sudo chmod 755 /usr/local/bin/monitor-modsec # en /etc/sudoers.d/monitor (usuario = aquel con el que se ejecuta PHP-FPM): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-modsec
El envoltorio toma la última directiva SecRuleEngine sin sangría: las líneas con sangría están dentro de bloques <LocationMatch>/<Directory> (por ejemplo, la desactivación del WAF para phpMyAdmin) y no definen el modo global.
El usuario en sudoers debe coincidir con el usuario del pool FPM: en un Apache/Debian normal es www-data, en HestiaCP el pool del sitio se ejecuta con el propietario del sitio (por ejemplo, admin) — compruebe grep -E '^user' /etc/php/*/fpm/pool.d/monitor.example.com.conf.
Si el sitio está detrás de un proxy Nginx (HestiaCP), Apache ve como cliente al propio proxy; el panel toma la IP real del atacante de la cabecera X-Forwarded-For. En las estadísticas solo entran las transacciones con una regla activada: la directiva SecAuditLogRelevantStatus anota en el registro de auditoría cualquier respuesta 4xx/5xx, por lo que también entran los 403/500 normales, que el panel no considera eventos WAF.
El bloque ---RULES--- lo necesita la sección «Todas las reglas activas»: el panel muestra no solo las reglas activadas, sino absolutamente todas las reglas CRS cargadas más las personalizadas. Las tres rutas del bucle for f in … son las ubicaciones típicas de las reglas CRS y los añadidos locales; si su disposición es distinta (el paquete coloca los archivos en su propio directorio, o las reglas personalizadas no están en /etc/modsecurity/custom-rules.conf), localice las rutas reales con el comando sudo grep -rl 'IncludeOptional\|^Include ' /etc/apache2/mods-enabled/security2.conf /etc/apache2/conf-enabled/*.conf 2>/dev/null e insértelas en la lista. Si el envoltorio es antiguo (sin esta sección), la sección simplemente mostrará la advertencia «no disponible» y el resto de la página funciona como antes.

22. Instalación de Auditd

Auditd (Linux Audit Daemon) registra las llamadas al sistema a nivel del núcleo: inicios y cierres de sesión, comandos sudo, intentos fallidos de autenticación, cambios en archivos. El monitor muestra los inicios de sesión, los intentos fallidos y los comandos sudo de hoy.

sudo apt install auditd audispd-plugins sudo systemctl enable --now auditd # Comprobar el estado y los eventos: sudo systemctl status auditd sudo ausearch -m USER_LOGIN -ts today
El monitor lee los eventos mediante ausearch (/usr/sbin/ausearch) y, si es necesario, desde /var/log/audit/audit.log con el comando tail. Ambos deben estar en sudoers.

23. Instalación de Monit

Supervisa los servicios (nginx, php-fpm, mysql, etc.) y los reinicia cuando fallan. Puede enviar alertas por email.

sudo apt install monit sudo systemctl enable --now monit # Configuraciones: sudo nano /etc/monit/monitrc ls /etc/monit/conf.d/
El monitor obtiene la lista de servicios mediante monit status. En /etc/monit/monitrc debe estar habilitada la interfaz HTTP (bloque set httpd con allow localhost), de lo contrario monit status devolverá un error.
¿En el panel aparece «0 servicios bajo supervisión»? Hay dos causas. (1) La interfaz HTTP está desactivada: en monitrc la línea set httpd está comentada (por defecto figura como # set httpd port 2812 …). Descomente el bloque y permita localhost. (2) El httpd activado por sí solo no supervisa nada: Monit solo cuenta lo que se describe con estrofas check; sin ellas la lista está vacía incluso con la interfaz en funcionamiento. Configuración mínima funcional:
# /etc/monit/conf.d/00-httpd — interfaz HTTP para localhost: set httpd port 2812 use address localhost allow localhost # ejemplos de estrofas check (qué supervisar): check process sshd with pidfile /run/sshd.pid start program = "/usr/bin/systemctl start ssh" stop program = "/usr/bin/systemctl stop ssh" check filesystem rootfs with path / if space usage > 90% then alert
sudo monit -t # comprobar la sintaxis (Control file syntax OK) sudo systemctl reload monit sudo monit status
La instalación automática coloca un conf.d listo con httpd en 2812 y un conjunto de comprobaciones: en una instalación nueva no hay que configurar nada manualmente.
¿El servicio está en estado «Con errores»? El monitor solo muestra el estado y deliberadamente no reinicia los servicios desde el panel web (sería ejecución remota de comandos root en un panel de seguridad). El diagnóstico y el reinicio se hacen por SSH mediante Monit:
sudo monit status <service> # causa del error sudo monit restart <service> # reinicio mediante Monit # si Monit no levanta el servicio, revise su propia unidad: sudo systemctl status <unit> --no-pager sudo journalctl -u <unit> -n 50 --no-pager

24. Instalación de PSAD (detección de escaneo de puertos)

PSAD analiza el registro de iptables y detecta escaneos de puertos y ataques de red, asignando a cada origen un nivel de amenaza (1–5). Complementa a fail2ban y Suricata.

sudo apt install psad # PSAD lee el registro de iptables — hay que activar el registro (UFW lo hace por sí mismo). # Para iptables puro, añada reglas LOG a las cadenas INPUT/FORWARD. sudo psad --sig-update sudo systemctl enable --now psad
El monitor lee los datos mediante psad --Status (necesario en sudoers). Sin el registro de iptables la página estará vacía — es normal mientras no haya habido escaneos.

25. AppArmor / SELinux (control de acceso)

Mandatory Access Control limita a qué archivos y recursos puede acceder un programa, incluso si ha sido comprometido. En Ubuntu/Debian se usa AppArmor de forma predeterminada (normalmente ya está instalado y activo).

# AppArmor (Ubuntu/Debian): sudo apt install apparmor apparmor-utils sudo systemctl enable --now apparmor sudo aa-status # comprobar perfiles
El monitor lee el estado mediante aa-status (debe estar en sudoers). Muestra el número de perfiles en modo enforce/complain y los procesos sin perfil.

Que haya más «Perfiles cargados» que enforce + complain es normal. En AppArmor 4.x (Ubuntu 24.04 y posterior) apareció el modo unconfined: el perfil está cargado en el núcleo, pero no limita nada. Ubuntu marca así decenas de perfiles de programas que usan user namespaces (navegadores, clientes de torrent y similares). Cuando existen esos perfiles, la tarjeta «Perfiles cargados» se vuelve ámbar y muestra su número — por ejemplo unconfined: 90 con 120 cargados y 26 en enforce. En realidad solo protegen los perfiles en enforce; en Ubuntu 22.04 (AppArmor 3.x) este modo no existe y las cifras siempre cuadran.

sudo aa-status | grep -E "profiles are" # desglose por modos sudo aa-enforce /etc/apparmor.d/profile-name # pasar el perfil a enforce
Pasar a enforce los perfiles que Ubuntu dejó deliberadamente en unconfined solo tiene sentido hacerlo de forma consciente: no están desactivados por error, sino porque de lo contrario se rompe el funcionamiento de los propios programas. Los perfiles en complain son otra cosa: ahí las reglas ya están escritas y solo no se aplican.

26. Instalación de debsums (integridad de paquetes)

debsums comprueba que los archivos de los paquetes instalados coinciden con las sumas de verificación del repositorio, detectando binarios del sistema alterados (complementa a AIDE). Una comprobación completa tarda 1–2 minutos, por eso se ejecuta mediante cron, y el panel lee el resultado de data/debsums/debsums.log y lo clasifica por categorías (solo importan los binarios y las bibliotecas).

La tarea va en el cron de root (sudo crontab -e). El script listo debsums-scan.sh se coloca en /usr/local/bin/ (chmod +x; consulte el resumen de tareas cron) y escribe él mismo el informe en data/debsums/ del panel.

sudo apt install debsums # Línea de cron (diariamente a las 4:30): 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

El script debsums-scan.sh encuentra por sí mismo la carpeta data/ del panel: no hace falta indicar la ruta.

Los cambios en /etc/ (configuraciones) y /usr/share/ (recursos) del servidor suelen ser normales; el panel los marca con un color distinto. Preocupantes son los cambios en binarios y bibliotecas (/bin, /sbin, /usr/lib, etc.): la tarjeta «Binarios / bibliotecas» muestra precisamente esos.

27. Configuración de informes de Lynis

Lynis se ejecuta manualmente o mediante cron. El informe debe guardarse en la carpeta data/lynis/ del proyecto — el monitor lee el archivo lynis-report.dat.

# Ejecución única (indique su propia ruta a la raíz del panel): sudo lynis audit system --report-file /path/to/monitor/data/lynis/lynis-report.dat # Auditoría diaria — línea de cron (envoltorio lynis-scan.sh listo en /usr/local/bin/, ver resumen): 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1
Tras la primera ejecución, la página «Auditoría de Lynis» mostrará de inmediato el hardening index, las advertencias y las recomendaciones.
Botón «Ejecutar auditoría» en la página de Lynis. Ejecuta lynis-scan.sh en segundo plano directamente desde el panel (sin esperar a cron): muestra «Escaneando…» y al terminar actualiza el informe por sí mismo. Para ello, el usuario web necesita una línea de sudoers que permita ejecutar el script — el instalador la añade automáticamente en /etc/sudoers.d/monitor. Si el panel se instaló manualmente o anteriormente, añádala con el mismo usuario ya indicado en el archivo:
u=$(sudo awk '/NOPASSWD/ && !/lynis-scan/ {print $1; exit}' /etc/sudoers.d/monitor) [ -n "$u" ] && echo "$u ALL=(ALL) NOPASSWD: /usr/local/bin/lynis-scan.sh" | sudo tee -a /etc/sudoers.d/monitor sudo visudo -c && sudo chmod 440 /etc/sudoers.d/monitor

28. Configuración de informes de Logwatch

Logwatch debe guardar los informes diarios en la carpeta data/logwatch/ del proyecto en formato .txt. El monitor muestra el último informe y el archivo.

# Diariamente (6:00) — línea de cron (envoltorio listo logwatch_daily.sh en /usr/local/bin/, ver resumen): 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1

Módulos del panel

29. Monitor de red (integrado)

El monitor de red no requiere instalación: es una página integrada del panel. Muestra el estado de red del servidor a partir de fuentes locales:

  • interfaces y tráfico — desde /proc/net/dev;
  • estado de los enlaces (UP/DOWN) e IP — mediante ip;
  • conexiones y puertos de escucha — mediante ss;
  • eventos de red del kernel en las últimas 24 h — mediante journalctl -k.

Las tres primeras fuentes funcionan sin sudo, por lo que las interfaces, el tráfico, las conexiones y los puertos se ven de inmediato. El bloque «Eventos del kernel» usa journalctl -k: se lee a través del grupo systemd-journal («Configuración de sudo», punto 2), no hace falta sudo. Compruebe que todo esté accesible para el usuario web:

# Comprobación como www-data (con él funciona PHP): sudo -u www-data bash -c 'cat /proc/net/dev' sudo -u www-data bash -c 'ip -o link show' sudo -u www-data bash -c 'ss -s' sudo -u www-data journalctl -k --no-pager -n 5
El bloque «Eventos de red del kernel» muestra eventos de la pila de red del kernel (cambio de enlace up/down, errores de portadora, «network unreachable»). Los registros del cortafuegos UFW BLOCK no aparecen aquí: están en las páginas «Cortafuegos UFW» y «Mapa de ataques». Un bloque vacío con la marca verde = no hubo fallos de red en las últimas 24 h.

30. Disco y SMART

La página integrada muestra tres cosas:

  • Sistemas de archivos — ocupación de las particiones (df); la barra se pone roja al ≥90%;
  • Unidades de almacenamiento — lista de discos (lsblk), solo los reales (loop/snap se ocultan);
  • Salud (SMART) — estado del disco y atributos (smartctl).

El espacio y la lista de dispositivos funcionan de inmediato, sin configuración. Para SMART se necesita el paquete smartmontools. El proceso web no tiene acceso directo a los dispositivos de disco, por eso SMART se captura mediante cron en el archivo data/disk/smart.txt, y el panel lo lee.

La tarea va en el cron de root (sudo crontab -e). El envoltorio listo smart-scan.sh se coloca en /usr/local/bin/ (chmod +x; consulte el resumen de tareas cron) y escribe por sí mismo en data/disk/ del panel.

sudo apt install smartmontools # Línea de cron (cada 30 minutos): */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1

El envoltorio smart-scan.sh encuentra por sí mismo el data/ del panel — no hace falta indicar la ruta. Internamente lsblk -e7,11 excluye loop/cdrom.

En discos virtuales (QEMU/KVM y similares) normalmente solo está disponible el estado general «salud: OK», mientras que la temperatura, las horas de funcionamiento y los sectores reasignados pueden aparecer vacíos — es normal. En un servidor físico se muestran todos los atributos.

31. Rendimiento (CPU/RAM/Red/Disco)

La página muestra el historial de carga del servidor de las últimas 24 horas: Load Average, uso de CPU y espera de I/O, RAM/Swap, tráfico de red (recepción/envío), I/O de disco (lectura/escritura), ocupación de disco e inodes, descriptores de archivo abiertos y conexiones MySQL, además del número actual de conexiones TCP y procesos. La ventana de 24 horas es la predeterminada: los botones de arriba la cambian a 7 días o 30 días.

Los datos los recopila cron/collect_metrics.php: cada 5 minutos escribe una instantánea «cruda» de los contadores (/proc/loadavg, /proc/meminfo, /proc/stat, /proc/net/dev, /proc/diskstats, df/df -i, /proc/sys/fs/file-nr, SHOW GLOBAL STATUS LIKE 'Threads_connected') en la tabla de BD system_metrics; los porcentajes y las velocidades los calcula la propia página por la diferencia entre instantáneas contiguas (ocupación de disco/inodes/descriptores/conexiones MySQL son valores instantáneos, sin recálculo). No se requiere sudo: las fuentes se leen sin permisos de root.

Dos profundidades de almacenamiento. Las instantáneas «en bruto» de cinco minutos viven un día (más una hora de margen, para que el punto más antiguo del gráfico tenga un predecesor con el que calcular). Cada hora terminada se pliega en una sola fila; esa serie horaria se guarda 30 días y es precisamente la que leen las vistas «7 días» y «30 días». Así, un mes de historial cuesta unas 720 filas en lugar de unas 8600.

En los periodos de 7 y 30 días se muestran medias por hora, no mediciones individuales: un pico de cinco minutos se suaviza a esa escala, y la página lo advierte. Para el Load Average la fila horaria guarda tanto la media como el pico de la hora, por lo que el gráfico muestra ambas líneas. Justo después de una actualización, los periodos largos están vacíos («todavía no hay historial para este periodo»): la serie horaria empieza a acumularse desde ese momento.
# Línea de cron (cada 5 minutos): */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1

El envoltorio collect-metrics-all.sh (véase el resumen de tareas cron) detecta por sí mismo todas las instancias del panel instaladas en el servidor y ejecuta el cron/collect_metrics.php de cada una en nombre del propietario del sitio.

Hasta que el recopilador no se ejecute al menos dos veces (los primeros ~10 minutos tras la instalación), la página muestra «recopilando datos»: los gráficos necesitan como mínimo un par de puntos contiguos para calcular velocidades y porcentajes.

Procesos principales. Debajo de los gráficos hay dos tablas con los cinco procesos que más CPU consumen:

  • «Procesos en la última medición»: qué cargaba el servidor en el momento de la última instantánea (la hora figura en la cabecera de la tarjeta);
  • «Procesos en el pico del día»: los mismos cinco, pero tomados en la medición con el Load Average más alto de las últimas 24 horas. Esa es la respuesta a la pregunta «qué fue eso de madrugada» cuando el pico del gráfico ya ha pasado.

Ambas tablas describen siempre las últimas 24 horas, aunque los gráficos muestren un mes: la instantánea de procesos solo la llevan las filas de cinco minutos, y esas no se guardan más de un día.

Los procesos de la propia medición (ps, el script PHP de recogida y la cadena cron/sudo por encima) se excluyen de la lista; de lo contrario encabezarían todas las instantáneas: ps muestra casi el 100 % de CPU en el instante en que arranca. Las demás tareas de cron lanzadas en ese mismo minuto sí aparecen, y son justo lo que la tabla debe mostrar.

Alertas de carga (sección «Ajustes» → «Alertas de carga»): al superarse el umbral de CPU/RAM/disco/inodes el panel envía una notificación por todos los canales activados (Telegram, Email, webhook, push, los mismos que el informe diario; no hay que activar nada aparte para las alertas) y otra cuando la métrica vuelve a la normalidad. Mientras el umbral se mantiene no repite el aviso: la siguiente notificación llega solo tras un ciclo «se normalizó → volvió a superarse».

Los umbrales los comprueba el mismo collect_metrics.php en cada ejecución (cada 5 minutos): no hace falta un cron aparte. El estado «ya notificado / aún no» se guarda en data/alerts_state.json, y los umbrales en los ajustes del panel.

32. Eventos de seguridad (un sensor cada 5 minutos)

El informe diario responde a la pregunta «cómo va el servidor». El sensor responde a otra distinta: «qué acaba de ocurrir». Cada 5 minutos consulta las fuentes rápidas y registra solo lo que ha cambiado desde la comprobación anterior; la página «Eventos de seguridad» del menú muestra el registro de hallazgos.

El sensor no toca las comprobaciones lentas —Lynis, debsums, actualizaciones de paquetes, caducidad del SSL—: sus cifras cambian una vez al día de todos modos y siguen siendo cosa del informe diario.

Qué detecta: nueve fuentes.

  • Fail2ban: nuevos baneos (se compara la lista de direcciones baneadas, de modo que un desbaneo no es un evento y un baneo vigente se informa exactamente una vez);
  • CrowdSec: nuevas decisiones;
  • Falco: eventos de nivel Emergency, Alert y Critical;
  • Suricata: nuevas alertas; la marca [Priority: 1] eleva el hallazgo a crítico;
  • ClamAV: nuevas líneas FOUND en el registro de análisis;
  • AIDE: cambios de archivos según la última comprobación (es decir, no más a menudo de lo que esta se ejecuta según su propio horario);
  • SSH: un inicio de sesión correcto como root (crítico) y un inicio de sesión desde una dirección nunca vista (advertencia);
  • Servicios de protección: UFW, Fail2ban, CrowdSec, Suricata, Falco: un mensaje cuando un servicio se detiene y otro cuando vuelve a funcionar;
  • Puertos: un puerto que ha quedado accesible desde fuera; si detrás hay una base de datos o una caché, es crítico.

Una herramienta que no está instalada, o cuyo registro el panel no puede leer, simplemente calla: eso no es un error ni un evento.

La primera comprobación no envía nada. Cada fuente guarda una marca de «hasta dónde se ha leído ya» (archivo data/secwatch_state.json) y la primera pasada solo escribe esa marca. De lo contrario, al activar la supervisión llegaría una notificación con todo lo que los registros han acumulado durante meses.

Configuración: «Ajustes» → «Supervisión de eventos»:

  • Activar la supervisión: mientras esté desactivada, la tarea de cron se ejecuta en vano y el registro no se llena;
  • Notificar a partir de: «Advertencia» o «Crítico». Los hallazgos por debajo del umbral llegan igualmente al registro, pero no a las notificaciones;
  • Pausa, min: intervalo mínimo entre notificaciones, 15 de forma predeterminada;
  • Horas de silencio: una franja con el formato 23-7 durante la cual no se envían advertencias.
Los hallazgos críticos salen de inmediato: no los retienen ni la pausa ni las horas de silencio, porque precisamente para eso existe un sensor de 5 minutos. Aun así hay un fusible: no más de 6 notificaciones por hora, por si una fuente empieza a parpadear.

Las notificaciones salen por los mismos canales que el informe diario (Telegram, Email, webhook, push): no hay que activar nada aparte. Todos los hallazgos se escriben en el registro con independencia de que se haya enviado una notificación, y se conservan 90 días.

El registro. Filtros: periodo (24 horas, 7, 30 o 90 días), importancia y fuente; se muestran los últimos 500 apuntes. El botón «Comprobar ahora» lanza exactamente la misma pasada que cron, lo cual va bien justo después de configurarlo para no esperar cinco minutos; además indica el motivo cuando no se ha enviado ninguna notificación («por debajo del umbral de importancia», «horas de silencio», «no hay ningún canal de notificación configurado»). El contador rojo junto a la entrada del menú es el número de eventos críticos de las últimas 24 horas.

La ficha de la dirección. Al pulsar una fila del registro de eventos o del bloque Direcciones relacionadas se abre todo lo que se sabe de una dirección: la cronología de sus eventos, el país y, en la primera línea, una conclusión. La fila del registro abre la ficha cuando el evento corresponde a una sola dirección; si nombra varias, los enlaces son las direcciones dentro de la fila. Hay cuatro conclusiones:

  • Acceso posible — hubo un acceso correcto desde la dirección y esa misma dirección fue detectada por otras defensas, o justo después se activaron AIDE, ClamAV o se abrió un puerto nuevo. El único caso que conviene comprobar de inmediato;
  • Inicio de sesión desde esta dirección — hay un acceso y nada más. Suele ser el propietario desde un lugar nuevo, por eso la ficha no se pinta de rojo;
  • Bloqueada — la dirección fue detectada y bloqueada: la defensa hizo su trabajo, no hay nada que hacer;
  • Requiere revisión — hay detecciones pero no hubo bloqueo. Si la dirección vuelve, ciérrela manualmente.

Bajo la cronología está el bloque Qué ocurría en el servidor al mismo tiempo: eventos que no tienen dirección alguna. AIDE no sabe quién cambió el archivo, ClamAV no sabe quién trajo el infectado. Se muestran los que coinciden con la actividad de la dirección y las seis horas siguientes. Es el acceso junto con lo que vino después lo que separa un intento de un acceso real.

La relación se construye por dirección. Un atacante que cambia de dirección la rompe. La ficha muestra lo que vieron las defensas y no se compromete a distinguir una intrusión de su propio acceso desde un lugar nuevo.

Horario. La tarea se añade al cron de root (sudo crontab -e), desplazada 2 minutos respecto a la recogida de métricas para que ambas no arranquen a la vez:

# cada 5 minutos — comprobación de eventos de seguridad 2-57/5 * * * * /usr/local/bin/security-watch-all.sh >> /path/to/monitor/logs/cron.log 2>&1
Ejecútelo solo mediante el wrapper, nunca con php cron/security_watch.php directamente. El wrapper encuentra todas las instalaciones del panel en el servidor y lanza la comprobación en nombre del propietario del sitio, con los mismos permisos de sudo que el panel usa desde la web. Una llamada directa desde el cron de root crearía el archivo de estado data/secwatch_state.json con propietario root y el botón «Comprobar ahora» dejaría de funcionar.
Si preparó el servidor con la instalación automática, la tarea ya está añadida: solo queda activar la supervisión en los ajustes.

33. Servidor web: peticiones y códigos de respuesta

La página «Servidor web» analiza el registro de accesos y muestra cuántas peticiones hubo, qué parte terminó en error y quién las hizo. ModSecurity responde a la pregunta «qué se ha bloqueado»; esta página, a la pregunta «qué llegó al sitio».

Qué se ve: número de peticiones, RPS, porcentaje de errores, un contador aparte de 5xx y el volumen servido; un gráfico de peticiones por horas; el reparto de códigos 2xx/3xx/4xx/5xx; direcciones frecuentes, IP frecuentes, errores frecuentes (código más ruta) y clientes (User-Agent). El periodo: 24 horas o 7 días.

La fuente es el registro de accesos en formato combined, por lo que sirven tanto Nginx como Apache y no hace falta ningún módulo adicional. El panel busca el registro entre los archivos estándar:

  • /var/log/nginx/access.log
  • /var/log/apache2/access.log
  • /var/log/apache2/other_vhosts_access.log
  • /var/log/httpd/access_log

Además de ellos, el panel revisa los archivos *access*.log y *requests*.log en /var/log/nginx/, /var/log/apache2/ y /var/log/httpd/. Así también encuentra el registro que un sitio escribe en su propio archivo con la directiva CustomLog, por ejemplo arciveo-monitor_requests.log, que crea el instalador de Arcivéo.

Solo se lee un registro: si Nginx está delante de Apache, sumar dos registros contaría cada petición dos veces. Se toma el primer registro de la lista anterior en el que se haya escrito durante el último día (los encontrados por patrón de nombre se prueban después, del más reciente al más antiguo); si en ninguno se ha escrito en un día, se toma el no vacío más reciente. Por eso, un registro vaciado por logrotate o abandonado por un servidor web detenido ya no oculta el que está en uso. Se lee el último fragmento del registro (3 MB) y el resultado se guarda en caché 5 minutos, así que en un sitio con mucho tráfico el periodo elegido puede no quedar cubierto por completo; la cobertura real aparece encima del gráfico.

«El panel no puede leer el registro del servidor web». La causa casi siempre son los permisos: los registros y su directorio pertenecen al grupo adm y el usuario web no está en él. El instalador de Arcivéo lo añade a ese grupo; tras una instalación manual, lo resuelve un pequeño wrapper de root al que se le permite una sola cosa, mostrar el registro (la página de ModSecurity funciona igual). El wrapper elige el registro con las mismas reglas que el panel. El comando aparece en la propia página; aquí está también. Péguelo entero: es un único comando:
sudo tee /usr/local/bin/monitor-weblog >/dev/null <<'EOF' && sudo chmod 755 /usr/local/bin/monitor-weblog && echo 'www-data ALL=(root) NOPASSWD: /usr/local/bin/monitor-weblog' | sudo tee /etc/sudoers.d/monitor-weblog >/dev/null && sudo visudo -c #!/bin/sh L="/var/log/nginx/access.log /var/log/apache2/access.log /var/log/apache2/other_vhosts_access.log /var/log/httpd/access_log $(ls -t /var/log/nginx/*access*.log /var/log/apache2/*access*.log /var/log/apache2/*requests*.log /var/log/httpd/*access_log 2>/dev/null)" for f in $L; do [ -r "$f" ] && [ -n "$(find "$f" -size +0 -mmin -1440 2>/dev/null)" ] && exec tail -c 3000000 "$f"; done for f in $(ls -t $L 2>/dev/null); do [ -r "$f" ] && [ -s "$f" ] && exec tail -c 3000000 "$f"; done EOF

El usuario web puede no ser www-data: compruebe con qué usuario se ejecuta el pool de PHP-FPM del sitio (ps -o user= -C php-fpm) y póngalo en la línea de sudoers. Si el nombre del registro no contiene ni access ni requests, añada su ruta al principio de la línea L=… dentro del wrapper.

«No hay registros para el periodo elegido»: el registro se ha leído, pero no se han encontrado líneas que encajen; o bien el sitio realmente no se abrió, o bien el registro acaba de rotarse (logrotate) y empieza de cero.

Intentos de exploits conocidos. El panel compara las peticiones del registro con las firmas de una veintena de exploits habituales: PHP-CGI, recorrido de directorios en Apache, PHPUnit, Log4Shell y fallos de routers, cámaras y pasarelas VPN. Para cada intento indica si puede funcionar en este servidor y se basa en hechos: las versiones instaladas de Apache y PHP, si hay Java y el código con el que el servidor respondió a la petición del archivo. Solo se marca en rojo lo que realmente puede funcionar.

Endurecimiento del servidor web. El panel solicita cada sitio del servidor a través de 127.0.0.1 y comprueba lo que recibe un visitante: si se ven las versiones del servidor web y de PHP, si el método TRACE está activado y si están las cabeceras de protección Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy y Content-Security-Policy. Los sitios salen de la dirección con la que se abre el panel y de los archivos de hosts virtuales, si el panel puede leerlos. El resultado se actualiza cada 10 minutos.

Si falta algo, bajo la tabla aparece un único comando. Crea un archivo aparte, zz-arciveo-hardening.conf, y recarga el servidor web solo si la prueba de configuración pasa; las cabeceras que el sitio ya envía no se duplican. CSP no se ofrece en una línea: la política se construye para cada sitio; de lo contrario, deja de cargar sus propios scripts, estilos y fuentes. Para deshacer el cambio en Apache:

sudo a2disconf zz-arciveo-hardening && sudo systemctl reload apache2
La firma de las páginas de error (ServerSignature) no se marca si la versión del servidor web ya está oculta: sin versión solo muestra el nombre del servidor y el del host.

34. Mapa de ataques (GeoIP)

La página «Mapa de ataques» determina el país por IP con el comando geoiplookup. Sin el paquete GeoIP no se detectan los países ni aparecen los puntos en el mapa:

sudo apt install geoip-bin geoip-database # Comprobación: geoiplookup 8.8.8.8
No se requiere sudo: la base /usr/share/GeoIP/GeoIP.dat la lee cualquiera y los resultados se almacenan en caché en tmp/geoip_cache.json. El propio mapa (Leaflet + teselas de OpenStreetMap) se carga en el navegador: se necesita internet en el equipo donde esté abierto el panel.

35. Exposición externa, actualizaciones y actualizaciones automáticas

Dos tarjetas integradas del panel que muestran no el «encendido/apagado» de una herramienta, sino la protección real del servidor. No requieren instalación y se leen localmente sin sudo.

Exposición externa: cuántos servicios escuchan en todas las interfaces (0.0.0.0/[::]) y son accesibles desde fuera. Resalta en rojo si hay bases de datos o cachés expuestas (MySQL, PostgreSQL, Redis, MongoDB, Memcached, Elasticsearch): es un agujero directo (−10 en la Puntuación de seguridad). Origen: ss -tuln.

Si la tarjeta está en rojo, cierre la base de datos al exterior: vincúlela a 127.0.0.1 (bind-address en la configuración de MySQL/PostgreSQL, bind 127.0.0.1 en Redis) o cierre el puerto en UFW.
«Puerto abierto» ≠ «accesible desde fuera». Un servicio que escucha en 127.0.0.1 (loopback) solo es visible para el propio servidor: no se puede llegar a él desde fuera, aunque el puerto esté «abierto». Por eso Postfix en el puerto 25, vinculado a loopback, es seguro: la configuración automática establece inet_interfaces = loopback-only (además de un smtpd_banner neutro, que resuelve la advertencia de Lynis MAIL-8818 sobre la revelación de la versión). La tarjeta «Exposición externa» cuenta como expuesto solo lo que escucha en 0.0.0.0/[::]; los servicios en loopback no se incluyen.
Lynis MAIL-8818 manualmente (si instaló el correo usted mismo): en /etc/postfix/main.cf defina smtpd_banner = $myhostname ESMTP (sin versión ni sistema operativo) e inet_interfaces = loopback-only, y luego sudo systemctl restart postfix.

Actualizaciones de seguridad: cuántos parches de seguridad esperan instalarse y si se requiere reiniciar tras actualizar el núcleo (−5 en la Puntuación de seguridad si hay parches). Origen: /usr/lib/update-notifier/apt-check, archivo /var/run/reboot-required. La lista detallada está en la página «Actualizaciones de seguridad».

# Instalar actualizaciones: sudo apt update && sudo apt upgrade # Comprobar qué escucha hacia fuera: ss -tuln | grep -E '0\.0\.0\.0|\[::\]'
La tarjeta de actualizaciones funciona en Ubuntu/Debian (update-notifier-common). Si apt-check no está presente, el monitor calcula los parches mediante apt-get -s upgrade.

Actualizaciones de seguridad automáticas (unattended-upgrades): en la página «Actualizaciones de seguridad» una tarjeta aparte muestra si la instalación automática de parches de seguridad está activada y cuándo se ejecutó por última vez. No se necesita sudo: el estado se lee mediante apt-config dump.

sudo apt install unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades # activar # Comprobar que está activado: apt-config dump | grep Unattended-Upgrade

Mantenimiento

36. Copias de seguridad

La copia de seguridad es el seguro principal: perder los datos es peor que cualquier intrusión. Se necesitan dos cosas: una copia del servidor/sitios y, aparte, una copia de la BD del panel (ahí están los usuarios, las claves WebAuthn, los ajustes y la licencia).

Opción A — HestiaCP: pestaña Backup del usuario → botón para crear la copia (o de forma programada en los ajustes del servidor). La copia incluye los sitios y sus BD.

Opción B — manual (cron): volcado de la BD + archivo del directorio data/ del panel:

# cron de root (sudo crontab -e) — copia diaria a las 2:30 (ponga sus propios nombres/rutas): 30 2 * * * mysqldump -u root MY_DB | gzip > /var/backups/monitor-db-$(date +\%F).sql.gz 40 2 * * * tar czf /var/backups/monitor-data-$(date +\%F).tar.gz -C /path/to/monitor data # Eliminar archivos con más de 14 días: 0 3 * * * find /var/backups -name 'monitor-*' -mtime +14 -delete
Una copia en el mismo servidor protege de errores, pero no de la pérdida del servidor. Copie los archivos en un almacenamiento externo (otro servidor, S3, rclone a la nube). Compruebe que la restauración funciona de verdad.

37. Actualización y migración del panel

Actualización a una nueva versión. Primero haga una copia de seguridad. Luego vuelva a subir los archivos de código, conservando sus datos:

  • sobrescribir (código): public/, includes/, assets/, cron/, database/, así como los .htaccess raíz (el controlador frontal: el enrutamiento no debe quedar de la versión anterior), manifest.json, sw.js;
  • no tocar: config.php (datos de la BD), data/ (informes), logs/, tmp/ (sesiones y caché).
# Tras la subida, vaciar la caché de PHP (si opcache está activado): sudo systemctl reload php*-fpm
FileZilla muestra SSH_FX_PERMISSION_DENIED — Permission denied. Los archivos del panel pertenecen a www-data (así se establecieron durante la instalación), mientras que el cliente SFTP se conecta con su propio usuario, que no tiene permiso de escritura. Dar a www-data todo el panel «para que funcione» es precisamente lo que provoca este error; a continuación, tres formas, cualquiera resuelve el problema.
# Variante A (recomendada): separar los propietarios: el código es suyo, las carpetas de trabajo del servidor web. # El servidor web no obtiene ningún permiso de escritura sobre el CÓDIGO del panel: sudo chown -R deploy:www-data /path/to/monitor sudo chown -R www-data:www-data /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs sudo find /path/to/monitor -type d -exec chmod 755 {} \; sudo find /path/to/monitor -type f -exec chmod 644 {} \; sudo chmod 750 /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs sudo chmod 640 /path/to/monitor/config.php # Variante B: ACL sobre los propietarios actuales (no cambiamos nada): sudo apt install -y acl sudo setfacl -R -m u:deploy:rwX /path/to/monitor sudo setfacl -R -d -m u:deploy:rwX /path/to/monitor # Variante C: mediante el grupo www-data. Más sencilla, pero el permiso de escritura # sobre los archivos del panel lo obtiene también el servidor web (código sustituible): sudo usermod -aG www-data deploy sudo find /path/to/monitor -type d -exec chmod 2775 {} \; sudo find /path/to/monitor -type f -exec chmod 664 {} \; sudo chmod 640 /path/to/monitor/config.php sudo chmod 2750 /path/to/monitor/data /path/to/monitor/tmp /path/to/monitor/logs
Por qué la variante A es segura. El panel escribe solo en tres directorios — data/ (informes), tmp/ (sesiones y caché), logs/; estos permanecen a nombre de www-data. El resto es código, y el servidor web solo lo necesita para lectura, que le concede el grupo www-data con permisos 644. Ventaja adicional: ante una vulnerabilidad en PHP, los archivos del panel ya no se pueden reescribir. En los paneles de hosting (HestiaCP y similares) la variante A no es necesaria: allí los archivos del sitio ya pertenecen a la cuenta con la que se conecta por SFTP, y el servidor web los lee mediante el grupo.
La trampa de la variante B: cualquier chmod posterior sobre los archivos restablece la máscara de la ACL, y el acceso desaparece silenciosamente. Si tras «poner orden en los permisos» la subida vuelve a toparse con Permission denied, repita ambos comandos setfacl.
El bit 2 de la variante C es setgid: los archivos subidos por SFTP permanecen en el grupo www-data; de lo contrario el panel no podrá sobrescribirlos. Tras la variante C, vuelva a conectarse en FileZilla: el nuevo grupo solo se aplica en un inicio de sesión nuevo. Comprobación: id deploy (debe aparecer el grupo www-data) y ls -ld /path/to/monitor (drwxrwsr-x: la letra s significa que setgid está activo).

Migración a otro servidor:

  1. En el nuevo servidor, ponga en marcha el sitio + HTTPS (consulte la página de instalación manual).
  2. Copie todos los archivos del panel junto con config.php, data/.
  3. Migre la BD: mysqldump en el antiguo → importar en el nuevo; corrija los datos de la BD en config.php.
  4. Repita en el nuevo servidor: sudoers, pertenencia al grupo adm, tareas de cron.
  5. La licencia está vinculada al dominio: si el dominio es el mismo, la clave seguirá funcionando.

38. Un segundo sitio en este servidor (otro dominio)

El panel ya está funcionando y en el mismo servidor hace falta un sitio más, con su propio dominio junto al monitor. Tras la configuración automática el servidor es un Apache + PHP-FPM corriente, así que el sitio necesita tres cosas: su directorio, su vhost y su certificado. El panel no se toca: vigila el servidor entero, no hace falta una segunda instancia para el segundo dominio.

Esta sección es para un servidor sin panel de hosting. Si tiene instalado HestiaCP o un panel similar, añada el dominio allí y no a mano. Y si prevé alojar muchos sitios (además con correo), instale el panel antes de la configuración automática: el script lo detecta y pasa al modo aditivo, mientras que un panel instalado después reconstruye la configuración de Apache, del cortafuegos y del correo.
Qué sustituir en los comandos. site2.example.com es la dirección del nuevo sitio. Puede ser tanto un dominio propio (mycompany.com) como un subdominio (shop.mycompany.com): para Apache no hay diferencia, solo cambian el registro DNS (paso 1) y la emisión del certificado (paso 5). site2 es simplemente un nombre corto del sitio: así se llaman el directorio /var/www/site2 y el archivo de configuración site2.conf. No guarda relación con el dominio, Apache toma la dirección únicamente de ServerName; el nombre puede ser cualquiera; por ejemplo, para shop.mycompany.com resulta cómodo usar shop: directorio /var/www/shop, configuración shop.conf.

1. DNS. En el panel de DNS de su registrador cree un registro A hacia la IP de este servidor:

# Subdominio (shop.mycompany.com): Tipo: A Nombre: shop Valor: 203.0.113.10 TTL: 3600 # Dominio propio (mycompany.com): dos registros, para que también funcione la dirección con www: Tipo: A Nombre: @ Valor: 203.0.113.10 TTL: 3600 Tipo: A Nombre: www Valor: 203.0.113.10 TTL: 3600

Al cabo de unos minutos compruébelo: dig +short site2.example.com debe devolver la IP del servidor.

2. Directorio del sitio.

sudo mkdir -p /var/www/site2 sudo chown -R www-data:www-data /var/www/site2

3. El vhost. El bloque se pega en la terminal entero: la ruta al socket de PHP-FPM se detecta sola:

PHPSOCK=$(ls -1 /run/php/php*-fpm.sock 2>/dev/null | head -1) # detección automática del socket de PHP-FPM sudo tee /etc/apache2/sites-available/site2.conf > /dev/null <<'EOF' <VirtualHost *:80> ServerName site2.example.com # Dominio propio: quite la # de la línea de abajo para que el sitio abra también con www. # Para un subdominio la línea no hace falta. #ServerAlias www.site2.example.com DocumentRoot /var/www/site2 <Directory /var/www/site2> Options -Indexes +FollowSymLinks AllowOverride All Require all granted </Directory> <FilesMatch \.php$> SetHandler "proxy:unix:__PHPSOCK__|fcgi://localhost" </FilesMatch> ErrorLog ${APACHE_LOG_DIR}/site2_error.log CustomLog ${APACHE_LOG_DIR}/site2_access.log combined </VirtualHost> EOF sudo sed -i "s#__PHPSOCK__#${PHPSOCK}#" /etc/apache2/sites-available/site2.conf

4. Active el sitio y compruebe la configuración antes de recargar:

sudo a2ensite site2.conf sudo apache2ctl configtest # se espera: Syntax OK sudo systemctl reload apache2

5. SSL. certbot ya lo instaló la configuración automática; para entonces el DNS debe apuntar al servidor:

# Subdominio: una sola dirección: sudo certbot --apache -d site2.example.com # Dominio propio: con www desde el principio (ambos registros A del paso 1 deben apuntar al # servidor y www debe estar en ServerAlias; si no, certbot no podrá verificar la segunda dirección): sudo certbot --apache -d site2.example.com -d www.site2.example.com

El tercer dominio y los siguientes se añaden igual: repita los pasos 1–5 cambiando el dominio y el directorio.

Quién responde a las peticiones «ajenas». Si Host no coincide con ningún ServerName (acceso por IP o un dominio ajeno apuntado a su servidor), Apache sirve el primer sitio habilitado por orden alfabético de /etc/apache2/sites-enabled/. La configuración automática desactiva el 000-default estándar, así que ese papel «de guardia» le queda al vhost del panel (arciveo-monitor.conf); esto no afecta a los sitios abiertos por su propio nombre. Si quiere que por IP abra otro sitio, nombre su archivo de modo que vaya antes alfabéticamente (por ejemplo 000-site2.conf).
La protección es de todo el servidor: UFW, Fail2ban, CrowdSec, ModSecurity y Suricata ya están funcionando y cubren el nuevo sitio automáticamente, no hay que configurar nada aparte. Los puertos 80/443 también están abiertos. El sitio tiene sus propios registros: site2_error.log y site2_access.log en /var/log/apache2/.
No coloque el directorio del nuevo sitio dentro del directorio del panel: la raíz web del panel se rige por su propio .htaccess (controlador frontal) y un sitio anidado devolvería algo distinto de lo que espera. Mantenga los sitios uno al lado del otro: /var/www/monitor, /var/www/site2.

39. Recuperación de acceso (clave perdida, contraseña, bloqueo de IP)

Si no puede iniciar sesión, todo se soluciona directamente en la BD desde el servidor. Abra la BD (el nombre está en config.php):

sudo mysql MY_DB

Clave WebAuthn perdida (no supera el segundo factor): desactive el 2FA, inicie sesión con la contraseña y registre una nueva clave:

UPDATE users SET webauthn_enabled = 0;

Olvidó la contraseña: establezca un nuevo hash (genérelo en el servidor e insértelo):

# Generar el hash de la nueva contraseña: php -r "echo password_hash('NEW_PASSWORD', PASSWORD_BCRYPT), \"\n\";" # En la BD (inserte el hash obtenido): # UPDATE users SET password = '$2y$10$...' WHERE username = 'admin';

Se bloqueó a sí mismo con el filtro de IP: desactive la restricción:

UPDATE settings SET value = '0' WHERE name = 'ip_restriction_enabled';
Siempre tiene acceso a la BD: sudo mysql en el servidor, o bien phpMyAdmin / la sección de BD en el panel del hosting. Tras la recuperación, vuelva a activar WebAuthn y el filtro de IP.

40. Todas las tareas de cron en un solo lugar

El resumen de tareas está en el cron root del servidor (se añaden mediante sudo crontab -e). Deje solo las líneas de las herramientas que use; ajuste las rutas a su servidor.

# Cron del servidor del monitor (root) — añádalo con: sudo crontab -e # 01:30 — análisis de ClamAV en rutas peligrosas (web, home, temp) → tarjetas «Archivos analizados» y «Último análisis» 30 1 * * * /usr/local/bin/clamav-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 02:00 — comprobación de integridad de archivos AIDE (requiere --config explícito) 0 2 * * * /usr/bin/aide --config /etc/aide/aide.conf --check > /var/log/aide/aide.log 2>&1 # al arrancar — restaurar permisos de /var/lib/aide (el archivo tmpfiles del paquete # aide-common.conf los deja en 0700 y el panel deja de ver la base) @reboot chmod 755 /var/lib/aide # 03:00 — auditoría de seguridad de Lynis 0 3 * * * /usr/local/bin/lynis-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:00 — actualización de la lista de bloqueo ipsum (level 1) 0 4 * * * /usr/local/bin/load-ipsum.sh >> /path/to/monitor/logs/cron.log 2>&1 # el conjunto ipsum al arrancar lo levanta el servicio ipsum-load.service (ANTES del cortafuegos, de lo contrario # UFW no verá el conjunto en before.rules) — no es cron. Aquí solo el refresco diario de arriba. # 06:00 — informe de Logwatch 0 6 * * * /usr/local/bin/logwatch_daily.sh >> /path/to/monitor/logs/cron.log 2>&1 # cada 30 min — comprobación SMART de discos */30 * * * * /usr/local/bin/smart-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 04:30 — integridad de paquetes debsums 30 4 * * * /usr/local/bin/debsums-scan.sh >> /path/to/monitor/logs/cron.log 2>&1 # 08:00 — informe programado por Email y Telegram 0 8 * * * /usr/local/bin/daily-report-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # cada hora — actualización de las listas de paquetes (para la tarjeta «Actualizaciones de seguridad») 0 * * * * /usr/bin/apt-get update -qq >/dev/null 2>&1 # cada 5 min — instantánea de recursos (CPU/RAM/red/disco) para la página «Rendimiento» */5 * * * * /usr/local/bin/collect-metrics-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # cada 5 min, desplazado respecto a la instantánea anterior — sensor de eventos de # seguridad: nuevos baneos, alertas de IDS, protección detenida, puerto abierto. # Mientras la supervisión esté desactivada en los ajustes, la tarea no hace nada. 2-57/5 * * * * /usr/local/bin/security-watch-all.sh >> /path/to/monitor/logs/cron.log 2>&1 # cada 15 min — resumen del servidor al área personal, para ver varios servidores # en una sola lista. Mientras el resumen esté desactivado, la tarea no hace nada. 3,18,33,48 * * * * /usr/local/bin/fleet-push-all.sh >> /path/to/monitor/logs/cron.log 2>&1
Los detalles de cada uno están en las secciones correspondientes. Las tareas de copia de seguridad (sección anterior) se añaden a este mismo cron. Tras los cambios, verifique: sudo crontab -l y que el servicio cron esté activo.
La hora de cron = la zona horaria del servidor, no la TIMEZONE de config.php. La constante TIMEZONE solo afecta a PHP (cómo muestra el panel las fechas), pero el demonio cron ejecuta las tareas según la hora del sistema del SO. Si la zona del servidor no coincide con la suya, el informe de las «08:00» llegará a otra hora. Ejemplo: el servidor está en otra zona (Europe/London, UTC+1) y usted en Madrid (UTC+2) → el informe de las «08:00» llegará a las 09:00 según su hora. Compruébelo y, si hace falta, ajuste la zona del sistema a la suya:
# Comprobar la zona actual del servidor: timedatectl # Establecer su zona (ejemplo) y reiniciar cron: sudo timedatectl set-timezone Europe/Madrid sudo systemctl restart cron
Después de esto, la línea 0 8 * * * se ejecutará a las 08:00 hora local. De lo contrario, habría que desplazar el propio cron, pero al cambiar entre horario de invierno/verano el desplazamiento volvería a descuadrarse, por eso es más correcto configurar la zona del sistema.
Scripts envoltorio listos para usar. Sus copias de trabajo y un crontab de ejemplo (crontab.txt) están en la carpeta system/ junto al proyecto, fuera de public_html. Esto no es parte del sitio — no hace falta subirlos a la raíz web; colóquelos en el servidor en las rutas del sistema (como en el crontab de arriba):
  • lynis-scan.sh → /usr/local/bin/ (chmod +x) — ejecuta lynis audit system, durante el análisis pone la marca /tmp/lynis-running y copia lynis-report.dat a data/lynis/ del panel;
  • logwatch_daily.sh → /usr/local/bin/ (chmod +x) — genera el informe diario de Logwatch (sshd, fail2ban, sudo, postfix) en data/logwatch/;
  • smart-scan.sh → /usr/local/bin/ (chmod +x) — captura el estado de los discos (smartctl) en data/disk/;
  • debsums-scan.sh → /usr/local/bin/ (chmod +x) — comprueba la integridad de los paquetes (debsums) en data/debsums/;
  • clamav-scan.sh → /usr/local/bin/ (chmod +x) — análisis antivirus de ClamAV en rutas peligrosas (web, home, temp); escribe el resumen en /var/log/clamav/scan.log, de donde lo lee la página de ClamAV (línea 01:30 del crontab de arriba);
  • load-ipsum.sh → /usr/local/bin/ (chmod +x) — actualiza el conjunto ipset ipsum (level 1) en el sitio, sin romper las reglas activas del cortafuegos (línea 04:00 del crontab de arriba);
  • daily-report-all.sh → /usr/local/bin/ (chmod +x) — ejecuta el informe cron/daily_report.php del panel (línea 08:00 del crontab de arriba);
  • daily_report.php — ya viene incluido en el panel (cron/daily_report.php), se ejecuta mediante daily-report-all.sh, no hace falta instalarlo aparte;
  • collect-metrics-all.sh → /usr/local/bin/ (chmod +x) — ejecuta cron/collect_metrics.php del panel (página «Rendimiento», línea */5 del crontab de arriba); collect_metrics.php ya viene incluido en el panel, no hace falta instalarlo aparte;
  • security-watch-all.sh → /usr/local/bin/ (chmod +x) — ejecuta cron/security_watch.php del panel en nombre del propietario del sitio (página «Eventos de seguridad», línea 2-57/5 del crontab anterior); security_watch.php ya forma parte del panel y no hay que instalarlo aparte;
  • fleet-push-all.sh → /usr/local/bin/ (chmod +x) — envía el resumen del servidor al área personal mediante cron/fleet_push.php del panel (línea 3,18,33,48 anterior);
  • crontab.txt (system/cron/) — ejemplo de tareas; añada las líneas necesarias mediante sudo crontab -e.
La ruta al script en el crontab debe coincidir con el lugar donde lo colocó.
Cómo colocar el script en /usr/local/bin/. No se puede escribir allí directamente desde FileZilla — el directorio pertenece a root y el cliente SFTP recibirá SSH_FX_PERMISSION_DENIED. El orden es este: primero suba el archivo a /tmp (donde todos pueden escribir), luego muévalo a su sitio con un solo comando:
# en FileZilla: en el campo «Sitio remoto» escriba /tmp y suba ahí el script, # luego por SSH (install pone de una vez el propietario y los permisos, no hacen falta chown/chmod): sudo install -o root -g root -m 755 /tmp/lynis-scan.sh /usr/local/bin/lynis-scan.sh rm -f /tmp/lynis-scan.sh # comprobación: el archivo está en su sitio, permisos rwxr-xr-x, sintaxis intacta bash -n /usr/local/bin/lynis-scan.sh && ls -l /usr/local/bin/lynis-scan.sh
No confunda los directorios: hace falta /tmp en la raíz del servidor — no /var/tmp ni tmp/ dentro del propio panel (este último pertenece a www-data y está cerrado a su usuario). En el árbol de FileZilla, /tmp es una rama de nivel superior, junto a var, no dentro de ella.
¿Instaló el servidor con la configuración automática? Estos envoltorios y sus tareas de cron ya están instalados por el script (en /usr/local/bin/, registro — /var/log/arciveo-cron.log) — no hace falta hacer nada manualmente.
Dónde buscan los scripts el panel. Los envoltorios son neutrales respecto al dominio: localizan las instalaciones del panel recorriendo /home/*/web/*/public_html y /var/www/*, y colocan los informes en su data/. Si el panel está en otra ruta, añádala a la línea for app in … dentro de los scripts, de lo contrario los informes de Lynis/SMART/debsums/Logwatch no llegarán al panel.
cron.log y permisos de acceso. El archivo logs/cron.log lo crea primero el cron root — pertenecerá a root, y la pestaña «Registro de cron» del panel no podrá ni leerlo ni vaciarlo. Cree el archivo de antemano con el usuario web (propietario del directorio del sitio; en HestiaCP es la cuenta, p. ej., admin) — así el cron root solo añadirá contenido sin cambiar el propietario:
# crear de antemano con el usuario web (antes de añadir las líneas de cron): sudo -u OWNER touch /path/to/monitor/logs/cron.log # si cron.log ya lo creó el cron root — cederlo al usuario web: sudo chown OWNER:OWNER /path/to/monitor/logs/cron.log sudo chmod 644 /path/to/monitor/logs/cron.log
Para averiguar el propietario del directorio: stat -c %U /path/to/monitor.
Gestión desde el panel. En la sección «Sistema» hay una página «Crontab» — puede ver y añadir tareas sin SSH. El panel edita solo las tareas añadidas a través de él mismo (un bloque aparte en el crontab root, marcado con comentarios de servicio); todo lo que ya está en el crontab (la lista de arriba) se muestra ahí en modo de solo lectura en la lista «Otras tareas del servidor» con el botón «Copiar al editor» — este solo traslada la programación/comando al formulario de adición, sin tocar la línea original. Para «pasar» una tarea existente a la gestión del panel, cópiela al editor, guárdela y luego elimine la línea antigua manualmente (sudo crontab -e), de lo contrario se ejecutaría dos veces.
Configuración única en el servidor. La página necesita un script envoltorio con privilegios — no un sudo crontab pelado (eso sería una escalada directa a root por parte de cualquiera que obtenga acceso a la sesión del panel), sino un script acotado con dos comandos (list/set) que solo toca su propio bloque entre los comentarios de servicio. Instálelo una sola vez:
sudo install -m 0755 -o root -g root /dev/stdin /usr/local/sbin/arciveo-cron <<'ARCIVEO_CRON_EOF' #!/bin/bash set -euo pipefail BEGIN='# >>> ARCIVEO-CRON-MANAGED (edited from the panel; do not edit by hand) >>>' END='# <<< ARCIVEO-CRON-MANAGED <<<' cmd=${1:-} case "$cmd" in list) [ "$#" -eq 1 ] || { echo "usage: ${0##*/} list" >&2; exit 2; } crontab -l -u root 2>/dev/null || true ;; set) [ "$#" -eq 1 ] || { echo "usage: ${0##*/} set (body on stdin)" >&2; exit 2; } body=$(cat) if grep -qF "$BEGIN" <<<"$body" || grep -qF "$END" <<<"$body"; then echo "invalid body: markers not allowed" >&2; exit 2 fi current=$(crontab -l -u root 2>/dev/null || true) tmp=$(mktemp); trap 'rm -f "$tmp"' EXIT { if grep -qF "$BEGIN" <<<"$current"; then awk -v b="$BEGIN" '{print} $0==b{exit}' <<<"$current" else [ -n "$current" ] && printf '%s\n' "$current" echo "$BEGIN" fi printf '%s\n' "$body" echo "$END" if grep -qF "$END" <<<"$current"; then awk -v e="$END" 'f{print} $0==e{f=1}' <<<"$current" fi } > "$tmp" crontab -u root "$tmp" ;; *) echo "usage: ${0##*/} <list|set>" >&2; exit 2 ;; esac ARCIVEO_CRON_EOF echo "www-data ALL=(ALL) NOPASSWD: /usr/local/sbin/arciveo-cron" | sudo tee -a /etc/sudoers.d/monitor sudo visudo -c
El usuario web puede diferir de www-data — compruebe con qué usuario funciona el pool de PHP-FPM del sitio (ps -o user= -C php-fpm) y sustitúyalo en la línea de sudoers.
El archivo nuevo se subió con un propietario incorrecto — la página responde «Access denied.». Si el archivo public/crontab_monitor.php se subió por FTP/SFTP con un usuario del sistema distinto (por ejemplo, root) al del resto de los archivos del sitio, el servidor web no podrá leerlo. Compare el propietario y los permisos con el archivo vecino y hágalos coincidir:
ls -la public/crontab_monitor.php public/ssl_monitor.php sudo chown OWNER:OWNER public/crontab_monitor.php sudo chmod 644 public/crontab_monitor.php

Diagnóstico

41. La herramienta está instalada, pero muestra «No instalada»

El monitor detecta la presencia de las herramientas mediante dpkg-query, la base de paquetes de APT. Si la herramienta no se instaló con apt (manualmente, desde snap o desde el código fuente), dpkg no la ve.

# Comprobar con dpkg: dpkg -l fail2ban | grep '^ii' dpkg -l auditd | grep '^ii' # Buscar la ruta del binario: which ufw fail2ban-client auditctl # Prueba de sudo como www-data: sudo -u www-data sudo fail2ban-client status sudo -u www-data sudo ufw status verbose

42. Solución de problemas (500, sin datos)

Error 500: revise los registros de PHP, nginx y del propio monitor:

tail -50 /var/log/nginx/error.log tail -50 /var/log/php*-fpm.log # Registros del monitor: tail -50 logs/monitor_$(date +%Y-%m-%d).log # Permisos de las carpetas: ls -la data/ tmp/ logs/
¿El panel está en un panel de hosting (HestiaCP, ISPmanager, cPanel)? Allí PHP no se ejecuta bajo www-data, sino bajo la cuenta del usuario (por ejemplo admin, propietario del directorio del sitio). Todas las reglas sudo y la pertenencia a grupos (adm, systemd-journal) deben asignarse a ese usuario; de lo contrario, los módulos mostrarán «Inactivo / 0» aunque los servicios estén funcionando. Para averiguar el usuario real de PHP: ps -o user= -C php-fpm | sort -u o el propietario del directorio del sitio stat -c '%U' /path/to/monitor. A continuación, en todos los comandos de abajo, sustitúyalo por www-data. La instalación automática detecta por sí sola el usuario web y le asigna los sudoers.

Los datos no se muestran: casi siempre son permisos sudo sin definir. Compruebe el comando concreto en nombre del usuario web (sustituya www-data por el suyo). El indicador -n = sin contraseña, igual que PHP; si pide contraseña, es que no hay regla en sudoers:

sudo -u www-data sudo -n fail2ban-client status sudo -u www-data sudo -n ufw status verbose sudo -u www-data sudo -n ipset list -t ipsum sudo -u www-data sudo -n /usr/sbin/ausearch -m USER_LOGIN -ts today sudo -u www-data sudo -n /usr/sbin/aa-status sudo -u www-data sudo -n /usr/sbin/psad --Status sudo -u www-data journalctl -u falco --no-pager -n 5
El módulo muestra «Inactivo» / «0» aunque la herramienta funciona (por ejemplo, sudo aa-status en el terminal muestra los perfiles, pero la página «AppArmor» indica «Inactivo»). Motivo: el usuario web no tiene permiso sudo para el comando de ese módulo. Compruébelo en la lista de arriba: si pide contraseña, añada la línea que falta a /etc/sudoers.d/monitor («Configuración de sudo»). Comandos «nuevos» habituales: /usr/sbin/aa-status (MAC), /usr/sbin/psad --Status (PSAD).
Si una página concreta (Falco, ModSecurity, Auditd, puertos abiertos de UFW) está vacía, contrástela con la lista de la sección sobre sudo: probablemente no esté permitido apache2ctl, ausearch, aa-status o ss, o el usuario web no esté en los grupos adm/systemd-journal (de ahí se leen los registros de fail2ban/auth/modsec y journalctl: Falco y eventos del kernel).

43. La página está vacía aunque hay datos en el servidor

Síntoma: en el servidor hay datos (visibles por shell), pero la página muestra «no hay datos» o un estado incorrecto — por ejemplo AIDE indica «No inicializada» aunque la base ya está creada.

La causa es open_basedir: muchos paneles y alojamientos limitan el grupo de PHP-FPM al directorio del dominio, por lo que las funciones de PHP file_exists(), file_get_contents(), filemtime() quedan bloqueadas para las rutas del sistema (/var/lib/aide, /var/log, /proc…). El monitor lo evita leyendo esas rutas con comandos del sistema estándar (cat, test, stat).

# ¿Se ve el archivo por shell (así lo lee el monitor)?: sudo -u www-data bash -lc 'test -e /var/lib/aide/aide.db && echo VISIBLE || echo NO' # Valor actual de open_basedir para el grupo del dominio: grep -ri open_basedir /etc/php/*/fpm/pool.d/ 2>/dev/null
Si el shell «ve» el archivo (VISIBLE) pero la página no — se trata de open_basedir. La solución correcta es leer con comandos del sistema (ya implementado para AIDE y el Monitor de red). No es necesario ampliar open_basedir a /var, /proc y resulta menos seguro.

44. La página SSL no funciona

El monitor verifica los certificados conectándose a los dominios directamente por el puerto 443. Si el dominio no es accesible desde el propio servidor o el puerto está cerrado por el cortafuegos, la comprobación fallará.

# Verificar el certificado manualmente: echo | openssl s_client -connect monitor.example.com:443 2>/dev/null \ | openssl x509 -noout -dates # Comprobar la disponibilidad: curl -I https://monitor.example.com
El monitor obtiene los dominios automáticamente de las configuraciones de Nginx (/etc/nginx/sites-enabled/, /etc/nginx/conf.d/) y Apache (/etc/apache2/sites-enabled/) más el host actual de HTTP_HOST.
Detección automática de subdominios. Los subdominios se detectan automáticamente a partir de los registros públicos de Certificate Transparency y se verifican por red, incluso si están alojados en otros servidores. No es necesario añadir nada manualmente.

45. Solo se ve una BD de varias

El monitor se conecta a MySQL con un usuario de config.php que solo tiene acceso a su propia base. MySQL muestra en information_schema únicamente las bases con privilegios, por eso las demás no se ven.

Para que el monitor vea todas las BD, conceda a este usuario permiso solo de lectura (una vez como root; sustituya el nombre de usuario de config.php):

sudo mysql -u root GRANT SELECT, PROCESS, SHOW DATABASES ON *.* TO 'DB_USER'@'localhost'; FLUSH PRIVILEGES; EXIT;
SELECT ON *.* otorga solo permiso de lectura: no permite modificar, eliminar ni crear nada, es seguro para la monitorización.
Sin este GRANT el panel solo ve su propia base; no es un error, sino una limitación de permisos. El panel no usa ningún sudo mysql: la lista de bases se obtiene mediante su propia conexión PDO.

46. PostgreSQL no aparece en la página «Base de datos»

PostgreSQL requiere acceso a nivel del usuario postgres, del que el usuario web del panel carece. Abrir desde PHP un sudo psql amplio no es seguro; en su lugar, el panel invoca un envoltorio restringido sin parámetros que solo imprime la versión, el número de conexiones y la lista de bases con sus tamaños. Créelo:

sudo tee /usr/local/bin/monitor-pgstat >/dev/null <<'EOF' #!/bin/sh # Arciveo Security Monitor - read-only PostgreSQL version, connections and per-database size sudo -u postgres psql -tAc "SELECT version();" | grep -oE 'PostgreSQL [0-9.]+' echo "---" sudo -u postgres psql -tAc "SELECT count(*) FROM pg_stat_activity;" echo "---" sudo -u postgres psql -tAc "SELECT datname || '|' || pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datistemplate = false;" EOF sudo chown root:root /usr/local/bin/monitor-pgstat sudo chmod 755 /usr/local/bin/monitor-pgstat # en /etc/sudoers.d/monitor (usuario = aquel con el que se ejecuta PHP-FPM): # www-data ALL=(ALL) NOPASSWD: /usr/local/bin/monitor-pgstat
Si no utiliza PostgreSQL, elimine la línea monitor-pgstat de sudoers (paso 13 de la instalación manual) y no cree el script: la tarjeta de PostgreSQL simplemente quedará inactiva.

47. Se activó una alerta: qué hacer

El panel muestra qué ocurre; a continuación, qué hacer en situaciones típicas. Principio general: no entrar en pánico, contrastar con la actividad legítima (sus acciones, actualizaciones, copias de seguridad) y reaccionar según la gravedad.

  • Mapa de ataques / muchos baneos de fail2ban: es normal en cualquier servidor conectado a internet (los bots prueban SSH/web sin parar). Lo importante es que los baneos se activen. Asegúrese de que el acceso por SSH sea solo con clave (contraseña desactivada) y de que su IP esté en ignoreip.
  • ModSecurity bloqueó peticiones: el WAF repele ataques al sitio, es su función. Si bloquea su tráfico legítimo (falso positivo), busque el rule id en los detalles y añada una excepción a la configuración de CRS.
  • AIDE: archivos modificados: contraste la lista con lo que usted hizo (actualizar paquetes, editar configuraciones es normal). Los cambios en binarios del sistema que usted no tocó son motivo de alerta. Tras cambios legítimos, actualice la base de AIDE.
  • debsums: binarios/bibliotecas modificados (fuera de /etc, fuera de /usr/share): posible sustitución. Verifique el paquete: debsums PACKAGE_NAME y, en caso de duda, reinstálelo (apt install --reinstall).
  • ClamAV / maldet: amenaza detectada: revise el archivo en cuarentena, no lo abra. Si es un web shell en el directorio del sitio, aísle el servidor y busque el punto de entrada (plugin vulnerable, fuga de accesos).
  • Falco: eventos críticos (lanzamiento de shell en un contenedor, acceso a archivos sensibles): analice el evento: de quién es el proceso, qué lo lanzó. A menudo es actividad legítima de administración.
  • Exposición externa: SGBD/caché en rojo: ciérrelos de inmediato: vincule el servicio a 127.0.0.1 o cierre el puerto en UFW. Es un agujero real.
  • SSL caduca / caducado: renueve el certificado (Let's Encrypt se renueva solo; si no, revise certbot renew o los ajustes en el panel).
  • Actualizaciones de seguridad pendientes: instálelas: sudo apt update && sudo apt upgrade; tras actualizar el kernel, reinicie el servidor.
Indicios de una intrusión real (procesos/usuarios desconocidos, binarios modificados, spam saliente, tareas cron desconocidas): desconecte el servidor del acceso externo, haga una copia de seguridad para análisis y, si los datos son críticos, levante un servidor limpio a partir de una copia de seguridad de confianza: eliminar un rootkit de forma fiable es difícil.