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écnicaEjemplo¿Sigue siendo dato personal?
Truncado (anonimización)203.0.113.45 pasa a 203.0.113.0Reduce mucho la identificación; en la práctica suele considerarse suficiente para estadísticas
EliminaciónQuitar la cadena de consulta de la URLNo, 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_anon en 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_user ni $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.

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:

  1. Pone a cero el último octeto de cualquier IPv4 de la línea.
  2. Recorta la IPv6 del principio de la línea (donde Nginx escribe la IP del cliente) a sus dos primeros grupos.
  3. 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.

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 el map. Comprueba que /etc/nginx/nginx.conf incluye /etc/nginx/conf.d/*.conf dentro del bloque http.
  • 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ódulo realip, comprueba que $remote_addr contiene la IP del cliente y no la del proxy.
  • Siguen apareciendo IPs completas en access.log: algún bloque server define su propio access_log sin el formato anonimizado. Búscalo con grep -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.