Un registro de acceso de Apache o nginx responde preguntas muy concretas: cuántos errores hubo, quién pide más, qué páginas no existen, a qué hora hay más tráfico y qué bots andan husmeando. Todas se contestan con awk, sort y uniq en una línea. Aquí tienes cada una con su resultado real sobre un registro de ejemplo y un script que las reúne en un informe.
También te puede interesar
Los ejemplos usan un registro de acceso de 60 líneas en el formato combinado, el que escriben por omisión Apache y nginx. Los datos son ficticios, con direcciones IP reservadas para documentación. Cada línea tiene la IP, la fecha, la petición, el código de estado, los bytes enviados, la página de origen y el navegador:
terminal$ wc -l access.log 60 access.log $ head -n 3 access.log 192.0.2.15 - - [30/Oct/2026:08:00:41 +0000] "POST /api/pedidos HTTP/1.1" 200 993 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 19_0 like Mac OS X) Safari/605.1" 198.51.100.23 - - [30/Oct/2026:08:03:08 +0000] "GET /productos/teclado HTTP/1.1" 200 9239 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/141.0" 198.51.100.23 - - [30/Oct/2026:08:20:50 +0000] "GET /wp-login.php HTTP/1.1" 404 1165 "-" "curl/8.14.1"
Separada por espacios, como la ve awk, cada línea tiene la IP en $1, la fecha en $4, el método en $6, la ruta en $7, el código de estado en $9 y los bytes en $10. Con eso basta para casi todo.
Los códigos de estado
terminal$ awk '{ print $9 }' access.log | sort | uniq -c | sort -rn
45 200
7 500
5 404
3 304La cadena sort | uniq -c | sort -rn es el corazón de este tipo de análisis: ordena para juntar los valores iguales, uniq -c los cuenta y el último sort pone primero los más frecuentes. El 304 significa «no ha cambiado» (el navegador ya tenía la imagen), y los 404 y 500 merecen una mirada.
Quién pide más
terminal$ awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -n 5
17 198.51.100.23
11 203.0.113.7
10 192.0.2.15
8 192.0.2.88
6 198.51.100.4Qué páginas no existen
terminal$ awk '$9 == 404 { print $7 }' access.log | sort | uniq -c | sort -rn
4 /wp-login.php
1 /.env
$ awk '$9 == 404 { print $1, $7 }' access.log | grep -E 'wp-login|\.env' | sort | uniq -c
3 198.51.100.23 /wp-login.php
1 198.51.100.4 /wp-login.php
1 203.0.113.7 /.env/wp-login.php y /.env en una web que no es WordPress ni debería publicar su archivo .env son la huella típica de los bots que recorren internet buscando instalaciones mal configuradas. Un 404 en esas rutas es buena señal: no encontraron nada. Si aparecen con un 200, toca revisar, y en nuestra guía para saber si atacaron tu WordPress explicamos qué buscar.
Peticiones por hora y por franja
terminal$ awk '{ split($4, t, ":"); print t[2] ":00" }' access.log | sort | uniq -c
11 08:00
14 09:00
19 10:00
16 11:00
$ awk '$4 >= "[30/Oct/2026:09:00" && $4 < "[30/Oct/2026:09:30"' access.log | wc -l
8La fecha es [30/Oct/2026:09:14:05: split la parte por los dos puntos y la hora queda en el segundo trozo. La segunda orden cuenta las peticiones entre las 9:00 y las 9:30 comparando la fecha como texto, que funciona porque dentro de un mismo día las horas se ordenan igual como texto que como número.
Errores del servidor y bytes enviados
terminal$ grep -c '" 5[0-9][0-9] ' access.log
7
$ awk '$9 >= 500 { print $4, $6, $7 }' access.log
[30/Oct/2026:08:24:29 "GET /api/pedidos
[30/Oct/2026:08:30:14 "POST /api/pedidos
[30/Oct/2026:09:18:32 "GET /api/pedidos
[30/Oct/2026:10:14:02 "POST /api/pedidos
[30/Oct/2026:10:44:07 "POST /api/pedidos
[30/Oct/2026:11:07:05 "GET /api/pedidos
[30/Oct/2026:11:32:03 "GET /api/pedidos
$ awk '$10 != "-" { total += $10 } END { printf "%.2f MB enviados\n", total / 1048576 }' access.log
0.32 MB enviadosUn 304 no envía cuerpo, y en el registro aparece un guion en lugar de los bytes; el filtro $10 != "-" lo deja fuera de la suma.
Los navegadores y los bots
terminal$ awk -F'"' '{ print $6 }' access.log | sort | uniq -c | sort -rn
23 Mozilla/5.0 (iPhone; CPU iPhone OS 19_0 like Mac OS X) Safari/605.1
20 Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/141.0
12 Mozilla/5.0 (X11; Linux x86_64) Firefox/144.0
5 curl/8.14.1El navegador va entre comillas y tiene espacios dentro, así que aquí se separa por comillas con -F'"': el sexto trozo es el agente de usuario. Que todas las peticiones de curl vayan a rutas sospechosas no es casualidad.
Un informe en un script
#!/usr/bin/env bash
# Uso: informe.sh REGISTRO
# Resumen de un registro de acceso en formato combinado (Apache o nginx).
if (( $# != 1 )) || [[ ! -r $1 ]]; then
echo "Uso: ${0##*/} REGISTRO (un archivo que se pueda leer)" >&2
exit 2
fi
registro=$1
titulo() {
printf '\n== %s ==\n' "$1"
}
total=$(wc -l < "$registro")
errores=$(awk '$9 >= 500' "$registro" | wc -l)
megas=$(awk '$10 != "-" { s += $10 } END { printf "%.2f", s / 1048576 }' "$registro")
printf 'Registro: %s\n' "$registro"
printf 'Peticiones: %d, errores del servidor: %d, enviado: %s MB\n' "$total" "$errores" "$megas"
titulo "Códigos de estado"
awk '{ print $9 }' "$registro" | sort | uniq -c | sort -rn
titulo "Las 3 IP con más peticiones"
awk '{ print $1 }' "$registro" | sort | uniq -c | sort -rn | head -n 3
titulo "Páginas no encontradas (404)"
awk '$9 == 404 { print $7 }' "$registro" | sort | uniq -c | sort -rn
titulo "Peticiones por hora"
awk '{ split($4, t, ":"); print t[2] ":00" }' "$registro" | sort | uniq -csalidaRegistro: access.log
Peticiones: 60, errores del servidor: 7, enviado: 0.32 MB
== Códigos de estado ==
45 200
7 500
5 404
3 304
== Las 3 IP con más peticiones ==
17 198.51.100.23
11 203.0.113.7
10 192.0.2.15
== Páginas no encontradas (404) ==
4 /wp-login.php
1 /.env
== Peticiones por hora ==
11 08:00
14 09:00
19 10:00
16 11:00Registros comprimidos y en directo
Los registros viejos suelen estar rotados y comprimidos con gzip (access.log.2.gz). No hace falta descomprimirlos: zcat los lee como cat y zgrep busca como grep:
terminal$ gzip -k access.log $ ls access.log* access.log access.log.gz $ zcat access.log.gz | wc -l 60 $ zgrep -c '" 404 ' access.log.gz 5
Para mirar en directo lo que va llegando, tail -f access.log se queda escuchando el final del archivo, y tail -f access.log | grep --line-buffered '" 500 ' enseña solo los errores según ocurren (el --line-buffered hace que grep no se guarde las líneas hasta llenar su búfer). Si quieres aprender a encontrar los registros de tu servidor, están en la guía de registros y archivos de configuración.
- PreguntaOrden
- ¿Cuántas peticiones?
wc -l access.log - ¿Qué códigos de estado?
awk '{print $9}' access.log | sort | uniq -c | sort -rn - ¿Quién pide más?
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head - ¿Qué no existe?
awk '$9 == 404 {print $7}' access.log | sort | uniq -c - ¿Errores del servidor?
awk '$9 >= 500' access.log - ¿En un registro comprimido?
zcat,zgrep
Todos los scripts de esta página se ejecutaron con Bash 5.3 en Ubuntu 26.04, en un contenedor limpio sin acceso a la red, y pasaron ShellCheck 0.11 sin avisos antes de publicarla; las salidas están copiadas de esa ejecución.