Los logs de acceso y de errores son la primera fuente de información cuando una web va lenta, devuelve errores o recibe tráfico sospechoso. En este tutorial configurarás en Ubuntu 24.04 los logs de Apache o Nginx para que incluyan el tiempo de respuesta, los analizarás desde la terminal con awk y con GoAccess, y ajustarás logrotate para que no llenen el disco.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Apache (
apache2) o Nginx instalado desde los repositorios de Ubuntu y recibiendo tráfico.
Paso 1: Localizar los logs
En Ubuntu, cada servidor escribe dos archivos principales:
| Servidor | Log de acceso | Log de errores |
|---|---|---|
| Apache | /var/log/apache2/access.log | /var/log/apache2/error.log |
| Nginx | /var/log/nginx/access.log | /var/log/nginx/error.log |
Los archivos rotados aparecen en el mismo directorio como access.log.1, access.log.2.gz, etc. Pertenecen al grupo adm, así que puedes leerlos sin sudo si añades tu usuario a ese grupo:
sudo usermod -aG adm $USER
El cambio se aplica en la siguiente sesión. Mientras tanto, usa sudo. Mira las últimas peticiones en tiempo real:
sudo tail -f /var/log/nginx/access.log
Cada línea sigue el formato combined, común a ambos servidores:
203.0.113.25 - - [25/Sep/2026:10:14:02 +0000] "GET /blog/ HTTP/1.1" 200 5123 "https://www.google.com/" "Mozilla/5.0 (X11; Linux x86_64) ..."
Los campos son: IP del cliente, usuario (si hay autenticación), fecha, línea de la petición, código de estado, bytes enviados, Referer y User-Agent. Pulsa Ctrl+C para salir de tail.
Paso 2: Añadir el tiempo de respuesta al log
El formato combined no dice cuánto tardó cada petición, que es justo lo que necesitas para encontrar páginas lentas. Añade ese dato al final de la línea, así las herramientas que entienden combined siguen funcionando.
En Nginx
Crea un archivo con el nuevo formato. Nginx carga /etc/nginx/conf.d/*.conf dentro del bloque http:
sudo nano /etc/nginx/conf.d/log-format.conf
log_format timed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time urt="$upstream_response_time"';
$request_time es el tiempo total en segundos con milisegundos, y $upstream_response_time el tiempo que tardó el backend (PHP-FPM, una aplicación Node, etc.) cuando Nginx actúa como proxy. Usa el formato en el bloque server de tu sitio:
sudo nano /etc/nginx/sites-available/your_domain
access_log /var/log/nginx/your_domain.access.log timed;
error_log /var/log/nginx/your_domain.error.log warn;
Tener un archivo por sitio facilita el análisis cuando el servidor aloja varios dominios. Comprueba y recarga:
sudo nginx -t && sudo systemctl reload nginx
En Apache
Define el formato en un archivo de configuración propio. %D es el tiempo de respuesta en microsegundos:
sudo nano /etc/apache2/conf-available/log-format.conf
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" rt=%D" timed
Actívalo y úsalo en el virtual host de tu sitio:
sudo a2enconf log-format
sudo nano /etc/apache2/sites-available/your_domain.conf
CustomLog ${APACHE_LOG_DIR}/your_domain.access.log timed
ErrorLog ${APACHE_LOG_DIR}/your_domain.error.log
Comprueba y recarga:
sudo apache2ctl configtest && sudo systemctl reload apache2
Verificar el nuevo formato
Haz una petición y mira la última línea del log (sustituye la ruta por la de Apache si usas Apache):
curl -s -o /dev/null http://localhost/
sudo tail -n 1 /var/log/nginx/your_domain.access.log
127.0.0.1 - - [25/Sep/2026:10:20:11 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.5.0" rt=0.002 urt="-"
Si la petición no aparece en el log del sitio, curl ha caído en otro server o virtual host; prueba con curl -H "Host: your_domain" http://localhost/.
Paso 3: Excluir peticiones que no aportan nada
Los health checks de un balanceador o de un monitor externo pueden generar miles de líneas al día. Puedes dejarlas fuera del log.
En Nginx, usa un map en /etc/nginx/conf.d/log-format.conf:
map $request_uri $loggable {
/health 0;
default 1;
}
Y añade la condición a la directiva access_log del sitio:
access_log /var/log/nginx/your_domain.access.log timed if=$loggable;
En Apache, marca la petición con SetEnvIf dentro del virtual host y excluye esa variable en CustomLog:
SetEnvIf Request_URI "^/health$" dontlog
CustomLog ${APACHE_LOG_DIR}/your_domain.access.log timed env=!dontlog
Recarga el servidor como en el paso anterior y comprueba que curl http://localhost/health ya no añade líneas al log.
Paso 4: Analizar los logs desde la terminal
Con awk, sort y uniq puedes responder a la mayoría de preguntas sin instalar nada. En el formato combined, $1 es la IP, $7 la ruta y $9 el código de estado. Define una variable con la ruta de tu log para no repetirla:
LOG=/var/log/nginx/your_domain.access.log
Reparto de códigos de estado:
sudo awk '{print $9}' "$LOG" | sort | uniq -c | sort -rn
18234 200
1203 304
412 404
37 301
5 502
Las 10 IP con más peticiones:
sudo awk '{print $1}' "$LOG" | sort | uniq -c | sort -rn | head -10
Las rutas que más errores 404 generan, útil para detectar enlaces rotos o escaneos de vulnerabilidades (/wp-login.php, /.env):
sudo awk '$9 == 404 {print $7}' "$LOG" | sort | uniq -c | sort -rn | head -10
Las 10 peticiones más lentas, usando el campo rt= del paso 2 (en Apache el valor está en microsegundos):
sudo grep -o '"[A-Z]* [^"]*" .* rt=[0-9.]*' "$LOG" | awk '{print $NF, $2}' | sort -t= -k2 -rn | head -10
rt=2.184 /api/reports
rt=1.920 /api/reports
rt=0.873 /search
Peticiones por hora, para ver picos de tráfico:
sudo awk '{print substr($4, 2, 14)}' "$LOG" | sort | uniq -c
Para incluir los archivos ya rotados y comprimidos, usa zcat -f, que lee tanto archivos .gz como sin comprimir:
sudo zcat -f /var/log/nginx/your_domain.access.log* | awk '{print $9}' | sort | uniq -c | sort -rn
Para el log de errores basta con filtrar por nivel o por texto. Por ejemplo, las últimas conexiones fallidas con el backend en Nginx:
sudo grep -E "upstream|connect\(\) failed" /var/log/nginx/your_domain.error.log | tail -20
Paso 5: Generar informes con GoAccess
GoAccess analiza los logs y muestra visitantes, rutas, códigos de estado, sistemas operativos y referers en la terminal o en un informe HTML. Está en los repositorios de Ubuntu:
sudo apt update
sudo apt install goaccess
Abre el panel interactivo sobre el log de acceso. Como tu formato añade campos al final de combined, GoAccess lo reconoce con el formato COMBINED:
sudo goaccess /var/log/nginx/your_domain.access.log --log-format=COMBINED
Muévete entre paneles con Tab y sal con q. Para generar un informe HTML estático de todo el historial, incluidos los archivos rotados:
sudo zcat -f /var/log/nginx/your_domain.access.log* | goaccess - --log-format=COMBINED -o ~/report.html
[PARSING STDIN] {21,891} @ {0/s}
Descarga ~/report.html a tu equipo con scp y ábrelo en el navegador. No publiques el informe en un directorio web accesible sin protegerlo con contraseña: contiene las IP de tus visitantes.
Paso 6: Configurar la rotación con logrotate
Ubuntu rota los logs de Apache y Nginx con logrotate, que se ejecuta una vez al día mediante un temporizador de systemd. Compruébalo:
systemctl list-timers logrotate.timer
NEXT LEFT LAST PASSED UNIT ACTIVATES
Fri 2026-09-26 00:00:00 UTC 13h left Thu 2026-09-25 00:00:03 UTC 10h ago logrotate.timer logrotate.service
La configuración de cada servidor está en /etc/logrotate.d/nginx y /etc/logrotate.d/apache2. Ambas usan el patrón *.log del directorio, así que los logs por sitio que creaste en el paso 2 también se rotan. Abre la de tu servidor:
sudo nano /etc/logrotate.d/nginx
Ajusta la política a tus necesidades. Este ejemplo rota a diario, guarda 30 días con la fecha en el nombre y comprime todo salvo el archivo más reciente:
/var/log/nginx/*.log {
daily
missingok
rotate 30
dateext
compress
delaycompress
notifempty
create 0640 www-data adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
run-parts /etc/logrotate.d/httpd-prerotate; \
fi \
endscript
postrotate
invoke-rc.d nginx rotate >/dev/null 2>&1
endscript
}
Deja intactos los bloques prerotate y postrotate que ya trae tu archivo: son los que avisan al servidor para que abra los logs nuevos (en Apache, con una recarga). Si solo quieres cambiar la retención, basta con modificar rotate y añadir dateext.
Consejo
delaycompressdeja sin comprimir el log del día anterior (access.log.1o con fecha). Así puedes seguir consultándolo congrepytaildirectamente, y las órdenes conzcat -fdel paso 4 funcionan con ambos.
Prueba la configuración en modo simulación. -d muestra lo que haría sin tocar ningún archivo:
sudo logrotate -d /etc/logrotate.d/nginx
rotating pattern: /var/log/nginx/*.log after 1 days (30 rotations)
empty log files are not rotated, old logs are removed
considering log /var/log/nginx/access.log
Si no hay errores, fuerza una rotación para comprobar que todo funciona de extremo a extremo:
sudo logrotate -f /etc/logrotate.d/nginx
ls -lh /var/log/nginx/
Debes ver archivos nuevos vacíos y los anteriores con la fecha en el nombre. Haz una petición y comprueba que se escribe en el access.log nuevo; si se escribe en el rotado, el postrotate no ha avisado al servidor.
Solución de problemas
Los logs rotados siguen creciendo y el nuevo está vacío. El servidor mantiene abierto el archivo antiguo. Revisa que el bloque postrotate está presente y ejecuta sudo systemctl reload nginx o sudo systemctl reload apache2.
logrotate no se ejecuta. Mira el resultado de la última ejecución con sudo journalctl -u logrotate.service -n 20. Un error de sintaxis en cualquier archivo de /etc/logrotate.d/ puede afectar a los demás; pruébalos todos con sudo logrotate -d /etc/logrotate.conf.
El disco se llena igualmente. Busca qué ocupa espacio con sudo du -sh /var/log/* | sort -h. Si la aplicación escribe sus propios logs fuera de /var/log/nginx o /var/log/apache2, necesita su propio archivo en /etc/logrotate.d/.
GoAccess muestra "Token doesn't match specifier". El formato del log no coincide con el indicado. Comprueba que usas --log-format=COMBINED y que el log no está en JSON u otro formato personalizado.
Conclusión
Tus logs ya incluyen el tiempo de respuesta de cada petición, ignoran los health checks, se pueden analizar con unas pocas órdenes o con GoAccess y se rotan con una retención definida. Con esto puedes localizar rápidamente errores, páginas lentas e IP abusivas.
Como siguientes pasos puedes:
- Usar las IP detectadas en el paso 4 para configurar rate limiting en Nginx o reglas de Fail2ban.
- Enviar los logs a un sistema centralizado (rsyslog, Loki o Elasticsearch) si gestionas varios servidores.
- Programar un informe diario de GoAccess con un temporizador de systemd.
