LGPD

Anonimizar IP no nginx: mascarar o último octeto do log para a LGPD

Abra o access.log e conte quantos meses de IP completo de visitante estão ali. IP é dado pessoal na LGPD, e ninguém no time usa o octeto final: relatório de região, contagem de acesso e bloqueio por bot funcionam igual com 203.0.113.0. Guardar o número inteiro é risco sem contrapartida.

Um map e um formato de log novo

O bloco vai dentro de http, no /etc/nginx/nginx.conf, antes dos includes dos sites. O map cobre IPv4 e IPv6 e só é avaliado quando a variável aparece no log:

map $remote_addr $ip_anon {
    ~(?P<ip>d+.d+.d+).   "$ip.0";
    ~(?P<ip>[^:]+:[^:]+):      "$ip::";
    default                    "0.0.0.0";
}

log_format lgpd '$ip_anon - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" "$http_user_agent"';

access_log /var/log/nginx/access.log lgpd;

O IPv4 perde o último octeto e o IPv6 fica com os dois primeiros grupos, o que basta para saber a operadora sem identificar a linha. Rode sudo nginx -t antes do sudo systemctl reload nginx.

Não mate o fail2ban junto

Essa é a parte que quebra em silêncio: o fail2ban lê o IP do log para banir e, com tudo mascarado, passa a banir 203.0.113.0 ou nada. A saída é um segundo log com IP real e vida curta. Repare na herança: um access_log declarado no server block substitui o do http, então as duas linhas precisam estar lá dentro.

# no server block do site, as duas linhas
access_log /var/log/nginx/access.log lgpd;
access_log /var/log/nginx/seguranca.log combined;

# /etc/fail2ban/jail.local — comentário só em linha própria
[nginx-4xx]
enabled  = true
filter   = nginx-4xx
logpath  = /var/log/nginx/seguranca.log
maxretry = 20
findtime = 5m

Crie um /etc/logrotate.d/nginx-seguranca com daily e rotate 3 para esse arquivo. Três dias resolvem investigação de ataque e você para de acumular histórico que teria de justificar num pedido de titular.

Post Recentes

Prompt injection em campo de usuário: isole a observação do pedido

A observação do pedido chegou com ordem embutida para gerar cupom. Delimite o texto do cliente e trate a resposta…

10 horas atrás

Preço promocional em massa no WooCommerce: agendar a Black Friday pelo WP-CLI

Um CSV com SKU e preço, um wp eval-file que agenda início e fim da promoção guardando o estado anterior,…

15 horas atrás

Too many connections no MySQL: achar quem segura as conexões antes de subir o limite

O erro 1040 aparece quando todas as conexões estão ocupadas. Antes de subir o max_connections, veja quem está segurando cada…

1 dia atrás

WP_MEMORY_LIMIT no WordPress: resolver memória esgotada no PHP 8.3

Allowed memory size exhausted no painel: qual das duas constantes do wp-config.php mexer, onde conferir o valor real e por…

2 dias atrás

Converter data dd/mm/aaaa para o MySQL com DateTime::createFromFormat

strtotime com data brasileira devolve false e o INSERT quebra no MySQL 8. Use createFromFormat com getLastErrors antes de mandar…

4 dias atrás

Erro 500 no Magento 2: achar o relatório em var/report pelo ID da tela

O hash que o Magento mostra junto do erro é o nome de um arquivo em var/report. Veja como abrir,…

5 dias atrás