
🌐 También en: English · Deutsch · Français
Ya está publicada la versión v3.6.0 del código Ansible que hay detrás de este blog y de dos servidores Nextcloud. Durante un tiempo será la última con funciones nuevas: a partir de ahora la prioridad es el mantenimiento. El hilo conductor de los últimos días: casi todos los fallos de las semanas anteriores pasaron desapercibidos porque las comprobaciones preguntaban «¿está configurado y en marcha?» en lugar de «¿funciona de verdad?». Esta versión cambia eso.
📦 La descarga que se cortaba en 1 GB
El fallo más reciente apareció esta mañana, y no gracias a una prueba: un correo de alerta avisaba de que un contenedor estaba limitado de CPU más de la mitad del tiempo. Era el nginx que está delante de PHP en uno de los servidores Nextcloud, un componente que normalmente apenas hace nada.
Detrás había un usuario que intentaba descargar un archivo de 13 GB. Cada intento se cortaba en torno a 1,08 GB. Coincidieron dos valores por defecto: el ingress guarda la respuesta en un fichero temporal de 1 GB como máximo y después deja de leer del pod hasta que el cliente se pone al día. El nginx del pod espera 60 segundos sin avance y cierra la conexión. El cliente recibe una descarga rota y el ingress registra «upstream prematurely closed». Las marcas de tiempo coincidían en tres intentos seguidos: 1 GB en pocos segundos y después exactamente 67 segundos de silencio.
La corrección es una sola anotación, con la que el ingress entrega los datos al ritmo del cliente en lugar de llenar un fichero temporal:
nginx.org/proxy-max-temp-file-size: "0"La alerta de CPU fue solo un efecto secundario: copiar 1 GB en pocos segundos llevó brevemente al límite a un contenedor con 0,5 núcleos.
Lo que queda: dos valores por defecto razonables pueden producir juntos un fallo. Merece la pena seguir una alerta sobre un síntoma, aunque el síntoma parezca inofensivo.
🧪 Pruebas que comprueban el efecto
Los roles de verificación comprueban ahora lo que un componente debe conseguir, no solo si existe:
- Cadena de bloqueo: para cada jail de fail2ban se bloquea una dirección de documentación (TEST-NET), que debe aparecer en el conjunto correcto del cortafuegos, se desbloquea y debe desaparecer. Dos jails bloquean la misma dirección y una la libera: el otro bloqueo debe mantenerse.
- Filtros: para cada jail, una línea de log preparada en el formato actual pasa por
fail2ban-regex. Un cambio de formato ya no puede dejar ciega una jail sin que nadie lo note. - Presencia de datos: las métricas clave deben existir y ninguna regla de alerta de Grafana puede quedarse en error o sin datos de forma permanente.
- Dominios SELinux: cada servicio del host debe ejecutarse en su propio dominio. «active (running)» en el dominio equivocado cuenta como fallo.
Lo que queda: «configurado y en marcha» es un estado. «¿Funciona?» es una prueba con una entrada y un resultado esperado.
💓 Señales de vida y la ruta del correo
Algunos fallos son silenciosos: una tarea nocturna que deja de ejecutarse nunca envía un error. Cada tarea periódica (AIDE, rkhunter, ClamAV, actualizaciones automáticas, avisos de inicio de sesión y de bloqueo) escribe ahora una marca de tiempo como métrica al terminar. Si una tarea se retrasa, Grafana avisa.
Eso solo sirve si los correos de alerta llegan. Por eso una regla se dispara a propósito cada semana; si ese correo no llega, la ruta del correo está rota. Además, Monit vigila el log de envío por si hay entregas fallidas.
Lo que queda: el silencio no es una señal. Una tarea tiene que avisar de que se ha ejecutado, y las alertas tienen que demostrar que todavía te llegan.
📉 Un presupuesto de errores para los logs
Dos exporters escribieron errores durante días sin que ninguna métrica lo mostrara. Una nueva alerta de Loki cuenta las líneas de error y de aviso por servicio y salta a partir de 100 por hora. El ruido conocido e inofensivo está en una lista de excepciones con su motivo.
Lo que queda: un servicio que se queja sin parar es un hallazgo, aunque todas sus métricas parezcan correctas.
🔍 Sondas de subida en el log de acceso – y lo que mostraron los dos primeros días
El 21 de octubre se hizo pública una vulnerabilidad crítica en el manejo de subidas de archivos (multipart/form-data) de PHP: una subida manipulada podía hacer que PHP ejecutara el código del remitente. Los escáneres empezaron a probarla en menos de un día. En el log de acceso, una sonda así parecía un formulario cualquiera: solo POST /.
El ingress registra ahora también el tipo de contenido y el tamaño de cada petición. Eso basta para un patrón sencillo: una subida multipart de pocos kilobytes a una página que nunca acepta subidas, como la portada o la página de inicio de sesión, es una sonda, no un visitante.
Los dos primeros días del nuevo log: los escáneres enviaron este tipo de subidas a los tres servidores, sobre todo desde direcciones de grandes proveedores de nube. Uno disparó 32 en un solo segundo, de unos 6 KB cada una, cambiando el nombre del navegador cada pocas peticiones para parecer un visitante normal. El contenido son datos de formulario manipulados para descarrilar el analizador de subidas. Si hubiera funcionado, el servidor ejecutaría el código del atacante: normalmente una pequeña web shell, una puerta trasera que acepta órdenes por HTTP.
No pasó nada. Cada sonda recibió un 400 (rechazada), un 404 o una redirección, y ninguna obtuvo una respuesta con contenido. Varios remitentes ya estaban bloqueados por fail2ban por haber tanteado otras rutas prohibidas. Las subidas normales – los clientes de escritorio de Nextcloud, las vistas previas de documentos, los medios en el panel del blog – se ven claramente distintas: otras rutas, otros tipos de contenido, usuarios con sesión iniciada.
Lo que queda: solo se puede juzgar lo que los logs registran. Dos campos más convirtieron «probablemente un ataque» en «seguro una sonda, seguro rechazada».
🔧 El resto
- El blog pasa a PHP 8.5.
- Límite de memoria de PHP-FPM en el blog de 1 GiB a 1,5 GiB: doce workers más OPcache nunca cupieron en 1 GiB; el comentario antiguo suponía la mitad de memoria por worker. Un error propio, encontrado midiendo.
- Actualizaciones progresivas (rolling updates) para los pods de la aplicación: el pod nuevo arranca antes de que se detenga el antiguo.
- Servidores Nextcloud con Redis 7, del flujo de módulos de la distribución.
- Log de acceso del ingress con el tipo de contenido y el tamaño de la petición: ahora se distinguen las subidas multipart sospechosas de los formularios normales.
- Pressure Stall Information (PSI) activado al arrancar, para que el panel de presión muestre datos.
🧭 Lo que viene: nada nuevo, a propósito
Para las próximas semanas y meses no tengo previstas más funciones ni cambios. La instalación hace lo que debe, y cada componente nuevo traería nuevas fuentes de fallos. El tiempo se dedica ahora a entender lo que ya existe (leer la configuración con ojos nuevos, medir en lugar de suponer) y a documentarlo: los deep dives, las guías de operación y las lecciones en los comentarios del código.
El mantenimiento sigue como siempre: actualizaciones cada lunes, certificados, correcciones de seguridad. Correcciones de fallos, sí; funciones nuevas, solo si hay una necesidad real.
Lo que queda: una instalación que se entiende vale más que una función más. La estabilidad también es una decisión.
🎯 Las lecciones
- Probar el efecto, no la configuración.
- Cada tarea periódica necesita una señal de vida; el silencio no significa «todo bien».
- Demostrar con regularidad la ruta de alertas, con una alerta que siempre se dispara.
- Seguir los síntomas: una alerta de CPU aparentemente inofensiva llevó a descargas rotas.
- Los valores por defecto son decisiones de otros; hay que contrastarlos con los propios órdenes de magnitud.
La lista completa de cambios está en el mensaje del tag v3.6.0.





