A requisição que você atendeu agora é um registro que você guarda
Tudo o mais nesta série aponta para fora. Esconder a origem, criptografar o disco, escolher a jurisdição, impedir que outra pessoa guarde um log seu. Este aqui aponta para a própria máquina, porque um servidor que responde requisições anota quem as fez — por padrão, em vários lugares ao mesmo tempo, com uma retenção que ninguém escolheu.
Veja o que uma imagem padrão está guardando quinze dias depois do deploy. /var/log/nginx/access.log tem uma linha por requisição: endereço, timestamp, caminho, referrer, user agent, rotacionado diariamente e mantido por quatorze dias. /var/log/auth.log tem cada sessão SSH, incluindo o endereço de origem de cada uma das suas. O journal tem os mesmos eventos de novo, mais o que quer que os seus serviços tenham impresso no stderr. Se o Docker estiver rodando, cada contêiner tem um arquivo de log JSON que, de fábrica, não tem limite de tamanho nenhum. Se o fail2ban estiver rodando, ele tem um log de cada endereço que já baniu e um banco de dados SQLite dizendo a mesma coisa, e os endereços em ambos estão completos.
Nada disso é malicioso, e a maior parte é genuinamente útil — durante mais ou menos uma hora depois que algo dá errado. O problema é o formato: fidelidade total, guardada por muito tempo, por acidente e não por decisão. A pergunta que vale a pena fazer sobre cada um desses arquivos não é "isso é sensível?", mas sim "o que eu realmente faria com a linha que escrevi três semanas atrás?" Para a esmagadora maioria das linhas na esmagadora maioria dos servidores, a resposta é nada, e um registro que você nunca vai ler é puro risco — diante de um comprometimento, de um backup que sobrevive à máquina, de quem mais cedo ou mais tarde vier pedir por ele.
Minimização de dados é o nome formal para a correção, e é a ideia menos controversa em proteção de dados: colete o que o trabalho exige, guarde enquanto o trabalho exigir, depois pare. O que vem a seguir aplica isso a uma máquina que você realmente administra, partindo do princípio de que você já passou pela primeira hora após a implantação e que há algo que vale a pena proteger na máquina.
SP·02Seis logs, e os dois que identificam pessoas
Antes de mudar qualquer coisa, conheça o inventário. Um VPS Debian ou Ubuntu pequeno rodando um serviço web normalmente escreve seis streams, e eles se sobrepõem mais do que as pessoas esperam — o mesmo evento frequentemente cai em três arquivos com três retenções diferentes.
O access log do nginx é o que identifica os seus visitantes. Uma linha por requisição, com o endereço do cliente, o caminho exato, o referrer e uma string de user-agent detalhada o bastante para ser, sozinha, um fingerprint fraco. O error log do nginx é o que as pessoas esquecem: ele registra client: 203.0.113.9 em todo timeout de upstream, todo 403, toda requisição malformada — e, diferente do access log, o formato dele é fixo e não pode ser personalizado com template.
O auth log — /var/log/auth.log na família Debian, /var/log/secure na família RHEL — identifica você. Toda linha de publickey aceita carrega o seu endereço de origem e o fingerprint da chave que abriu a sessão. Quem ler um mês dele descobre de quais redes você administra, em que horários, e quantas chaves distintas você possui. Em uma máquina de posse anônima, esse arquivo costuma ser mais revelador do que qualquer coisa que os seus visitantes tenham gerado.
O journal guarda uma cópia da maior parte do que já foi dito, mais o stdout e o stderr de cada unidade, e em uma instalação padrão ele pode crescer até 10% do sistema de arquivos, com teto de 4 GB, antes de começar a descartar as entradas mais antigas. Em qualquer disco de 40 GB ou mais, esse teto é os 4 GB completos, o que, nos volumes que um servidor pequeno produz, equivale a muitos meses.
Os logs de aplicação são o coringa. Um framework em modo debug registra URLs completas, incluindo query strings, e query strings costumeiramente carregam tokens de sessão, links de redefinição de senha e termos de busca. O PHP-FPM pode ser configurado para escrever o próprio access log, que duplica a linha de requisição que o nginx já escreveu, a partir de um arquivo que as suas configurações de nginx nunca tocam.
Os logs de contêiner são o silencioso da lista. O driver json-file, padrão do Docker, não tem rotação nenhuma a menos que você configure, então /var/lib/docker/containers/*/*-json.log guarda tudo o que o contêiner já disse desde que foi criado. É uma forma comum de descobrir que uma política de retenção de "14 dias" na verdade está guardando onze meses, e é o mesmo tipo de surpresa que as regras de firewall que o Docker escreve por trás do ufw.
Dos seis, dois carregam identificadores de seres humanos que vale a pena minimizar de propósito: o access log do nginx (os seus visitantes) e o auth log (você). Os demais, na maior parte, só precisam de um teto de tamanho e um relógio mais curto.
SP·03Trunque no momento da escrita, não na rotação
O instinto é continuar registrando normalmente e limpar depois — um cron noturno que reescreve o arquivo de ontem com os endereços removidos. Não construa isso. Um job de limpeza significa que os endereços brutos realmente existiram no disco por até um dia inteiro, e durante esse dia eles foram capturados pelo seu backup, possivelmente copiados pelo snapshot em nível de bloco do seu provedor, e deixados em qualquer coisa que o sistema de arquivos tenha feito com os blocos antigos. Pior, o job é uma peça móvel: ele falha silenciosamente na semana em que o disco enche, e nada te avisa que o arquivo de ontem ainda está completo.
A única redução em que você realmente pode confiar é a que acontece antes de a linha ser escrita. No nginx isso é um bloco map avaliado no momento do log, que reescreve o endereço em uma variável nova, e um log_format que usa essa variável nova em vez de $remote_addr. O endereço completo nunca chega a ser serializado. Não há nada para limpar depois, nada para agendar, e nada para dar errado no dia em que você não está olhando.
# /etc/nginx/conf.d/00-privacy-log.conf -- http context, loaded before the sites
map $remote_addr $ip_trunc {
# IPv4: keep the /24, zero the host part
~(?<v4>\d+\.\d+\.\d+)\. "${v4}.0";
# IPv6: keep the first two groups, drop the rest
~(?<v6>[^:]+:[^:]+): "${v6}::";
# anything the two patterns cannot parse -- including compressed forms
# like ::1 -- falls through here, i.e. fails closed rather than open
default "0.0.0.0";
}
log_format privacy '$ip_trunc - - [$time_local] "$request" $status '
'$body_bytes_sent "$http_referer" "$http_user_agent" '
'rid=$request_id rt=$request_time';
Três detalhes decidem se isso realmente funciona. Primeiro, o map precisa viver no contexto http — dentro de um bloco server o nginx se recusa a iniciar. Colocá-lo em conf.d/ com um nome que ordene cedo é a forma mais simples de garantir isso.
Segundo, se você está atrás da Cloudflare ou de qualquer outra edge, verifique o que $remote_addr contém. O módulo realip o substitui pelo endereço real do cliente logo no início do processamento da requisição, e mantém o endereço de conexão em $realip_remote_addr. Essa ordem está a seu favor: o map roda no momento do log, então ele trunca o visitante real, e não a edge. Mas isso também significa que, se você não configurou o realip, você está truncando o endereço da Cloudflare e não aprendendo nada, enquanto o endereço verdadeiro fica sentado em um header.
Terceiro, audite o resto da format string. Truncar $remote_addr não resolve absolutamente nada se a linha ainda terminar com "$http_x_forwarded_for" ou carregar $http_cf_connecting_ip — e boa parte dos formatos padrão e de painel de controle inclui um desses. O endereço completo está nos headers da requisição; ele só fica de fora do arquivo se você deixar todo header que o carrega fora do format.
Note o que o substituiu: $request_id, uma string hexadecimal aleatória de 32 caracteres que o nginx gera por requisição. Esse é o eixo da próxima seção.
O que o truncamento quebra, e os três trabalhos que um log realmente faz
Sempre tem alguém que objeta que logs anonimizados são inúteis, e essa pessoa tem razão pela metade — porque "os logs" são três trabalhos sem relação nenhuma vestindo um único nome de arquivo, e só um deles precisa do endereço.
Trabalho um: bloquear quem estiver martelando você agora. Isso precisa do endereço completo, e precisa dele em segundos. Não precisa dele amanhã. O fail2ban é a ferramenta de sempre, e ele genuinamente não consegue funcionar com um arquivo truncado — banir 203.0.113.0 bane um host inocente e deixa o atacante conectado. Mas esse trabalho fica satisfeito com um arquivo que existe por um dia, ou sem usar arquivo nenhum: o próprio limit_req e limit_conn do nginx guardam o estado em memória compartilhada, agem em microssegundos em vez de no intervalo de polling do fail2ban, e não escrevem nada em disco.
Trabalho dois: descobrir por que aquela requisição retornou um 500. Isso precisa de correlação, não de identidade. Um request ID que atravessa do nginx até a aplicação amarra a linha do access log, o erro de upstream e o stack trace da aplicação a um único evento — que é o que você realmente estava tentando fazer quando recorria ao endereço. Na prática o ID é melhor: ele sobrevive a um cliente em rede móvel cujo endereço muda no meio da sessão, e não perde validade quando quatro visitantes compartilham um endereço CGNAT.
Trabalho três: entender o tráfego ao longo do tempo. Volume, mistura de status codes, quais caminhos estão quentes, se o crawler está fora de controle. Um endereço truncado é suficiente aqui, e o /24 que sobrevive ao truncamento já basta para ver que uma única rede é responsável por 40% das suas requisições.
Então o desenho não é "registrar menos", mas dividir por relógio: um stream de fidelidade total que vive por um dia e alimenta as ferramentas de bloqueio, e um stream truncado que vive pelo tempo que você quiser manter estatísticas. Os dois são escritos pelo nginx no mesmo instante, então não há etapa de processamento no meio, nem uma janela em que a coisa errada fique em disco por mais tempo do que deveria.
# inside the server block, or in a snippet included by it
access_log /var/log/nginx/access.log privacy; # truncated, keep for weeks
access_log /var/log/nginx/security.log secip; # full address, keep for a day
# static assets are pure noise in a privacy log -- drop them entirely
location ~* \.(?:css|js|svg|png|jpe?g|webp|woff2?|ico)$ {
access_log off;
expires 30d;
}
O formato secip é uma linha só, ao lado do outro — $remote_addr, o timestamp, a requisição e o status, nada mais. É o único arquivo na máquina onde um endereço de visitante completo tem permissão para ficar, o que torna a sua retenção uma única decisão em um único lugar, em vez de uma propriedade sobre a qual você precisa raciocinar espalhada por seis arquivos.
journald, auth.log, e o rastro que leva de volta até você
A privacidade do visitante é a parte sobre a qual todo mundo escreve. O rastro do administrador é a parte que importa em uma máquina cujo objetivo inteiro é que o seu nome não esteja vinculado a ela, e ele está quase inteiramente em dois lugares.
/var/log/auth.log registra uma linha Accepted publickey para cada sessão que você abre, com o seu endereço de origem e o fingerprint da sua chave. Ao longo de um mês, isso é uma agenda dos seus hábitos de trabalho e uma lista das redes que você usa. Se você sempre se conecta pelo mesmo túnel, isso é um único endereço repetido e relativamente sem graça. Se você se conecta de onde quer que esteja, isso é um histórico de viagens.
O journal guarda os mesmos eventos, mais tudo o que as suas units imprimiram, e os padrões dele são generosos: SystemMaxUse= é 10% do sistema de arquivos, e MaxRetentionSec= não está definido, o que significa que não há limite de tempo nenhum — só um limite de tamanho. Em um servidor tranquilo, essa combinação retém meses.
Existe uma troca real aqui, e ela merece ser dita sem rodeios, não apenas mencionada de passagem. Os logs que descrevem você são os mesmos logs que dizem como alguém entrou. Defina Storage=volatile e o journal passa a viver só na RAM, desaparecendo a cada reboot — genuinamente privado, e genuinamente inútil na manhã em que você encontrar um processo que não iniciou, porque o primeiro reboot do seu atacante apagou a evidência. Para a maioria das pessoas, o meio-termo sensato é armazenamento persistente com um teto rígido e um relógio curto: longo o bastante para investigar um incidente que você percebe em até uma semana, curto o bastante para que o arquivo não seja um diário.
Duas notas de implementação que pegam as pessoas de surpresa. Imagens Debian e Ubuntu diferem quanto a ter o rsyslog instalado ou não; se /var/log/auth.log existe na sua máquina, então é o rsyslog que está escrevendo nele, e os limites do journald não governam esse arquivo — isso é trabalho do logrotate. E ForwardToSyslog= é o que alimenta o rsyslog a partir do journal, então desativá-lo em uma máquina que tem os dois evita que você fique guardando duas cópias de tudo sob duas políticas de retenção diferentes.
A sua política de retenção é o que os seus backups disserem que é
Esta é a que desfaz todo o trabalho cuidadoso feito até aqui, e ela é invisível a menos que você vá procurar.
Digamos que o logrotate mantenha quatorze dias de logs do nginx, e você esteja satisfeito com isso. Agora acrescente o backup fora do site que você fez certo em configurar: uma execução diária do Borg ou do restic, com retenção de sete diários, quatro semanais e seis mensais. Cada um desses arquivos contém o /var/log como ele estava no dia em que rodou. O arquivo mensal mais antigo tem seis meses e guarda os quatorze dias de logs que eram atuais naquele momento. A sua retenção efetiva de logs não é quatorze dias. São seis meses, em um repositório criptografado que você não consegue dar grep sem restaurar, em uma segunda máquina, em um país diferente.
Existem exatamente duas soluções honestas. Excluir os diretórios de log do backup — eles são a única coisa em um servidor que você quase sempre consegue reconstruir ou passar sem, e uma restauração que omite o /var/log não é uma restauração pior. Ou incluí-los de propósito e aceitar que a sua retenção real é a do repositório, e nesse caso diga isso em qualquer política que você publicar, porque a alternativa é uma política declarada que a sua própria infraestrutura contradiz.
# exclude logs from the backup, and prove it took
borg create --stats \
--exclude '/var/log' \
--exclude '/var/lib/docker/containers' \
::'{hostname}-{now:%Y-%m-%d}' /
# the check that matters: can you still find an address in the newest archive?
borg list ::"$(borg list --short --last 1)" | grep -c '^var/log/' || echo "clean"
Já que você está nesse estado de espírito, mais dois vizinhos do mesmo problema. Os snapshots do seu provedor não são seus. Um snapshot em nível de hypervisor captura o disco como ele estava, incluindo logs que você já rotacionou para fora desde então, e ele vive no armazenamento do provedor, sob a retenção do provedor — o que é mais um motivo para os endereços nunca terem sido escritos por completo, e não um motivo para fazer algo esperto depois.
E não recorra ao shred em um VPS. Sobrescrever um arquivo em um disco virtual thin-provisioned, sobre um sistema de arquivos copy-on-write, em cima de um SSD que remapeia blocos para wear-levelling, não sobrescreve de forma confiável as células físicas que o guardavam. Exclusão segura em armazenamento alugado e virtualizado é teatro. A redução que funciona é a que você fez no momento da escrita; tudo depois disso é um melhor esforço que você não consegue verificar. Criptografar o volume muda essa equação — mas muda antes de os dados serem escritos, o que é a mesma lição de novo.
As cópias que você não controla
Minimização na sua própria máquina é uma camada entre várias, e ter clareza sobre as outras é o que impede que isso vire uma falsa sensação de completude.
A rede da sua hospedagem vê o registro de fluxo. Origem, destino, portas, bytes, timing — de cada conexão que entra e sai da máquina, quer você registre alguma coisa, quer não. Nenhuma configuração no servidor muda isso. É boa parte do motivo pelo qual a jurisdição em que a máquina está é uma variável real, e não uma de marketing; o que é exigido por lei da rede difere enormemente de país para país.
A sua CDN ou edge registra os próprios logs, na borda. Se a Cloudflare termina o TLS para você, ela tem a linha da requisição e o endereço do cliente antes de o seu servidor sequer entrar em cena, na própria agenda de retenção dela, sujeita ao próprio processo legal dela. Truncar o seu log de origem não alcança isso retroativamente. Rodar uma edge que você mesmo possui é a versão disso que você realmente consegue configurar — e as regras de log deste guia se aplicam primeiro à máquina de edge, já que é lá que os endereços não truncados chegam.
Rastreadores de erro e analytics tiram isso da máquina por você. O Sentry e a maioria dos concorrentes dele anexam o IP do cliente a cada evento por padrão; a configuração normalmente se chama algo como send_default_pii, e vale a pena checar em vez de presumir. Qualquer analytics hospedado é, por construção, uma cópia de terceiro do access log que você acabou de passar uma tarde truncando.
E-mail é o mais vazador de todos. Se qualquer coisa na máquina manda e-mail, os headers carregam o host e o endereço de quem enviou, e cada relay no caminho guarda uma cópia do envelope com timestamps. Um formulário de contato que te manda e-mail é um log que você não administra.
Nada disso torna o trabalho local inútil — a cópia local é a que é apreendida junto com a máquina, exfiltrada em uma invasão, ou entregue por você mesmo. Ela é simplesmente a única camada que você controla por completo, e tratá-la como o quadro inteiro é que é o erro.
SP·08Minimização por design, não exclusão ao ser notificado
Vale a pena ser preciso aqui, porque as duas coisas são confundidas, e a diferença entre elas é a diferença inteira entre prática de engenharia padrão e algo que você não deveria fazer.
Decidir de antemão o que o seu serviço coleta e por quanto tempo guarda isso é prática comum, documentada e incentivada. Sob o GDPR, são dois dos princípios centrais — minimização de dados e limitação de armazenamento — e uma retenção de log mais curta é um controle que os auditores pedem, não um que eles questionam. Não existe obrigação geral, para um operador de site ou um cliente de hospedagem na UE, de reter logs de tráfego; a diretiva de retenção geral que antes sugeria o contrário foi derrubada pelo Tribunal de Justiça da União Europeia em 2014, e as leis nacionais que sobrevivem a isso majoritariamente vinculam provedores de telecomunicações, não pessoas que rodam um servidor web. Os Estados Unidos também não têm nenhum mandato geral de retenção para operadores de site.
Destruir registros específicos depois de você ter sido notificado sobre eles é um ato completamente diferente. Um pedido de preservação de dados, uma retenção determinada por litígio, uma ordem judicial ou uma investigação policial muda o que você pode fazer com os dados que existem naquele momento, e "minha política de retenção apagou isso" não é uma defesa se você acelerou a exclusão por causa da notificação. Nada neste guia é sobre isso. Uma política de retenção é algo que você define em uma terça-feira qualquer e depois deixa quieto; se ela só é encurtada quando alguma coisa acontece, ela nunca foi uma política.
A mesma distinção atravessa o resto deste site: existe uma linha real entre engenharia de privacidade e a postura "bulletproof" que se vende como imunidade. Desenhar um serviço que nunca acumula um histórico de visitantes fica confortavelmente do lado certo dessa linha, na mesma categoria de criptografar os seus discos e não pedir aos usuários um endereço de e-mail que você não precisa.
Duas consequências práticas. Se você publica uma política de privacidade, faça os números de retenção dela baterem com o que realmente está no disco — quatorze dias declarados e seis meses reais em um repositório de backup é o tipo de lacuna que transforma um operador de boa-fé em um de má-fé no papel. E anote a decisão para você mesmo, em um comentário no topo do arquivo do logrotate se não houver outro lugar, porque a pessoa que vai ter que justificar esses números daqui a dezoito meses é você, e ela não vai lembrar por que o número era sete. Nada do que foi dito acima é aconselhamento jurídico; se você opera em algum lugar com deveres de retenção específicos de setor, verifique-os contra a sua própria situação antes de encurtar qualquer coisa.
SP·09Passo a passo
-
01
Descubra o que a máquina já está guardando
Não configure nada antes de medir. O objetivo desta etapa é achar o arquivo que você esqueceu, que na maioria das máquinas é um log de contêiner ou um log de aplicação que ninguém olha desde o deploy.
# biggest log files anywhere on the box, largest last sudo du -ah /var/log /var/lib/docker/containers 2>/dev/null \ | sort -h | tail -20 # how much disk the journal holds, and how far back it goes journalctl --disk-usage journalctl --output=short-iso | head -1 # how old is the oldest nginx line still on disk? zcat -f /var/log/nginx/access.log* 2>/dev/null | head -1
Depois olhe uma linha de cada arquivo e pergunte o que ela identifica. O comando abaixo conta quantos endereços completos e distintos estão atualmente recuperáveis a partir dos seus logs web — normalmente é esse número que justifica o resto deste guia.
zcat -f /var/log/nginx/access.log* 2>/dev/null \ | grep -oE '^([0-9]{1,3}\.){3}[0-9]{1,3}' | sort -u | wc -lAnote o que você encontrar. Você vai rodar esses mesmos comandos no final para provar que a mudança fez efeito.
-
02
Trunque o endereço do cliente antes de o nginx escrever a linha
Crie o map e os dois formatos em um arquivo carregado dentro do contexto
http. No Debian e no Ubuntu,/etc/nginx/conf.d/é incluído a partir donginx.confantes das configurações dos sites, que é exatamente onde isso pertence.sudo tee /etc/nginx/conf.d/00-privacy-log.conf > /dev/null <<'EOF' # The client address is reduced here, at log time, and never written in full # to the long-retention file. $realip_remote_addr still holds the connecting # address if you need it while debugging a proxy problem. map $remote_addr $ip_trunc { ~(?<v4>\d+\.\d+\.\d+)\. "${v4}.0"; ~(?<v6>[^:]+:[^:]+): "${v6}::"; # anything the two patterns cannot parse -- including compressed forms # like ::1 -- falls through here, i.e. fails closed rather than open default "0.0.0.0"; } # Long retention: no full address, no forwarded-for header, request id instead. log_format privacy '$ip_trunc - - [$time_local] "$request" $status ' '$body_bytes_sent "$http_referer" "$http_user_agent" ' 'rid=$request_id rt=$request_time'; # Short retention: the one file allowed to hold a complete address. log_format secip '$remote_addr [$time_local] "$request" $status'; EOF sudo nginx -t && sudo systemctl reload nginxAgora aponte o site para eles. No seu bloco server, substitua a linha
access_logexistente pelo par, e desligue o log para os assets estáticos já que está nisso.access_log /var/log/nginx/access.log privacy; access_log /var/log/nginx/security.log secip;
Recarregue, carregue uma página, e leia o resultado. O primeiro campo deve terminar em
.0, e a linha deve carregar um valorrid=.sudo nginx -t && sudo systemctl reload nginx curl -s -o /dev/null https://your-domain.example/ sudo tail -1 /var/log/nginx/access.log
Se o endereço ainda estiver completo, o bloco server está sobrescrevendo o formato em algum lugar mais abaixo —
grep -rn access_log /etc/nginx/encontra a linha que vence. -
03
Mantenha endereços completos só onde algo age sobre eles
Se o fail2ban estiver rodando, ele está atualmente lendo o arquivo que você acabou de truncar. Aponte-o para o log de segurança em vez disso, e dê a esse log uma vida de um dia para que os endereços completos expirem sozinhos.
sudo tee /etc/fail2ban/jail.d/nginx-privacy.local > /dev/null <<'EOF' [nginx-http-auth] enabled = true logpath = /var/log/nginx/error.log [nginx-botsearch] enabled = true logpath = /var/log/nginx/security.log maxretry = 6 findtime = 10m bantime = 1h EOF sudo fail2ban-client reload
Depois cuide da própria memória do fail2ban, que a maioria das pessoas nunca toca: ele mantém o próprio log de banimentos sob o padrão
rotate 4 weekly, e um banco de dados SQLite de cada banimento que já emitiu.dbpurgeageé o que expira linhas desse banco de dados — defina algo próximo do seu banimento mais longo, em vez de deixar no padrão.sudo tee /etc/fail2ban/fail2ban.d/retention.local > /dev/null <<'EOF' [Definition] dbpurgeage = 2d loglevel = NOTICE EOF sudo systemctl restart fail2ban
Melhor ainda, para floods simples, faça o trabalho no nginx, onde nada é registrado.
limit_reqguarda os contadores em memória compartilhada, responde em microssegundos em vez de em um intervalo de polling, e não deixa registro nenhum de quem foi limitado — veja o runbook da primeira hora de DDoS para dimensionar as zonas sob carga real.# http context: keyed on the truncated address, so nothing complete is # held in memory either. 10m of shared state is plenty for a small site. limit_req_zone $ip_trunc zone=perip:10m rate=20r/s; # in the location you want protected limit_req zone=perip burst=40 nodelay;
Definir a chave da zona por
$ip_truncem vez de$binary_remote_addré uma troca deliberada: o limite agora se aplica a um/24inteiro de uma vez, então um escritório movimentado atrás de um único bloco compartilha um orçamento. Para um site pequeno isso costuma ser bom, e ocasionalmente uma melhoria; se não for, defina a chave da zona pelo endereço completo — o estado de um rate limiter vive em memória compartilhada e nunca é escrito em disco, então isso não faz parte do que você está minimizando aqui. -
04
Limite o journal e pare a segunda cópia
O journald aceita um drop-in, que sobrevive a atualizações de pacote de um jeito que editar o
journald.confnão sobrevive. Os valores abaixo mantêm cerca de uma semana — o suficiente para investigar algo que você percebe na segunda-feira e que começou na sexta — dentro de um teto rígido de 200 MB.sudo mkdir -p /etc/systemd/journald.conf.d sudo tee /etc/systemd/journald.conf.d/retention.conf > /dev/null <<'EOF' [Journal] Storage=persistent SystemMaxUse=200M SystemMaxFileSize=20M MaxRetentionSec=7day MaxFileSec=1day ForwardToSyslog=no EOF sudo systemctl restart systemd-journald journalctl --disk-usage
O restart aplica os tetos de tamanho imediatamente; o relógio de retenção é aplicado conforme novos arquivos são rotacionados, então um journal já maior do que deveria encolhe na próxima rotação em vez de instantaneamente.
journalctl --vacuum-time=7dforça isso agora, se você quiser o disco de volta hoje.ForwardToSyslog=noimporta em qualquer imagem que venha com o rsyslog: sem isso, toda entrada do journal também é anexada a/var/log/syslog, sob a agenda do logrotate em vez da do journald, e você fica com duas cópias com duas datas de expiração diferentes. Verifique em qual situação você está antes de presumir:systemctl is-active rsyslog 2>/dev/null || echo "rsyslog not running" ls -la /var/log/auth.log /var/log/syslog 2>/dev/null
Se esses arquivos existirem, o rsyslog é dono deles, e o passo seis é onde a retenção deles é definida. Se não existirem, o journal é a única cópia, e você acabou de limitá-la.
-
05
Impeça a aplicação de registrar de novo o que você removeu
Nada do que foi feito até aqui toca a sua aplicação, e uma aplicação no modo errado vai escrever alegremente o endereço completo, a URL completa e o token de sessão em um arquivo próprio dela. Três coisas para checar.
O PHP-FPM traz uma diretiva
access.logna configuração do pool, comentada por padrão mas ativada por muitos painéis de controle. Se estiver ativa, é uma segunda cópia de cada linha de requisição, a partir de um arquivo que o seu trabalho no nginx nunca tocou.grep -rn '^access.log' /etc/php/*/fpm/pool.d/ || echo "fpm access log off"
O nível de log do seu framework decide se query strings e corpos de requisição vão parar no disco. O modo debug, na maioria dos frameworks, registra a URL completa, e um link de redefinição de senha é uma URL completa. Defina o nível de produção e confirme que é realmente esse o nível carregado, e não o do arquivo que você acha que está sendo lido.
O driver padrão do Docker nunca rotaciona. Corrija isso no nível do daemon, para que todo contêiner futuro herde o teto. Note que isso se aplica a contêineres criados depois do restart — os existentes mantêm o arquivo atual, sem limite, até serem recriados.
sudo tee /etc/docker/daemon.json > /dev/null <<'EOF' { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } EOF sudo systemctl restart docker docker inspect --format '{{.HostConfig.LogConfig}}' $(docker ps -q) 2>/dev/nullSe um contêiner já está segurando um arquivo grande, recriá-lo com
docker compose up -d --force-recreateé o que realmente trunca o histórico — só reiniciar mantém o mesmo arquivo de log. -
06
Defina a retenção de propósito, em um único lugar por arquivo
O logrotate é onde vive o relógio de tudo que o rsyslog e o nginx escrevem. Edite o bloco já existente em vez de adicionar um segundo: dois blocos nomeando o mesmo caminho fazem o logrotate falhar com um erro de entrada duplicada e parar de rotacionar esse arquivo por completo, que é a forma mais comum de uma mudança de retenção silenciosamente virar uma retenção infinita.
# check for duplicates BEFORE editing, then again after sudo logrotate --debug /etc/logrotate.conf 2>&1 | grep -i 'duplicate\|error'
No caso do nginx, os dois arquivos querem relógios diferentes: o truncado pode viver por semanas, o que guarda endereços completos não deveria sobreviver ao dia. Adicione um bloco separado para o log de segurança — um caminho diferente, então sem duplicata — e encurte o que já vem pronto.
sudo tee /etc/logrotate.d/nginx-security > /dev/null <<'EOF' # The only file on this box that holds complete client addresses. # One day, uncompressed so fail2ban can read it. Do not lengthen without # a reason you would be happy to write down here. /var/log/nginx/security.log { daily rotate 1 maxage 1 missingok notifempty nocompress create 0640 www-data adm sharedscripts postrotate [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid` endscript } EOF sudo sed -i 's/^\trotate 14$/\trotate 7/' /etc/logrotate.d/nginx sudo logrotate --debug /etc/logrotate.d/nginx-securityFaça a mesma aritmética para
/etc/logrotate.d/rsyslogse o rsyslog estiver instalado — os padrões dele mantêm quatro semanas deauth.log, o que são quatro semanas das suas próprias sessões SSH. E rode a passagem de debug mais uma vez: ela imprime exatamente quais arquivos ela rotacionaria e apagaria, o que é a única forma de confirmar que os números que você acabou de digitar são os números em vigor. -
07
Prove isso, inclusive através do backup
Rode de novo as medições do passo um. A contagem de endereços completos e distintos no log de retenção longa agora deve parar de crescer, e depois de um ciclo de rotação ela deve ser zero.
# should print 0 once the pre-change files have rotated out zcat -f /var/log/nginx/access.log* 2>/dev/null \ | grep -oE '^([0-9]{1,3}\.){3}[0-9]{1,3}' \ | grep -v '\.0$' | sort -u | wc -l # and the journal should now be bounded journalctl --disk-usageDepois, a verificação que quase ninguém faz: olhe dentro do backup. Um repositório que ainda carrega
/var/logestá guardando a fidelidade e a retenção que você acabou de passar uma tarde removendo, e vai continuar guardando enquanto o seu arquivo mais antigo sobreviver.borg list | head -3 # borg lists oldest first borg list ::"$(borg list --short | head -1)" 2>/dev/null \ | grep -c '^var/log/' || echo "no logs in oldest archive"
Se a contagem for diferente de zero, ou você adiciona a exclusão de antes e deixa os arquivos antigos expirarem, ou você os poda deliberadamente. Até que uma dessas coisas aconteça, a sua retenção real é a do repositório — que é a frase mais útil deste guia inteiro, e a mais fácil de esquecer.
Por fim, anote os números em algum lugar onde a próxima pessoa vá encontrá-los: a retenção que você escolheu, o motivo, e a data. Um comentário no topo do arquivo do logrotate já basta. A configuração acima é uma decisão, e uma decisão que ninguém consegue reconstruir daqui a um ano vira um padrão de novo.


