Los logs de un servidor web contienen datos personales aunque no lo parezca: la dirección IP de cada visitante, y a menudo correos, nombres o tokens que llegan en los parámetros de la URL. Según el RGPD, una IP es un dato personal, así que conservar esos logs durante meses sin necesidad es un riesgo legal y de seguridad. En este tutorial configurarás Nginx en Ubuntu 24.04 para que escriba los logs ya anonimizados, dejarás un log completo de retención corta para seguridad y crearás un script para seudonimizar logs antiguos antes de archivarlos o compartirlos.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS con Nginx instalado (
sudo apt install nginx), por ejemplo un VPS de CubePath. - Un usuario no root con privilegios
sudo. - Conocer qué logs necesitas para seguridad (por ejemplo, si usas fail2ban sobre los logs de Nginx).
Paso 1: Localizar los datos personales en tus logs
Antes de cambiar nada, comprueba qué datos personales aparecen realmente. Estas búsquedas cuentan las líneas que contienen direcciones de correo y parámetros de URL con nombres sospechosos:
sudo zgrep -cE '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' /var/log/nginx/access.log*
sudo zgrep -oE '[?&](email|user|name|phone|token|dni)=[^& ]*' /var/log/nginx/access.log* | sort | uniq -c | sort -rn | head
/var/log/nginx/access.log:14
/var/log/nginx/access.log.1:22
/var/log/nginx/access.log.2.gz:9
8 /var/log/nginx/access.log:[email protected]
3 /var/log/nginx/access.log:?token=eyJhbGciOi...
Estos resultados te dicen qué tienes que tratar además de la IP. Que haya correos o tokens en las URL también es un problema de la aplicación: los datos sensibles deben enviarse en el cuerpo de una petición POST, no en la URL.
Las técnicas que vas a aplicar son de dos tipos:
| Técnica | Ejemplo | ¿Sigue siendo dato personal? |
|---|---|---|
| Truncado (anonimización) | 203.0.113.45 pasa a 203.0.113.0 | Reduce mucho la identificación; en la práctica suele considerarse suficiente para estadísticas |
| Eliminación | Quitar la cadena de consulta de la URL | No, el dato desaparece |
| Seudonimización con clave | [email protected] pasa a [email protected] | Sí: quien tenga la clave puede relacionarlo, pero no se lee a simple vista |
Paso 2: Crear un formato de log anonimizado en Nginx
Nginx permite calcular una variable nueva a partir de otra con la directiva map. Vas a crear $remote_addr_anon, que pone a cero el último octeto de las IPv4 y conserva solo los dos primeros grupos (un /32) de las IPv6.
Crea un fichero en /etc/nginx/conf.d/, que Nginx incluye dentro del bloque http:
sudo nano /etc/nginx/conf.d/logs-anonimizados.conf
map $remote_addr $remote_addr_anon {
~^(?P<ip>\d+\.\d+\.\d+)\.\d+$ $ip.0;
~^(?P<ip>[0-9a-f]+:[0-9a-f]+): $ip::;
default 0.0.0.0;
}
log_format anonimizado '$remote_addr_anon - - [$time_local] '
'"$request_method $uri $server_protocol" $status $body_bytes_sent '
'"$http_user_agent" $request_time';
Este formato difiere del combined por defecto en tres puntos:
- Usa
$remote_addr_anonen lugar de la IP real. - Registra
$uri(la ruta sin la cadena de consulta) en lugar de$request, así que los parámetros como?email=...no llegan al log. - No registra
$remote_userni$http_referer, que pueden contener nombres de usuario y URL de otras webs con datos personales.
Ahora indica a Nginx que use este formato. En Ubuntu, el log de acceso se define en el fichero principal:
sudo nano /etc/nginx/nginx.conf
Dentro del bloque http, busca la línea access_log /var/log/nginx/access.log; y cámbiala por:
access_log /var/log/nginx/access.log anonimizado;
Si alguno de tus bloques server en /etc/nginx/sites-available/ define su propio access_log, añade también anonimizado al final de esa línea; si no, seguirá usando el formato completo.
Comprueba la sintaxis y recarga Nginx:
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Haz una petición con un parámetro y mira la última línea del log:
curl -4 -s -o /dev/null "http://localhost/[email protected]"
sudo tail -n 1 /var/log/nginx/access.log
127.0.0.0 - - [25/Sep/2026:10:44:12 +0000] "GET / HTTP/1.1" 200 615 "curl/8.5.0" 0.000
La IP aparece truncada y el correo del parámetro ha desaparecido. Pide a alguien que acceda desde fuera o prueba desde tu equipo para ver una IP pública anonimizada.
Paso 3: Mantener un log completo de retención corta
Con IPs truncadas ya no puedes bloquear a un atacante concreto con herramientas como fail2ban, ni investigar un incidente reciente. El RGPD permite tratar datos personales por interés legítimo de seguridad si los conservas solo el tiempo necesario. Una solución habitual es escribir dos logs: uno completo que se borra pronto y otro anonimizado para estadísticas y archivo.
Edita de nuevo /etc/nginx/nginx.conf y añade una segunda línea access_log justo después de la anterior:
access_log /var/log/nginx/access.log anonimizado;
access_log /var/log/nginx/security.log;
security.log usa el formato combined por defecto, con IP completa. Recarga Nginx:
sudo nginx -t && sudo systemctl reload nginx
La rotación de Nginx en Ubuntu, definida en /etc/logrotate.d/nginx, se aplica a todos los ficheros /var/log/nginx/*.log y conserva 14 días. Si quieres una retención distinta para cada fichero, sepáralos en bloques de logrotate propios como se explica en la guía de rotación y retención de logs. Si usas fail2ban, apunta sus jails de Nginx a /var/log/nginx/security.log.
NotaEl log de errores de Nginx (
/var/log/nginx/error.log) incluye la IP del cliente en cada mensaje y su formato no se puede cambiar. Trátalo como el log de seguridad: retención corta y acceso restringido.
Paso 4: Seudonimizar logs antiguos antes de archivarlos
Los logs escritos antes del cambio, o los de otras aplicaciones que no puedes configurar, necesitan un tratamiento posterior. Este script lee un log (comprimido o no), trunca las IPv4 e IPv6 y sustituye la parte local de cada correo por un HMAC-SHA256 con una clave secreta. Usar una clave evita que alguien recupere los correos probando direcciones conocidas, y mantiene el mismo seudónimo para el mismo correo, lo que permite seguir contando usuarios únicos.
Genera la clave y guárdala con permisos solo para root:
sudo install -m 600 /dev/null /etc/anon-logs.key
openssl rand -hex 32 | sudo tee /etc/anon-logs.key > /dev/null
Crea el script:
sudo nano /usr/local/sbin/anonimizar-log
#!/usr/bin/env bash
# Uso: anonimizar-log ORIGEN DESTINO.gz
set -euo pipefail
if [[ $# -ne 2 ]]; then
echo "Uso: $0 ORIGEN DESTINO.gz" >&2
exit 1
fi
src="$1"
dst="$2"
ANON_KEY="$(< /etc/anon-logs.key)"
export ANON_KEY
zcat -f -- "$src" | perl -MDigest::SHA=hmac_sha256_hex -pe '
s/\b(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}\b/$1.0/g;
s/^([0-9a-f]{1,4}:[0-9a-f]{1,4}):[0-9a-f:]*/$1::/i;
s/([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,})/substr(hmac_sha256_hex(lc $1, $ENV{ANON_KEY}), 0, 12) . "\@" . $2/ge;
' | gzip > "$dst"
Qué hace cada sustitución:
- Pone a cero el último octeto de cualquier IPv4 de la línea.
- Recorta la IPv6 del principio de la línea (donde Nginx escribe la IP del cliente) a sus dos primeros grupos.
- Sustituye la parte local de cada correo por los 12 primeros caracteres de su HMAC y conserva el dominio.
Hazlo ejecutable:
sudo chmod 755 /usr/local/sbin/anonimizar-log
Pruébalo con un fichero de ejemplo:
printf '%s\n' \
'203.0.113.45 - - [25/Sep/2026:10:12:01 +0000] "GET /[email protected] HTTP/1.1" 200 512 "-" "curl/8.5.0"' \
'2001:db8:85a3::8a2e:370:7334 - - [25/Sep/2026:10:12:05 +0000] "POST /api/pedidos HTTP/1.1" 201 88 "-" "Mozilla/5.0"' \
> /tmp/prueba.log
sudo anonimizar-log /tmp/prueba.log /tmp/prueba-anon.log.gz
zcat /tmp/prueba-anon.log.gz
203.0.113.0 - - [25/Sep/2026:10:12:01 +0000] "GET /[email protected] HTTP/1.1" 200 512 "-" "curl/8.5.0"
2001:db8:: - - [25/Sep/2026:10:12:05 +0000] "POST /api/pedidos HTTP/1.1" 201 88 "-" "Mozilla/5.0"
El seudónimo del correo será distinto en tu servidor porque depende de tu clave. Aplica el script a los logs rotados antes de moverlos al archivo, y borra después el original:
sudo anonimizar-log /var/log/nginx/access.log.2.gz /var/archivo/nginx/access-20260923.anon.log.gz
Crea antes el directorio de destino (sudo mkdir -p /var/archivo/nginx) y comprueba el resultado con zcat antes de borrar nada.
AdvertenciaLa expresión de IPv4 también transforma cualquier otro texto con forma de IP, como algunos números de versión en el User-Agent. Es un efecto aceptable para logs de archivo, pero revisa el resultado si otras herramientas procesan esos ficheros.
Paso 5: Comprobar que no se escapan datos personales
Repite las búsquedas del paso 1 sobre los logs nuevos y los archivados. No deberían aparecer correos en claro ni IPs completas:
sudo grep -cE '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}' /var/log/nginx/access.log
sudo grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' /var/log/nginx/access.log | grep -vc '\.0$'
0
0
El segundo comando cuenta las IPv4 al principio de la línea que no terminan en .0; debe devolver 0. Programa esta comprobación de vez en cuando: un cambio en la aplicación o en un bloque server puede volver a introducir datos personales sin que nadie lo note.
Solución de problemas
nginx: [emerg] unknown "remote_addr_anon" variable: Nginx no está cargando el fichero que contiene elmap. Comprueba que/etc/nginx/nginx.confincluye/etc/nginx/conf.d/*.confdentro del bloquehttp.- Todas las IPs aparecen como
0.0.0.0: la IP no encaja en ninguna expresión. Si Nginx está detrás de un proxy o un balanceador y usas el módulorealip, comprueba que$remote_addrcontiene la IP del cliente y no la del proxy. - Siguen apareciendo IPs completas en
access.log: algún bloqueserverdefine su propioaccess_logsin el formatoanonimizado. Búscalo congrep -rn access_log /etc/nginx/.
Conclusión
Nginx escribe ahora un log de acceso sin IPs completas ni parámetros de URL, conserva un log completo con retención corta para seguridad y dispones de un script para seudonimizar logs antiguos antes de archivarlos. Como siguientes pasos, revisa los logs de tu aplicación con las mismas búsquedas del paso 1, documenta en tu registro de actividades de tratamiento qué logs guardas y durante cuánto tiempo, y ajusta la retención de cada fichero con logrotate.
