Son las diez y media en el SOC. Salta una alerta de La Alacena, la tienda online de comida de despensa: srv-web, su servidor, lleva toda la mañana contestando 200 a una ruta que no es de la tienda. Nadie ha entrado en el servidor: lo que pasa es que nginx sirve algo que no debería, y lo sirve a quien lo pida.
Es de los fallos más corrientes: una copia de la base de datos que alguien dejó en la carpeta de la
web «un momento», un panel que se quedó sin contraseña, un .zip con el código. No hace falta
romper nada para llevárselo: basta con saber la ruta. Tu trabajo es averiguar qué se ha servido, a
quién, cuánto y desde cuándo, cerrarlo en nginx sin tirar la tienda y contarlo. Todo sale de los
registros de nginx.
La ficha del SOC dice qué es de La Alacena:
tecnico@srv-web:~$ cat ~/ficha-alacena.txt La Alacena · srv-web (192.168.40.10) Ficha del SOC, revisada el 5 de octubre de 2026 Lo que es público (nginx, puerto 80): la tienda (/), las fotos de los productos (/productos/) y el carrito. Lo que no: el panel de la tienda (/admin/): solo desde la oficina (192.168.40.50). las copias de la base de datos: cada noche, a /var/backups/alacena (fuera de la web). Si se toca nginx: sudo nginx -t antes de recargar. Si la tienda no contesta, no se vende.
Lo que apunta nginx
nginx apunta cada petición en /var/log/nginx/access.log, una línea por petición, en el formato combined:
tecnico@srv-web:~$ tail -n 3 /var/log/nginx/access.log 100.80.116.164 - - [20/Oct/2026:09:51:30 +0000] "GET /productos/garbanzos.jpg HTTP/1.1" 200 43317 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0" 100.80.116.164 - - [20/Oct/2026:09:51:48 +0000] "GET /productos/atun-claro.jpg HTTP/1.1" 200 53951 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0" 100.80.116.164 - - [20/Oct/2026:09:51:49 +0000] "GET /carrito.html HTTP/1.1" 200 53 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0"
Por orden: quién pidió (la IP), dos guiones (el segundo, el usuario si entró con contraseña), cuándo, la petición (método, ruta y versión), el código con el que contestó (200 bien, 404 no existe, 403 prohibido…), los bytes que mandó, de dónde venía el enlace y con qué programa se pidió. Lo que falla va además a /var/log/nginx/error.log.
Contar lo que se ha servido
Con awk se saca un campo de cada línea ($9, el código; $7, la ruta), y con sort | uniq -c
se cuenta cuántas veces sale cada uno:
tecnico@srv-web:~$ awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn 75 200 4 404
tecnico@srv-web:~$ awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn 19 / 12 /copias/alacena-2026-10-19.sql.gz 11 /productos/garbanzos.jpg 10 /productos/atun-claro.jpg 10 /productos/arroz-bomba.jpg 7 /productos/aceite-oliva.jpg 5 /carrito.html 4 /favicon.ico 1 /admin/
La tienda, las fotos, el carrito… y una ruta que no está en la ficha, pedida doce veces: una copia de
la base de datos en /copias/. Está ahí de verdad, dentro de la carpeta de la web:
tecnico@srv-web:~$ ls -l /var/www/alacena/copias total 1792 -rw-r--r-- 1 www-data www-data 1832843 Oct 20 02:15 alacena-2026-10-19.sql.gz
Ejercicio
Qué se ha servido: access.log con awk
En srv-web, la tienda de La Alacena, mira en access.log qué códigos y qué rutas se han servido esta mañana, y encuentra la que no debería estar a la vista (la ficha, en ~/ficha-alacena.txt).
Preparando la máquina…
A quién y cuánto
grep se queda con las líneas de la copia, y awk con la IP de cada una:
tecnico@srv-web:~$ grep copias /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c 4 198.51.100.177 8 203.0.113.151
Y awk también suma: cuántas veces se sirvió entera (código 200) y cuántos bytes salieron con ella.
tecnico@srv-web:~$ awk '$7 ~ /copias/ && $9 == 200 {n++; b += $10} END {print n, b}' /var/log/nginx/access.log 12 21994116
Unos 22 MB: la copia, doce veces. La primera vez, la primera línea que la nombra:
tecnico@srv-web:~$ grep -m1 copias /var/log/nginx/access.log 203.0.113.151 - - [20/Oct/2026:07:29:00 +0000] "GET /copias/alacena-2026-10-19.sql.gz HTTP/1.1" 200 1832843 "-" "curl/7.81.0"
Ejercicio
A quién y cuánto: grep, awk, sort y uniq
La copia de la base de datos se ha servido varias veces. Saca desde qué direcciones, cuántos bytes en total y cuándo fue la primera vez.
Preparando la máquina…
Cerrarlo en nginx
El sitio de la tienda está en /etc/nginx/sites-available/alacena:
tecnico@srv-web:~$ cat /etc/nginx/sites-available/alacena server { listen 80 default_server; listen [::]:80 default_server; root /var/www/alacena; index index.html; server_name alacena.lan; location / { try_files $uri $uri/ =404; } }
Un bloque location aplica a las rutas que empiezan por lo que dice; dentro, deny all prohíbe
todo. Se añade antes de la última llave, la que cierra el server:
tecnico@srv-web:~$ tail -n 5 /etc/nginx/sites-available/alacena } location /copias/ { deny all; } }
Sin editor, sed -i '$i …' mete una línea justo antes de la última: con
sudo sed -i '$i location /copias/ { deny all; }' /etc/nginx/sites-available/alacena queda el
mismo bloque en una sola línea. Y si algo sobra, cat -n dice en qué línea está y
sudo sed -i '<n>d' la borra.
Antes de recargar, siempre nginx -t: comprueba la configuración sin tocar lo que está en
marcha.
tecnico@srv-web:~$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
tecnico@srv-web:~$ sudo systemctl reload nginx
Ahora la copia contesta 403 y la tienda sigue contestando; error.log apunta cada intento:
tecnico@srv-web:~$ curl -sI http://localhost/copias/alacena-2026-10-19.sql.gz HTTP/1.1 403 Forbidden Server: nginx/1.18.0 (Ubuntu) Date: Tue, 20 Oct 2026 10:30:15 GMT Content-Type: text/html Content-Length: 162 Connection: keep-alive
tecnico@srv-web:~$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost/ 200
tecnico@srv-web:~$ tail -n 1 /var/log/nginx/error.log 2026/10/20 10:30:15 [error] 406#406: *80 access forbidden by rule, client: 127.0.0.1, server: alacena.lan, request: "HEAD /copias/alacena-2026-10-19.sql.gz HTTP/1.1", host: "localhost"
tecnico@srv-web:~$ sudo nginx -t nginx: [emerg] unexpected "}" in /etc/nginx/sites-enabled/alacena:15 nginx: configuration file /etc/nginx/nginx.conf test failed
tecnico@srv-web:~$ sudo systemctl reload nginx Job for nginx.service failed. See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.
tecnico@srv-web:~$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost/ 200
Ejercicio
Cerrarlo en nginx: location, deny all, nginx -t y reload
Que nginx no sirva nada de /copias/ a nadie, sin tirar la tienda. Comprueba la configuración antes de recargar.
Preparando la máquina…
Sacarla de la web
Cerrarlo en nginx es lo urgente; lo bueno es que la copia no esté en la web. find busca si hay
más:
tecnico@srv-web:~$ sudo find /var/www -name '*.sql*' /var/www/alacena/copias/alacena-2026-10-19.sql.gz
Según la ficha, las copias van a /var/backups/alacena. Se mueve, no se borra: es la copia de anoche.
tecnico@srv-web:~$ sudo mv /var/www/alacena/copias/*.sql.gz /var/backups/alacena/
tecnico@srv-web:~$ sudo ls -l /var/backups/alacena total 3580 -rw-r--r-- 1 root root 1828632 Oct 19 02:15 alacena-2026-10-18.sql.gz -rw-r--r-- 1 www-data www-data 1832843 Oct 20 02:15 alacena-2026-10-19.sql.gz
Ejercicio
Sacar la copia de la carpeta pública: find y mv
La copia no pinta nada en la web: según la ficha, las copias van a /var/backups/alacena. Busca si hay más dentro de /var/www y llévalas allí (sin borrarlas).
Preparando la máquina…
El panel, solo para la oficina
La ficha dice que el panel es solo para la oficina, y access.log dice que alguien de fuera lo vio:
tecnico@srv-web:~$ grep /admin/ /var/log/nginx/access.log 192.0.2.229 - - [20/Oct/2026:07:16:00 +0000] "GET /admin/ HTTP/1.1" 200 75 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0"
allow y deny se miran en orden y la primera que casa decide: se deja pasar a la oficina y se
prohíbe al resto. Si la oficina tiene más de un ordenador, un allow por cada uno, todos antes del
deny all.
tecnico@srv-web:~$ tail -n 9 /etc/nginx/sites-available/alacena } location /copias/ { deny all; } location /admin/ { allow 192.168.40.50; deny all; } }
tecnico@srv-web:~$ curl -sI http://localhost/admin/ HTTP/1.1 403 Forbidden Server: nginx/1.18.0 (Ubuntu) Date: Tue, 20 Oct 2026 10:30:32 GMT Content-Type: text/html Content-Length: 162 Connection: keep-alive
403 también para ti: localhost no es la oficina. Otra forma es pedir contraseña: htpasswd crea el
archivo de usuarios, y en el location, auth_basic y auth_basic_user_file.
tecnico@srv-web:~$ sudo htpasswd -cb /etc/nginx/.htpasswd oficina Alacena-2026 Adding password for user oficina
Ejercicio
El panel, solo para la oficina: allow y deny (o auth_basic)
El panel de la tienda (/admin/) contesta a cualquiera, y la ficha dice que es solo para la oficina (192.168.40.50). Ciérralo para el resto sin tocar la tienda.
Preparando la máquina…
En la sala: la tienda abierta
En la sala estás en el SOC a las diez y media, con la alerta de La Alacena en la pared y la terminal de srv-web en tu puesto. Mientras la copia siga a la vista, quien la pedía la sigue pidiendo (y, al rato, alguien más): cada quince minutos la alerta vuelve con las veces que se ha servido, y lo servido cuenta en la nota. A las doce y media, si sigue a la vista, el día se cierra así. Bloquear con ufw a quien se la lleva no basta; hay que cerrarlo en nginx, la copia y el panel. Si la tienda deja de contestar (un restart con un error, el 80 cerrado), no vende: es servicio caído. La nota mira además que la copia acabe fuera de la web, guardada, y que la oficina siga entrando al panel.