Un sistema de logs centralizado te permite buscar en los logs de todos tus servidores desde un único sitio, graficar tasas de error y recibir una alerta cuando algo falla. En este tutorial montarás uno en un único servidor Ubuntu 24.04 con tres componentes de Grafana Labs: Loki guarda los logs, Grafana Alloy los recoge y procesa, y Grafana es la interfaz para buscar, crear paneles y definir alertas. Procesarás los logs de acceso de Nginx, crearás un panel con los códigos de estado HTTP y una alerta que salta cuando se disparan los errores 5xx.
Las versiones anteriores de este stack usaban Promtail como recolector. Promtail llegó al final de su vida útil en 2026 y Grafana Alloy es su sustituto, así que esta guía usa Alloy y explica cómo convertir una configuración de Promtail existente.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS y al menos 2 GB de RAM (con 4 GB irás más holgado), por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudoy UFW activado con SSH permitido. - Un dominio con un registro A que apunte a la IP pública del servidor. En esta guía lo llamamos
your_domain; Grafana se servirá enhttps://your_domain.
Paso 1: Añadir el repositorio APT de Grafana
Loki, Alloy y Grafana se publican en el repositorio APT firmado de Grafana. Añade su clave y el repositorio:
sudo apt update
sudo apt install -y gpg wget curl jq
sudo mkdir -p /etc/apt/keyrings
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
Instala los tres paquetes, además de Nginx, que hará de proxy inverso para Grafana y de fuente de logs de ejemplo:
sudo apt install -y loki alloy grafana nginx
Paso 2: Configurar Loki
La configuración empaquetada de Loki guarda los datos en /tmp, que no sobrevive a un reinicio. Crea un directorio de datos persistente:
sudo mkdir -p /var/lib/loki
sudo chown loki:loki /var/lib/loki
Abre el archivo de configuración:
sudo nano /etc/loki/config.yml
Sustituye su contenido por una configuración de un solo nodo que escucha solo en localhost, usa el índice TSDB con el esquema v13 y borra los logs a los 30 días:
auth_enabled: false
server:
http_listen_address: 127.0.0.1
http_listen_port: 3100
grpc_listen_address: 127.0.0.1
grpc_listen_port: 9096
common:
instance_addr: 127.0.0.1
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-04-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 720h
compactor:
working_directory: /var/lib/loki/compactor
retention_enabled: true
retention_delete_delay: 2h
delete_request_store: filesystem
analytics:
reporting_enabled: false
Como Alloy y Grafana se ejecutan en el mismo servidor, Loki no necesita ser accesible desde fuera. La guía de instalación de Grafana Loki de esta misma sección explica cada bloque en detalle.
Activa y reinicia Loki, y espera a que indique que está listo (unos 15 segundos):
sudo systemctl enable loki
sudo systemctl restart loki
curl -s http://127.0.0.1:3100/ready
ready
Paso 3: Recoger los logs con Grafana Alloy
Alloy se configura con pipelines de componentes: las fuentes leen los logs, los procesadores los transforman y los escritores los envían a Loki. Crearás dos pipelines: uno para el journal de systemd y otro para los logs de acceso de Nginx.
Alloy se ejecuta con el usuario alloy. Dale acceso de lectura al journal y a /var/log/nginx, cuyos archivos pertenecen al grupo adm:
sudo usermod -aG systemd-journal,adm alloy
Abre la configuración de Alloy:
sudo nano /etc/alloy/config.alloy
Sustituye su contenido por lo siguiente:
// Destino al que envían sus logs todos los pipelines.
loki.write "local" {
endpoint {
url = "http://127.0.0.1:3100/loki/api/v1/push"
}
}
// Pipeline 1: journal de systemd.
loki.relabel "journal" {
forward_to = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
}
loki.source.journal "system" {
max_age = "12h"
relabel_rules = loki.relabel.journal.rules
labels = { job = "systemd-journal", host = constants.hostname }
forward_to = [loki.write.local.receiver]
}
// Pipeline 2: log de acceso de Nginx.
local.file_match "nginx" {
path_targets = [{
"__path__" = "/var/log/nginx/access.log",
"job" = "nginx",
"host" = constants.hostname,
}]
}
loki.source.file "nginx" {
targets = local.file_match.nginx.targets
forward_to = [loki.process.nginx.receiver]
}
loki.process "nginx" {
// Descarta los health checks del balanceador antes de guardarlos.
stage.drop {
expression = `"GET /healthz `
drop_counter_reason = "healthcheck"
}
// Extrae campos del formato "combined" por defecto.
stage.regex {
expression = `^(?P<remote_addr>\S+) \S+ \S+ \[[^\]]+\] "(?P<method>\S+) (?P<path>\S+) [^"]*" (?P<status>\d{3}) `
}
// Adjunta el código de estado como metadatos estructurados, no como etiqueta del índice.
stage.structured_metadata {
values = { status = "" }
}
forward_to = [loki.write.local.receiver]
}
Cómo funciona el pipeline de Nginx:
local.file_matchdefine qué archivos seguir y las etiquetas que recibe cada línea (jobyhost).loki.source.filelos lee y recuerda su posición, así que los reinicios no duplican líneas.stage.dropdescarta cualquier línea que coincida con la expresión regular. Aquí son las peticiones de health check, que añaden volumen pero no información.stage.regexanaliza cada línea con grupos de captura con nombre y los guarda en un mapa temporal de campos extraídos.stage.structured_metadataadjunta elstatusextraído a cada línea como metadatos. Se puede filtrar por él al consultar, pero no crea flujos nuevos.
ImportanteLas etiquetas crean flujos en Loki, y cada flujo tiene un coste. Limita las etiquetas a unos pocos valores de baja cardinalidad (
job,host,unit). No conviertas nunca en etiquetas rutas, IP de clientes ni identificadores de usuario; filtra por ellos al consultar.
Comprueba la sintaxis del archivo. alloy fmt termina con un error que señala la línea si algo está mal:
alloy fmt /etc/alloy/config.alloy > /dev/null && echo "sintaxis OK"
Activa y reinicia Alloy, y lee su log:
sudo systemctl enable alloy
sudo systemctl restart alloy
sudo journalctl -u alloy -n 30 --no-pager
No debería haber líneas level=error. Genera algunas peticiones a Nginx y confirma que Loki tiene ambos jobs:
for i in 1 2 3; do curl -s -o /dev/null http://localhost/; curl -s -o /dev/null http://localhost/missing; done
curl -s http://127.0.0.1:3100/loki/api/v1/label/job/values | jq -c .data
["nginx","systemd-journal"]
Migrar una configuración de Promtail existente
Si ya usas Promtail, Alloy puede traducir su configuración YAML al formato de Alloy:
alloy convert --source-format=promtail --output=/tmp/promtail-converted.alloy /etc/promtail/config.yml
Revisa el resultado, intégralo en /etc/alloy/config.alloy y, cuando Alloy ya esté enviando logs, detén y desactiva Promtail con sudo systemctl disable --now promtail para que las líneas no se envíen dos veces.
Paso 4: Configurar Grafana detrás de Nginx con HTTPS
Por defecto Grafana escucha en el puerto 3000 en todas las interfaces. Limítalo a localhost e indícale su URL pública. Abre su archivo de configuración:
sudo nano /etc/grafana/grafana.ini
En la sección [server], define estas tres claves (quita el ; inicial que las comenta):
[server]
http_addr = 127.0.0.1
domain = your_domain
root_url = https://your_domain/
Añade Loki como origen de datos mediante provisioning, para que exista en cuanto arranque Grafana y no se pueda modificar por error desde la interfaz:
sudo nano /etc/grafana/provisioning/datasources/loki.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://127.0.0.1:3100
isDefault: true
editable: false
Activa e inicia Grafana:
sudo systemctl enable --now grafana-server
curl -s http://127.0.0.1:3000/api/health | jq -r .database
ok
Ahora crea un bloque server de Nginx que haga de proxy hacia Grafana. Grafana Live usa WebSockets en /api/live/, que necesita las cabeceras Upgrade:
sudo nano /etc/nginx/sites-available/grafana
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name your_domain;
location / {
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3000;
}
location /api/live/ {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:3000;
}
}
Activa el sitio, comprueba la configuración y recarga Nginx:
sudo ln -s /etc/nginx/sites-available/grafana /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Abre HTTP y HTTPS en el cortafuegos y solicita un certificado de Let's Encrypt. Certbot edita el bloque server para añadir TLS y una redirección desde HTTP:
sudo ufw allow 'Nginx Full'
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your_domain
Visita https://your_domain. Inicia sesión con el usuario admin y la contraseña admin; Grafana te obliga a definir una contraseña nueva en ese momento. Elige una robusta.
Paso 5: Explorar los logs en Grafana
Abre Explore en el menú izquierdo. El origen de datos Loki ya está seleccionado. Cambia el editor de consultas al modo Code y ejecuta:
{job="nginx"}
Verás las peticiones que hiciste en el paso 3, además del tráfico que estás generando ahora con Grafana. Algunas consultas útiles para probar:
Solo errores de servidor y de cliente, usando los metadatos estructurados que extrajo Alloy:
{job="nginx"} | status >= 400
Analiza la línea al consultar con pattern para filtrar por campos que no extrajiste en Alloy, aquí la ruta de la petición:
{job="nginx"} | pattern `<ip> - <_> [<_>] "<method> <path> <_>" <code> <_>` | path = "/missing"
Accesos SSH fallidos del journal:
{job="systemd-journal", unit="ssh.service"} |= "Failed password"
Paso 6: Crear un panel
Crea un panel (dashboard) con dos visualizaciones: la tasa de peticiones por código de estado y las líneas de error en bruto.
- Ve a Dashboards > New > New dashboard y pulsa Add visualization. Selecciona el origen de datos Loki.
- En modo Code, escribe la siguiente consulta y define la leyenda como
{{status}}:
sum by (status) (count_over_time({job="nginx"} [$__auto]))
- Mantén la visualización Time series, titula el panel
Peticiones Nginx por estadoy vuelve al dashboard. - Añade una segunda visualización, elige el tipo Logs y usa esta consulta:
{job="nginx"} | status >= 500
- Titúlala
Respuestas 5xxy guarda el dashboard comoNginx.
count_over_time convierte las líneas de log en un número por ventana de tiempo, y sum by (status) produce una serie por código de estado. $__auto deja que Grafana elija la ventana según el nivel de zoom.
Paso 7: Alertas sobre patrones de log
Grafana puede evaluar consultas de métricas LogQL de forma periódica y avisarte cuando superan un umbral. Primero crea un punto de contacto en Alerting > Contact points, por ejemplo correo (requiere configurar la sección [smtp] de grafana.ini) o un webhook de Slack. Usa Test para confirmar que llega.
Después crea la regla en Alerting > Alert rules > New alert rule:
- Llámala
Pico de 5xx en Nginxy selecciona el origen de datos Loki. - Escribe esta consulta, que cuenta las respuestas 5xx de los últimos 5 minutos:
sum(count_over_time({job="nginx"} | status >= 500 [5m]))
- Define la condición de umbral como Is above 10.
- Crea una carpeta y un grupo de evaluación que se evalúe cada
1m, y define un periodo pendiente (pending period) de5mpara que un pico breve no despierte a nadie. - Selecciona tu punto de contacto y guarda la regla.
Para probarla, baja temporalmente el umbral a 0 y pide una URL a la que tu aplicación responda con un error 5xx. La regla pasa a Pending, después a Firing, y recibes una notificación. Restaura el umbral al terminar.
Solución de problemas
Los logs de Nginx no aparecen en Loki. Revisa sudo journalctl -u alloy | grep -i nginx. Un error permission denied significa que el cambio al grupo adm no se ha aplicado; confírmalo con id alloy y reinicia Alloy.
Grafana muestra "Data source connected, but no labels found". Loki funciona pero no ha recibido nada. Comprueba que Alloy está en marcha y que curl -s http://127.0.0.1:3100/loki/api/v1/labels lista etiquetas.
502 Bad Gateway en https://your_domain. Grafana no está en marcha o no escucha en 127.0.0.1:3000. Revisa sudo systemctl status grafana-server y sudo ss -tlnp | grep 3000.
El filtro | status >= 400 no devuelve nada. Los metadatos estructurados solo existen en las líneas recibidas después de añadir el pipeline. Las líneas anteriores se pueden filtrar con la consulta pattern del paso 5. Confirma también que el log_format de Nginx es el combined por defecto, o ajusta la expresión regular de stage.regex.
Loki registra too many outstanding requests desde Grafana. Un dashboard está consultando un rango de tiempo muy amplio. Acota el rango o añade etiquetas más selectivas al selector de flujos.
Conclusión
Ya tienes un stack de logs completo en Ubuntu 24.04: Alloy recoge el journal y los logs de Nginx y procesa el log de acceso, Loki lo guarda todo durante 30 días y Grafana, servido por HTTPS, te da búsqueda, un panel y una alerta sobre errores 5xx.
Para ampliarlo, instala Alloy en tus otros servidores y apunta su loki.write a esta instancia de Loki a través de un proxy inverso con TLS y autenticación, añade pipelines para los logs de tus aplicaciones (las etapas stage.json y stage.logfmt procesan formatos estructurados) y añade Prometheus como segundo origen de datos para correlacionar métricas y logs en los mismos paneles.
