
Proteger um novo VPS na primeira hora
Um endereço IPv4 público é sondado a poucos minutos do seu primeiro pacote, por máquinas que nunca ouviram falar de você e nunca vão ouvir. Isso é uma boa notícia: quase tudo o que mira um servidor novo é genérico, e uma hora bem empregada derrota quase tudo isso. A mesma hora, feita na ordem errada, tranca você para fora de uma máquina em que ninguém pode fazer login no seu lugar.
Entregamos um shell root cerca de 47 segundos depois que o pagamento é confirmado e então, deliberadamente, paramos por aí. Não instalamos nenhum agente, não gerenciamos seu firewall, e não guardamos uma cópia das suas credenciais — uma chave que guardamos é uma chave que podemos ser obrigados a entregar, e esse princípio atravessa toda a plataforma. A consequência é simples e vale a pena dizer sem rodeios: a segurança do seu servidor é o estado em que você o deixa durante a primeira hora.
O que vem a seguir é essa hora, na ordem em que nós mesmos a executamos, no Debian 13 e no Ubuntu 24.04. Duas configurações fazem a maior parte do trabalho. O resto desta página existe por causa de três coisas que parecem corretas, passam na checagem final de qualquer tutorial, e estão silenciosamente erradas: um drop-in de SSH que é sobrescrito silenciosamente, uma configuração de porta que um sshd ativado por socket ignora, e um runtime de contêineres que publica portas por baixo do seu firewall. Cada uma delas já pegou alguém que fez tudo o mais certo.
O que realmente está batendo na porta
Observe o journalctl -u ssh em um servidor que está online há uma hora e o volume vai assustar na primeira vez que você ver. Não deveria. O que você está vendo é a radiação de fundo da internet: um punhado de operações de varredura, algumas acadêmicas, algumas comerciais, algumas criminosas, que enumeram o espaço IPv4 inteiro continuamente e entregam os resultados a bots de adivinhação de credenciais. Seu endereço foi alcançado porque existe, em ordem numérica, e será alcançado de novo em algumas horas, faça você o que fizer.
Isso muda o formato do problema de um jeito útil. Você não está se defendendo de um adversário que escolheu você e vai se adaptar; você está se defendendo de um script com um repertório fixo — root, admin, ubuntu, test, git, oracle, postgres, e as vinte mil senhas que já apareceram em vazamentos de dados. Ele não tem paciência, não tem criatividade e nenhum interesse em uma máquina que não responde de primeira. Desligar a autenticação por senha não desacelera esse atacante. Isso o tira do tabuleiro por completo.
Dois detalhes valem a pena saber. Primeiro, o IPv6 é dramaticamente mais silencioso, porque um /64 não pode ser varrido por completo — mas no momento em que seu registro AAAA se torna público, ou o endereço da sua máquina aparece em um cabeçalho de e-mail ou em um log de transparência de certificados, o silêncio acaba. Nunca trate o IPv6 como um esconderijo; trate-o como um palheiro menor. Segundo, um endereço IPv4 tem um passado. Ele teve um inquilino antes de você, e se esse inquilino configurou mal um servidor de e-mail ou hospedou algo que acabou em uma lista negra, você herda a reputação até ela se dissipar. Se e-mail importa para você, verifique o endereço nas blocklists de sempre antes de construir em cima dele — é uma checagem de cinco minutos que evita duas semanas depurando entregabilidade.
As duas configurações que fazem noventa por cento do trabalho
Quase todo comprometimento real de um servidor pequeno começa em um de dois lugares: uma senha que podia ser adivinhada, ou um serviço que estava escutando e não deveria estar. As correções correspondentes são PasswordAuthentication no e um firewall com política padrão drop. Não são glamourosas, juntas levam quinze minutos, e valem mais do que todos os outros controles desta página somados.
O motivo é estrutural, não estatístico. As duas são fechadas por padrão — falham para o lado seguro. Um sshd somente com chaves não pode sofrer brute-force não importa quantas tentativas cheguem, porque não existe caminho no código que aceite uma senha. Um firewall com bloqueio padrão protege serviços que você ainda nem instalou, incluindo o banco de dados que você vai adicionar daqui a três meses e esquecer de vincular ao localhost. Tudo o mais em um checklist de hardening é enumeração do que pode dar errado: uma lista de coisas específicas para desligar, que só é completa até onde a lista alcança.
Então, se você só vai ler uma seção e fechar a aba, leia esta, faça os passos 2 a 5 abaixo, e considere a hora bem gasta. O resto é genuinamente útil e genuinamente secundário.
Onde a configuração do SSH mora agora, e a armadilha nela
No Debian 13 e no Ubuntu 24.04, o /etc/ssh/sshd_config começa com uma linha Include /etc/ssh/sshd_config.d/*.conf, e as imagens cloud vêm com um arquivo nesse diretório — geralmente 50-cloud-init.conf — que já define PasswordAuthentication. Editar o arquivo principal e colar suas próprias diretivas no final parece natural e produz uma configuração que não faz o que diz que faz.
A regra é a única coisa sobre o sshd que surpreende quase todo mundo: para cada palavra-chave, o primeiro valor obtido vence. Isso é o oposto de quase todo outro sistema de mesclagem de configuração que você já usou. O Include fica perto do topo do arquivo principal, então um drop-in é lido antes do corpo do sshd_config — e entre os drop-ins, a ordem alfabética decide. Um arquivo chamado 10-hardening.conf vence o 50-cloud-init.conf. Um arquivo chamado 60-hardening.conf perde para ele, silenciosamente, e você não vai descobrir isso por um restart que reporta sucesso.
É por isso que você nunca deve confiar no arquivo que acabou de escrever. Pergunte ao daemon o que ele realmente resolveu:
sshd -t # syntax check — silence means valid
sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractive|allowusers|port) 'O sshd -T imprime a configuração efetiva depois que todo include, override e valor padrão já foi resolvido. Se essa saída disser passwordauthentication yes, a autenticação por senha está ligada, não importa o que qualquer arquivo no disco afirme. Nada além disso conta como verificação.
A segunda armadilha é da mesma família. O Ubuntu 24.04 inicia o sshd por ativação de socket: o ssh.socket é dono da porta de escuta, e o Port em sshd_config é completamente ignorado. Verifique com systemctl is-enabled ssh.socket; se estiver habilitado e você quiser uma porta diferente, mude-a com systemctl edit ssh.socket e um override ListenStream= — não no sshd_config, onde a configuração vai parecer certa e não vai fazer nada.
Tirar o SSH da porta 22: teatro, mas teatro barato
Vamos ser honestos quanto a isso, porque a internet não é. Mudar o sshd para a porta 2222 ou 47000 não impede nenhum atacante competente. Uma varredura TCP completa em um único host leva segundos, o serviço se anuncia no banner, e quem quer que tenha decidido atacar você especificamente vai encontrar antes de terminar o café.
O que essa mudança realmente faz é cortar o seu log de autenticação em algo como noventa e cinco por cento, e isso tem valor real: é a diferença entre logs que você só passa o olho e logs que você de fato lê. Sinal que você consegue ver vence sinal que está enterrado. Se você monitora qualquer coisa, um auth.log silencioso é a forma mais barata de tornar uma anomalia visível.
Os custos são pequenos, mas são reais, e são o motivo de chamarmos isso de opcional em vez de recomendado. Você vai esquecer a porta quando voltar daqui a oito meses. Uma porta não padrão acima de 1024 pode, em princípio, ser tomada por um processo sem privilégios se o sshd algum dia parar de escutar. Firewalls de saída restritivos em redes de clientes bloqueiam portas incomuns, então ocasionalmente você não vai conseguir alcançar seu próprio servidor a partir de um escritório ou de um hotel. E em um sistema ativado por socket você precisa mudar a porta no lugar certo, conforme a seção anterior. Faça isso se logs mais silenciosos importam para você. Não faça isso e depois se sinta seguro, e nunca faça isso no lugar de usar somente chaves.
Bloqueio padrão, e o ruleset que realmente usamos
Um firewall que lista o que bloquear é um sistema de arquivamento. Um firewall que lista o que permitir é um controle de segurança. A distinção é o jogo inteiro, porque só o segundo cobre o serviço que você vai instalar mês que vem, a porta de debug que você abriu numa sexta-feira, e o contêiner que resolveu se expor.
Na nossa plataforma você tem duas camadas, e elas são independentes. O filtro de borda é opcional, configurado por servidor no painel e aplicado no hypervisor, então o tráfego bloqueado nunca chega sequer ao seu guest — útil para regras de camada 4 que você quer aplicadas antes que o seu kernel gaste um ciclo com elas, e para manter um guest comprometido inalcançável enquanto você trabalha nele. O firewall do guest é inteiramente seu: nftables, iptables, pf, o que a sua imagem trouxer. Nós nunca tocamos nele. Use os dois se a máquina importa; use pelo menos o segundo sempre.
Duas coisas no ruleset do passo 5 merecem explicação, porque a maioria dos rulesets copiados e colados erra nelas. Não descarte todo o ICMP. Parece uma boa faxina, mas quebra o path-MTU discovery, o que produz a pior classe de bug que existe: requisições pequenas funcionam, respostas grandes travam, e nada nos seus logs menciona o firewall. Aceite no mínimo destination-unreachable, time-exceeded e parameter-problem. Não filtre o ICMPv6 por tipo a menos que você conheça a lista. O IPv6 depende do ICMPv6 para neighbour discovery e router advertisement; bloqueie-o de forma ampla e sua conectividade IPv6 morre de um jeito que parece problema de roteamento. Aceitar todo o ICMPv6 em um único host é uma troca razoável, e o ruleset abaixo faz exatamente isso.
Um aviso antes de você rodar flush ruleset: se o Docker estiver instalado, essa linha remove as regras de NAT e de filtro que o Docker escreveu, e a rede dos contêineres para de funcionar até que systemctl restart docker as recoloque. Carregue seu ruleset primeiro, reinicie o Docker depois, e leia a próxima seção antes de publicar uma única porta de contêiner.
As portas que você nem sabia que estavam abertas
Pergunte à máquina no que ela está escutando. Não no que você acha que instalou — no que está vinculado agora mesmo:
ss -tulpn | grep -vE '127\.0\.0\.1|\[::1\]'Tudo que sobrevive a esse filtro é alcançável a partir de algum lugar que não a própria máquina. Em uma imagem padrão, os achados de sempre são rpcbind na 111 (nada que você rode precisa dele), um listener exim4 deixado pela instalação base, e um stub do systemd-resolved que é inofensivo em loopback e um resolver aberto se não estiver. Os perigosos aparecem depois, com software que você instalou de propósito: PostgreSQL em 0.0.0.0:5432 porque um tutorial mandou editar listen_addresses, Redis sem senha porque o Redis não tinha senha por padrão durante a maior parte da sua história, um node Elasticsearch, um exporter do Prometheus, um notebook Jupyter. Cada um desses já foi o primeiro passo de uma invasão real inúmeras vezes.
Agora a pegadinha que pega até quem toma cuidado. O Docker não pede permissão ao seu firewall. Quando você escreve -p 5432:5432, o Docker insere regras de DNAT na chain PREROUTING da tabela nat, que o kernel avalia antes de sua chain input sequer ver o pacote; o tráfego é então encaminhado para o contêiner. Sua política de bloqueio padrão na input não é consultada, o ufw status mostra a porta fechada, e o banco de dados está na internet pública. Esse é um comportamento documentado do Docker, é assim há uma década, e já expôs um número enorme de bancos de dados.
A correção é uma única string, e é o hábito que vale a pena criar:
# wrong — reachable from the entire internet, whatever your firewall says
docker run -p 5432:5432 postgres
# right — bound to loopback, reachable only through an SSH tunnel or a proxy
docker run -p 127.0.0.1:5432:5432 postgresPor fim, busque uma opinião de fora. Um port scan feito a partir do próprio servidor te diz o que o kernel pensa; uma varredura feita de outro lugar te diz o que o mundo vê, que é o único número que importa. Rode nmap -Pn -p- <your-ip> a partir do seu laptop, não da máquina, e compare o resultado com a lista que você esperava.
Atualizações automáticas: ative, e ative para valer
A janela que compromete servidores pequenos não é o zero-day. São as quatro semanas entre um CVE se tornar público com uma prova de conceito funcional e a próxima vez que você por acaso vai logar. As ferramentas de ataque absorvem uma falha remota nova em questão de dias; uma máquina que você corrige “quando sobrar tempo” fica exposta durante todo esse intervalo, e todo operador honesto sabe quão longo esse intervalo realmente é.
A objeção às atualizações automáticas é o medo de quebrar algo, e vale a pena levar a sério — e depois restringir. Limite a automação ao pocket de segurança da sua distribuição, onde os mantenedores fazem backport das correções para a versão empacotada em vez de lançar novas versões upstream. Uma atualização de segurança do Debian para o openssl é um build corrigido da mesma versão que você já roda; o risco de isso mudar o comportamento é bem menor do que o risco de deixar um buraco remoto aberto por um mês. Upgrades de funcionalidade continuam manuais, onde devem ficar.
A parte que as pessoas pulam é o reboot. Um libssl corrigido no disco não faz nada pelo processo que mapeou o antigo na inicialização, e uma atualização de kernel não faz absolutamente nada até você dar boot nela. Ou você aceita um reboot sem intervenção em uma janela que você escolhe, ou instala o needrestart e deixa ele te dizer quais serviços estão rodando contra bibliotecas apagadas — mas faça um dos dois. “As atualizações automáticas estão ativadas” enquanto o uptime mostra 340 dias é uma ilusão confortável, e é a mais comum que vemos.
fail2ban, CrowdSec, ou nada
O fail2ban lê seus logs, percebe falhas repetidas vindas de um endereço e o bane por um tempo. O CrowdSec faz o mesmo e compartilha os veredictos por uma rede de participantes, então você consegue bloquear um endereço que já se comportou mal em outro lugar antes que ele chegue até você. Os dois são softwares bons. Nenhum dos dois faz o que a maioria das pessoas pensa que faz em um servidor SSH somente com chaves.
Uma vez que PasswordAuthentication é no, um brute-force de SSH não pode ter sucesso. Não é “dificilmente teria sucesso” — não existe caminho de código para isso. Banir um endereço depois de cinco falhas, portanto, não previne nada; reduz o volume de log e uma quantidade trivial de CPU. Isso é um benefício real, só não é um benefício de segurança, e a troca não é gratuita: uma jail mal escrita que observa o log errado já trancou mais administradores para fora dos próprios servidores do que atacantes.
Onde essas ferramentas realmente merecem seu lugar é uma camada acima, nos serviços que você de fato expõe: um formulário de login, um admin do WordPress, uma API com rate limit, um servidor de e-mail com SMTP AUTH. Esses sim aceitam senha, são vulneráveis a brute-force, e uma lista de banimento é exatamente o controle certo. Então nosso veredito é estreito e específico. Pule a jail no SSH. Coloque o CrowdSec ou o fail2ban na frente do que tiver um campo de senha. E qualquer um que você escolher, coloque seu próprio endereço de gerenciamento na whitelist primeiro — o ignoreip existe para a noite que você vai lembrar pelos motivos errados.
Fazendo a máquina te avisar quando algo muda
Prevenção é o que você consegue fazer em uma hora. Detecção é o que te avisa que a hora não foi suficiente. Não precisa ser elaborada, e em um único servidor três coisas baratas cobrem a maior parte do terreno.
Preserve os logs. Em muitas imagens o journal vive em /run e evapora no reboot, o que significa que o registro de um incidente desaparece no reboot que vem logo depois dele. Criar /var/log/journal é uma correção de uma linha só e a coisa de maior valor nesta seção.
Seja avisado sobre logins. Uma linha em /etc/ssh/sshrc que dispara o logger a cada início de sessão não custa nada e te dá um registro limpo e fácil de buscar com grep, separado da conversa própria do sshd. Um detalhe que pega as pessoas: o sshd roda ~/.ssh/rc em vez de /etc/ssh/sshrc quando o usuário tem um, então o arquivo do sistema é pulado exatamente para a conta com mais chance de ter um dotfile personalizado. Se você precisa de um hook que não possa ser sombreado, use pam_exec em vez disso.
Saiba como o sistema de arquivos estava no primeiro dia. O AIDE registra hashes dos seus binários, bibliotecas e unit files e reporta o que mudou desde então. A pegadinha é fundamental e geralmente ignorada: um banco de dados guardado na mesma máquina que ele monitora pode ser regenerado por quem quer que a tenha comprometido, e então a checagem reporta “nenhuma mudança” para sempre. Copie o banco de dados para fora da máquina, ou pelo menos registre o hash dele em outro lugar, e a ferramenta passa a valer os vinte minutos que custa. Deixado no lugar, é só uma falsa sensação de segurança.
O princípio geral vale a pena declarar sozinho: o log em que você não pode confiar é o que está na máquina comprometida. Qualquer coisa em que você realmente pretenda confiar — um journal, um banco de dados de integridade, um backup — deveria ter uma cópia em algum lugar que o mesmo atacante não controle. Um segundo servidor pequeno em outra jurisdição é uma resposta legítima para isso, e um de cinco dólares já basta.
O que isto não faz
Tudo o que vimos acima é trabalho de perímetro, e vale a pena ser preciso sobre esse limite para você não confundir uma porta da frente trancada com um cofre.
Não protege dados em uma máquina em execução. Um kernel enrijecido e um firewall fechado são irrelevantes para quem tem acesso ao hypervisor, e para o conteúdo da RAM enquanto a máquina está ligada. Se a sua preocupação é o disco quando o servidor está desligado, apreendido ou desativado, isso é criptografia de disco, e é um procedimento diferente com um modo de falha diferente — veja criptografar um disco de VPS com LUKS.
Não te torna anônimo. Um servidor pode estar perfeitamente protegido e ainda assim anunciar quem é o dono, por meio de um registro WHOIS, uma chave SSH reutilizada, uma tag de analytics, um cliente SSH que conecta de casa sem um salto intermediário, ou um certificado que amarra duas identidades. Pagar em Monero e depois logar a partir do seu próprio endereço desfaz o pagamento. Esse modo de falha tem sua própria página: os erros que te desanonimizam.
Não conserta a sua aplicação. Uma SQL injection, um endpoint de admin sem autenticação ou uma dependência com backdoor não se importam com as suas configurações de sshd. A maioria dos comprometimentos de servidores bem administrados chega pela porta que você abriu deliberadamente, de propósito, para um serviço que você escreveu ou instalou.
Não é um backup. Ransomware, um rm -rf com uma variável que estava vazia, e um upgrade que deu errado terminam todos do mesmo jeito. Tire uma cópia, coloque em outro lugar, e restaure a partir dela uma vez antes de precisar de verdade — a mesma disciplina que faz uma migração para outro host sobrevivível é a que faz uma terça-feira ruim sobrevivível.
Nada disso é um argumento contra a hora. É um argumento para saber exatamente o que essa hora comprou.
- Faça o deploy com uma chave, nunca com uma senha
Gere o par de chaves na sua própria máquina e cole a metade pública no formulário de deploy; a imagem vai instalá-la e você nunca vai ter uma senha de root para perder. Se você fizer o deploy a partir de uma imagem marcada como
cloud-init, pode passar a configuração inteira da primeira hora como user-data — até 64 KiB — e a máquina já sobe protegida.# on your laptop, not on the server ssh-keygen -t ed25519 -a 100 -C "$(whoami)@laptop-cvh" cat ~/.ssh/id_ed25519.pub # paste this into the deploy formUse ed25519, a menos que algo na sua toolchain recuse, caso em que RSA de 4096 bits serve bem. Proteja a chave privada com uma senha e carregue-a em um agente SSH; um arquivo de chave sem senha em um laptop é uma senha escrita no monitor. O mesmo conjunto de chaves é injetado no modo de recuperação, o que é o que torna o passo 4 recuperável.
- Abra a segunda sessão antes de mexer em qualquer coisa
Isto não é opcional e não é paranoia. Daqui para frente você vai mudar o daemon pelo qual está conectado e o firewall que te deixa alcançá-lo. Mantenha uma sessão aberta e ociosa como uma corda de volta para a máquina, e faça cada mudança em uma sessão diferente. Se uma mudança sair errada, a sessão aberta continua autenticada e pode desfazê-la; uma conexão nova seria recusada.
# terminal A — the rope. Log in and leave it alone. ssh -i ~/.ssh/id_ed25519 root@<server-ip> # terminal B — where every command below runs. ssh -i ~/.ssh/id_ed25519 root@<server-ip>Teste cada mudança abrindo uma terceira conexão, nunca reconectando a sessão em que você está trabalhando. Se a terceira conexão falhar, você ainda tem dois shells funcionando e um problema em vez de uma crise.
- Crie a conta que você vai realmente usar
Root via SSH é conveniente por exatamente uma hora. Depois disso, uma conta nomeada com sudo te dá uma trilha de auditoria, te protege de um comando digitado errado rodando com privilégios totais, e permite desativar uma conta comprometida sem desativar a máquina.
adduser --disabled-password --gecos "" deploy install -d -m 0700 -o deploy -g deploy /home/deploy/.ssh cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys chown deploy:deploy /home/deploy/.ssh/authorized_keys chmod 0600 /home/deploy/.ssh/authorized_keys usermod -aG sudo deploy # a long random password, stored in your manager, used only by sudo passwd deployDefina essa senha. Uma conta sudo cuja senha nunca foi definida não consegue usar o sudo, e descobrir isso depois de já ter desativado o login de root é a forma clássica de se trancar para fora com todo arquivo correto. Verifique antes de continuar: em um terminal novo,
ssh deploy@<server-ip>, depoissudo -v. Os dois precisam funcionar. - Escreva o drop-in do SSH, depois pergunte ao daemon o que ele leu
Um drop-in numerado que ordena mais cedo vence o que quer que a imagem cloud tenha trazido, conforme a regra de ordenação acima. Escreva-o, valide a sintaxe, releia a configuração efetiva, e só então dê reload.
cat > /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF' PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AuthenticationMethods publickey PermitEmptyPasswords no AllowUsers deploy MaxAuthTries 3 LoginGraceTime 20 X11Forwarding no AllowAgentForwarding no AllowTcpForwarding no # remove this line if you use ssh -L / -D tunnels ClientAliveInterval 300 ClientAliveCountMax 2 EOF chmod 0644 /etc/ssh/sshd_config.d/10-hardening.conf sshd -t # silence means the syntax is valid sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|allowusers) ' systemctl reload sshLeia essa saída do
sshd -Tantes de dar reload, não depois. Ela precisa dizerpermitrootlogin noepasswordauthentication no. Se não disser, seu arquivo está sendo sobrescrito por um que ordena mais cedo — rodels /etc/ssh/sshd_config.d/e renomeie o seu para uma ordem mais baixa. Agora abra um terceiro terminal e faça login comodeploy. Só feche a corda quando isso funcionar. - Carregue um firewall com bloqueio padrão
O nftables já vem com as duas distribuições e substitui o ruleset do iptables por um único arquivo legível. Ajuste as portas aceitas aos serviços que você realmente roda — a lista abaixo assume SSH e um servidor web, e nada além disso.
apt install -y nftables cat > /etc/nftables.conf <<'EOF' #!/usr/sbin/nft -f flush ruleset table inet filter { chain input { type filter hook input priority filter; policy drop; ct state established,related accept ct state invalid drop iif lo accept # ICMP must live: dropping it blackholes path-MTU discovery. ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded, parameter-problem } accept ip6 nexthdr icmpv6 accept tcp dport 22 ct state new accept tcp dport { 80, 443 } ct state new accept limit rate 5/minute burst 5 packets log prefix "nft-drop: " level info } chain forward { type filter hook forward priority filter; policy drop; } chain output { type filter hook output priority filter; policy accept; } } EOF nft -c -f /etc/nftables.conf # check before you commit to it systemctl enable --now nftables nft list ruleset | head -40Se o Docker estiver instalado, o
flush rulesettambém remove as regras do Docker: rodesystemctl restart dockerdepois e confirme que seus contêineres ainda respondem. Em seguida, no seu laptop, rodenmap -Pn -p- <server-ip>e confira que a lista de portas abertas bate com o que você acabou de permitir — nem mais, nem menos. - Ative as atualizações de segurança automáticas
Só o pocket de segurança, com uma janela de reboot que você escolheu em vez de uma que você vai ficar adiando. Escolha um horário tranquilo para os seus usuários e que não seja em hora cheia, para não colidir com o cron de todo mundo.
apt install -y unattended-upgrades needrestart cat > /etc/apt/apt.conf.d/51-cvh-auto <<'EOF' // Debian. On Ubuntu, replace the two Origins-Pattern lines with: // "${distro_id}:${distro_codename}-security"; Unattended-Upgrade::Origins-Pattern { "origin=Debian,codename=${distro_codename}-security,label=Debian-Security"; "origin=Debian,codename=${distro_codename},label=Debian-Security"; }; Unattended-Upgrade::Automatic-Reboot "true"; Unattended-Upgrade::Automatic-Reboot-WithUsers "false"; Unattended-Upgrade::Automatic-Reboot-Time "04:17"; Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; EOF unattended-upgrade --dry-run --debug | tail -25 systemctl status unattended-upgrades --no-pagerSe um reboot sem intervenção for genuinamente inaceitável, defina como
"false"e deixe oneedrestart -breportar quais serviços estão rodando contra bibliotecas apagadas e se há um kernel mais novo instalado — e então aja de acordo. O que não é aceitável é não fazer nenhum dos dois. - Feche o que está escutando, e confira de fora
Enumere o que está vinculado a algo além do loopback, remova o que você não usa, e confirme o resultado a partir de uma máquina que não seja esta.
ss -tulpn | grep -vE '127\.0\.0\.1|\[::1\]' # the usual leftovers on a base image systemctl disable --now rpcbind.socket rpcbind 2>/dev/null apt purge -y exim4-base exim4-config exim4-daemon-light 2>/dev/null apt autoremove --purge -y # what each running unit is allowed to reach systemd-analyze security --no-pager | head -20Para qualquer coisa que precise continuar rodando mas não precisa de plateia — um banco de dados, um cache, um painel de administração, um exporter de métricas — vincule-o a
127.0.0.1na sua própria configuração e alcance-o por um túnel SSH:ssh -L 5432:127.0.0.1:5432 deploy@<server-ip>. Isso é estritamente melhor do que abrir uma porta e torcer para que a autenticação do próprio serviço seja sólida, e é o caminho a seguir por padrão. - Registre a linha de base e arme os alarmes
Dez minutos que só se pagam no dia em que algo dá errado — que é exatamente o dia em que você não vai conseguir reconstruir nada disso de memória.
# persistent journal, so a reboot stops erasing the evidence mkdir -p /var/log/journal systemd-tmpfiles --create --prefix /var/log/journal systemctl restart systemd-journald # a clean, greppable line per SSH session cat > /etc/ssh/sshrc <<'EOF' logger -t ssh-login "user=$USER from=${SSH_CONNECTION%% *} tty=${SSH_TTY:-none}" EOF # file-integrity baseline — then get the database off the machine apt install -y aide aideinit mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db sha256sum /var/lib/aide/aide.db # copy this hash somewhere else last -n 20 ; lastb -n 20 # who got in, and who triedTermine anotando, fora do servidor, quatro coisas: o fingerprint da chave de host SSH obtido com
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub, o hash do AIDE de acima, as portas que você abriu deliberadamente, e onde a chave privada mora. Essa anotação é o que transforma uma manhã ruim em um checklist em vez de uma investigação.
Os controles, classificados pelo que cada um realmente garante
| Controle | O que impede | O que não impede | Custo | Veredito |
|---|---|---|---|---|
| SSH somente com chaves (PasswordAuthentication no) | Todo bot de adivinhação de credenciais da internet, de forma permanente e por construção | Quem tiver sua chave privada, ou uma falha no próprio sshd | 5 minutos, uma vez | Inegociável |
| Firewall com bloqueio padrão (nftables) | Serviços que você esqueceu que estavam escutando, e todos os que você instalar sem querer depois | Ataques contra as portas que você abriu de propósito | 10 minutos, uma vez | Inegociável |
| Atualizações de segurança automáticas | A janela de n dias — as semanas entre um exploit público e seu próximo login | Zero-days, e qualquer coisa que precise de um reboot que você nunca agenda | 5 minutos mais uma janela de reboot | Inegociável |
| Usuário nomeado com sudo em vez de root | Comandos digitados errado com privilégios totais, e processos rodando como root sem necessidade | Uma escalação de privilégio local nas mãos de quem já está dentro | 3 minutos | Vale a pena |
| Linha de base de integridade de arquivos (AIDE) | Alterações silenciosas em binários, bibliotecas e unit files do sistema | Absolutamente nada, se o banco de dados ficar na mesma máquina que ele monitora | 20 minutos mais armazenamento fora da máquina | Vale a pena se a máquina importar |
| Tirar o SSH da porta 22 | Cerca de 95% do volume do seu log de autenticação | Qualquer um que rode um port scan, ou seja, todo mundo que importa | 2 minutos, mais a noite em que você esquece a porta | Opcional, para logs mais silenciosos |
| fail2ban / CrowdSec no sshd | Reincidentes, e a CPU que eles desperdiçam com você | Nada, assim que a autenticação por senha está desligada | 10 minutos, mais um risco real de ficar trancado para fora | Pule no SSH; use na sua aplicação |
Perguntas que merecem resposta
Quanto tempo depois do deploy a varredura começa?
Minutos, tipicamente. Scanners que varrem a internet inteira cobrem todo o espaço IPv4 continuamente, então um endereço novo é alcançado na próxima passada, não importa o que esteja rodando nele. Assuma que você está sendo sondado desde o momento em que a máquina responde ao primeiro pacote — por isso o hardening acontece na primeira hora, e não no primeiro fim de semana.
Eu ainda preciso de fail2ban se o login por senha está desativado?
No SSH, não. Com PasswordAuthentication no não existe caminho de código que um brute-force possa vencer, então banir depois de cinco falhas não previne nada — reduz o ruído nos logs, o que vale alguma coisa, mas não é segurança. O lugar onde essas ferramentas realmente têm valor é na frente de qualquer coisa que genuinamente aceite senha: um formulário de login web, um painel de administração, SMTP AUTH. Se você instalar uma, coloque seu próprio endereço na whitelist primeiro.
ufw, firewalld ou nftables?
Qualquer um deles, contanto que a política padrão seja drop e você entenda o que colocou no ar. O ufw é o mais amigável e, por baixo, escreve regras nftables no Debian e no Ubuntu atuais. O nftables puro é um único arquivo legível que dá para comparar e versionar, e é por isso que o usamos. A escolha importa muito menos do que a política padrão — e nenhum dos três muda o fato de que o Docker publica portas por baixo de todos eles.
Devo tirar o SSH da porta 22?
Só para deixar os logs mais silenciosos. Isso não impede nenhum atacante competente — uma varredura completa leva segundos e o banner identifica o serviço. O que isso faz é remover a maior parte do ruído do seu log de autenticação, o que deixa anomalias reais visíveis. Trate isso como higiene de log, nunca como um controle, e verifique se o seu sshd é ativado por socket antes de mudar: no Ubuntu 24.04 a diretiva Port em sshd_config é ignorada e a porta pertence ao ssh.socket.
As atualizações automáticas vão quebrar meu site às quatro da manhã?
Restritas ao pocket de segurança, muito raramente. Esses pacotes são correções com backport para a versão que você já roda, não são novos lançamentos upstream. O risco real é o reboot, não o patch — então escolha você mesmo a janela, defina Automatic-Reboot-WithUsers como false para que ele espere enquanto alguém estiver logado, e se o serviço realmente não puder reiniciar sem intervenção, rode needrestart -b em agenda fixa e aja de acordo com o que ele reportar. A única posição indefensável é atualização automática com um uptime medido em anos.
Eu me tranquei para fora. Quais são minhas opções?
Inicialize o servidor em modo de recuperação pelo painel. Ele sobe um ambiente Alpine em RAM com as mesmas chaves SSH da sua conta e o disco desmontado em /dev/vda, então você pode montá-lo, corrigir o drop-in do sshd ou o arquivo do firewall, desmontar e reiniciar. O boot de recuperação em si não toca em nada no disco. O fingerprint da chave de host do modo de recuperação é diferente do seu normal — isso é esperado, não é uma interceptação.
Rodar o Lynis ou um script de hardening CIS no lugar disso é uma boa ideia?
Como auditoria, sim; como substituto, não. O Lynis é uma segunda opinião genuinamente útil e vai encontrar coisas que esta página não menciona. Scripts automatizados de remediação CIS são outra história: eles aplicam centenas de mudanças pensadas para uma frota de estações de trabalho corporativas, várias das quais quebram um servidor de formas difíceis de rastrear semanas depois. Faça a hora manualmente primeiro, para entender o que a sua máquina está fazendo, depois rode um auditor e leia os achados um de cada vez.
O hardening deixa meu servidor anônimo?
Não, e confundir as duas coisas é um erro comum e caro. O hardening controla quem consegue entrar; o anonimato controla quem consegue saber que o servidor é seu. Um servidor perfeitamente trancado ainda vaza a titularidade por meio de um registro WHOIS, uma chave SSH reutilizada, uma tag de analytics ou um login de administração feito da sua conexão residencial. Pagar em Monero e depois se conectar direto da sua própria conexão anula o pagamento por completo — isso está coberto em os erros que te desanonimizam.
Keep exploring
Criptografar um disco de VPS com LUKS
A outra metade do trabalho: esta página fecha a rede, aquela protege o disco quando a máquina está desligada.
Erros de anonimato que te desanonimizam
Um servidor perfeitamente protegido que ainda assim conta pra todo mundo quem é o dono. As falhas são comportamentais, não técnicas.
Configurar um VPS com WireGuard
O primeiro serviço que vale a pena colocar atrás do firewall que você acabou de construir — uma porta privada para tudo que você vinculou ao localhost.
Deploy your offshore server.
Escolha uma região. Escolha um plano. Cole uma chave. Pague. Os próximos 47 segundos são por nossa conta.