Configuração de um Novo VPS para Produção (para Iniciantes)
Visão Geral
Esta skill guia você passo a passo na preparação de um servidor Ubuntu limpo para produção, de forma que ele se mantenha o mais autônomo possível. O público-alvo são pessoas sem experiência em administração de sistemas. Portanto:
- Defaults seguros, não "como um profissional". Nada de NOPASSWD-sudo, nada de verificações desativadas por conveniência. O que simplifica a vida de um especialista, para um iniciante é uma armadilha.
- Autonomia através da automação. Atualizações de segurança, backups e monitoramento devem funcionar sozinhos. A pessoa apenas reage a alertas.
- Cada passo é verificado. Após uma alteração, verifica-se o resultado, e só então se prossegue para o próximo passo.
SO Alvo: Ubuntu 22.04 / 24.04 LTS. Comandos para outras distribuições são diferentes.
Regras de Ouro (Antes de QUALQUER Alteração)
- Backup do config:
sudo cp /caminho/config /caminho/config.bak.$(date +%F-%H%M) - Verifique o estado:
sudo systemctl status <serviço>/sudo docker ps - Após a alteração, verifique o resultado (logs, status, conexão de teste).
- Nunca feche a sessão SSH atual até que, em uma nova sessão, tenha certeza de que o acesso e o sudo funcionam. Perder o SSH em um servidor remoto = ter que usar o console VNC do provedor.
Princípios de segurança em produção (siga em todas as etapas):
- Segredos (chaves de API, tokens, senhas) — apenas em
.envou variáveis de ambiente, nunca no código. .env— obrigatoriamente no.gitignore(senão os segredos vazarão para o git no primeiro commit).- Bancos de dados não devem ficar expostos à internet — apenas localhost ou através de rede privada/VPN.
- No servidor, não há modo de depuração:
DEBUG=False,NODE_ENV=production. - Aplicações não devem ser executadas como root — use um usuário/contêiner separado.
- Sem portas abertas desnecessárias (painéis de administração, dashboards, métricas — fechados ou atrás de IPs confiáveis).
- Não desative verificações de segurança por conveniência (
verify=False,allowAll— apenas localmente). - SSH — apenas por chaves, não por senha.
0. Diagnóstico — O que Já Existe
Execute PRIMEIRO. Provedores geralmente entregam o servidor com configuração parcial (cloud-init, ufw, etc.).
# Sistema
lsb_release -a && uname -r
free -h && df -h && nproc
# Quem sou eu e quais permissões tenho
whoami && id
# Config SSH (valores efetivos, não apenas arquivos!)
sudo sshd -T | grep -iE 'permitrootlogin|passwordauthentication|pubkeyauthentication|^port '
# Firewall
sudo ufw status verbose
# O que está ouvindo portas
sudo ss -tlnp
# Atualizações automáticas (ativadas?)
systemctl status unattended-upgrades 2>/dev/null | head -5
cat /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null
# Docker / fail2ban
sudo docker ps 2>/dev/null
sudo fail2ban-client status 2>/dev/null
Após o diagnóstico, informe ao usuário o que foi encontrado e não inicie a configuração sem confirmação.
1. Configuração Inicial e Segurança
Consulte a seção 0 — parte pode já estar feita. Não faça duas vezes.
1.1 Atualização do Sistema
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
# Se o kernel foi atualizado, será necessário reiniciar (veja a saída). Reinicie conscientemente:
# sudo reboot # e reconecte após ~30-60s
1.2 Usuário sudo Separado (NÃO trabalhe como root)
sudo adduser <USERNAME> # definirá uma senha — escolha uma forte, salve no gerenciador de senhas
sudo usermod -aG sudo <USERNAME>
Para o iniciante, mantenha o sudo com solicitação de senha. Isso é intencional: NOPASSWD-sudo remove a última barreira em caso de comprometimento da chave ou sessão. A senha sudo também serve como fallback se a chave SSH for perdida (veja 1.3).
1.3 Chaves SSH (Acesso sem Senha por Chave)
Tipo de chave — ed25519 (não RSA). Nome — <servidor>_<dispositivo>, com passphrase.
Na máquina local:
ssh-keygen -t ed25519 -f ~/.ssh/<servidor>_<dispositivo> -C "<servidor>_<dispositivo>"
ssh-copy-id -i ~/.ssh/<servidor>_<dispositivo>.pub <USERNAME>@<SERVER_IP>
Verifique o acesso por chave em uma sessão SEPARADA (não feche a antiga!):
ssh -i ~/.ssh/<servidor>_<dispositivo> -o IdentitiesOnly=yes <USERNAME>@<SERVER_IP> 'whoami; sudo whoami'
Adicione um alias conveniente em ~/.ssh/config na máquina local:
Host <alias>
HostName <SERVER_IP>
User <USERNAME>
IdentityFile ~/.ssh/<servidor>_<dispositivo>
IdentitiesOnly yes
1.4 Hardening do SSH
Crie /etc/ssh/sshd_config.d/99-hardening.conf:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
AllowTcpForwarding local
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers <USERNAME>
AllowTcpForwarding localpermite o redirecionamento local (ssh -L— para acessar um serviço em127.0.0.1antes de configurar o domínio) e proíbe o remoto (-R). Se o encaminhamento não for necessário, useno.
Aplique e verifique:
sudo sshd -t # sintaxe OK?
sudo systemctl restart ssh
sudo sshd -T | grep -iE 'permitroot|passwordauth' # valores efetivos = no
Armadilha 1 — cloud-init sobrescreve o hardening. O arquivo /etc/ssh/sshd_config.d/50-cloud-init.conf frequentemente contém PasswordAuthentication yes e em sshd_config.d/ vale a regra "primeiro match vence". Verifique através de sudo sshd -T (valores efetivos), não apenas seu arquivo. Se ele estiver sobrescrevendo, corrija/remova a linha em 50-cloud-init.conf.
Armadilha 2 — Ubuntu 24.04 usa ssh.socket. A diretiva Port em sshd_config é ignorada. Para alterar a porta SSH: sudo systemctl edit ssh.socket → adicione ListenStream=<PORT> (e remova a padrão). Teste através do console do provedor — um erro aqui bloqueia o acesso.
1.5 Atualizações Automáticas de Segurança ⭐ (Núcleo da Autonomia)
Este é o passo mais importante para "o servidor se manter sozinho". Sem ele, as vulnerabilidades em pacotes se acumulam.
sudo apt install unattended-upgrades apt-listchanges -y
sudo dpkg-reconfigure -plow unattended-upgrades # escolha "Yes" — criará 20auto-upgrades
Verifique /etc/apt/apt.conf.d/20auto-upgrades:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
Em /etc/apt/apt.conf.d/50unattended-upgrades (descomente/configure as linhas principais):
// Instalar atualizações de segurança (ativado por padrão):
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Remove-Unused-Dependencies "true";
// Reinicialização automática se a atualização exigir (kernel). Escolha uma janela com pouca carga:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
Decisão sobre "reinicialização automática":
true= o servidor reinicia sozinho à noite ao atualizar o kernel (mais seguro, mas pode causar uma breve indisponibilidade).false= o kernel será atualizado, mas a reinicialização depende de você (precisa de monitoramento). Para um servidor de produção único de um iniciante, geralmente é melhortrue+ janela noturna.
Verificação — execução de teste sem instalação:
sudo unattended-upgrade --dry-run --debug 2>&1 | tail -20
systemctl list-timers 'apt-daily*' # timers ativos?
1.6 fail2ban (Proteção SSH contra Força Bruta)
A configuração básica é suficiente — não complique com recidive/nftables no início.
sudo apt install fail2ban -y
/etc/fail2ban/jail.d/sshd.local:
[sshd]
enabled = true
bantime = 1h
findtime = 30m
maxretry = 4
# Não banir a si mesmo: adicione SEUS IPs estáticos (descubra: curl ifconfig.me)
ignoreip = 127.0.0.1/8 ::1 <SEU_IP_CASA>
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
1.7 Fuso Horário e (para VPS pequenos) swap
sudo timedatectl set-timezone <Região/Cidade> # ex.: America/Sao_Paulo ou UTC
# Swap é necessário em VPS com pouca RAM (< 2 GB) para que o OOM não mate serviços:
free -h
sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile
sudo mkswap /swapfile && sudo swapon /swapfile
grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swap.conf && sudo sysctl --system
2. Firewall UFW
Princípio: tudo fechado, apenas o necessário aberto. Primeiro, libere o SSH, senão você se tranca para fora.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH' # IMPORTANTE: antes do enable!
sudo ufw enable
sudo ufw status verbose
Para um servidor web, adicione HTTP/HTTPS:
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
Painéis de admin/dashboards/métricas — apenas para IPs confiáveis, não para "todos":
sudo ufw allow from <IP_CONFIAVEL> to any port <PORTA_ADMIN> proto tcp comment 'painel admin'
Nunca use
sudo ufw reset— isso apaga todas as regras, incluindo SSH.
3. Docker (Opcional — se as aplicações estiverem em contêineres)
Instalação
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker <USERNAME> # faça logout/login para o grupo ser aplicado
Ser membro do grupo
dockeré efetivamente equivalente a root (através dedocker run -v /:/host). Isso é aceitável para um único administrador, mas não conceda esse grupo a usuários em quem você não confia acesso total ao servidor.
Rotação de Logs (Obrigatório — senão os logs consomem o disco)
/etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker
⚠️ Docker Ignora o UFW
Uma porta mapeada como -p 8080:8080 fica acessível da internet, mesmo que o UFW não a tenha liberado.
Opções seguras:
- Mapear apenas para localhost:
-p 127.0.0.1:8080:8080(acesso apenas via nginx/túnel SSH). - Não mapear portas de banco de dados para fora — comunique-se pelo nome do serviço dentro da rede docker.
4. nginx + HTTPS (Opcional — se houver aplicação web e domínio)
nginx como proxy reverso antes da aplicação (a aplicação escuta em 127.0.0.1, externamente apenas nginx).
sudo apt install nginx -y
/etc/nginx/sites-available/<app>:
server {
listen 80;
server_name <DOMINIO>;
location / {
proxy_pass http://127.0.0.1:<PORTA_APP>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo ln -s /etc/nginx/sites-available/<app> /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
HTTPS gratuito via Let's Encrypt (certbot emitirá e configurará a renovação automática):
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d <DOMINIO>
# A renovação automática já está configurada por um timer. Verificação:
sudo certbot renew --dry-run
Proteção: adicione server_tokens off; em /etc/nginx/nginx.conf (ocultar a versão do nginx) e cabeçalhos de segurança (Strict-Transport-Security, X-Content-Type-Options nosniff).
5. Backups Automáticos (restic + Armazenamento Externo)
Instruções completas (instalação, repositório, script, timer, restauração): leia references/backups.md — lá está o passo a passo.
Princípios fundamentais que NÃO podem ser violados:
- O backup DEVE ser externo (offsite). Uma cópia no mesmo servidor (ou disco) não salvará em caso de falha do servidor/disco. No mínimo, armazenamento externo (compatível com S3: Backblaze B2, rsync.net, etc.).
- A senha de criptografia do repositório deve estar FORA do servidor (gerenciador de senhas). Perder a senha = todos os backups se tornam permanentemente ilegíveis. Esta é a forma mais comum de ficar sem backup.
- Backup sem teste de restauração não é backup. Pelo menos uma vez, faça uma restauração de teste (veja backups.md, "Restauração de Treinamento").
- Automação: execução diária via systemd timer + rotação de snapshots antigos.
O Que Fazer Backup
Pense em categorias, não em caminhos individuais — assim nada será esquecido:
| Categoria | Exemplos de Caminhos | Por Quê |
|---|---|---|
| Configs do sistema | /etc inteiro | nginx, ssh, ufw, fail2ban, units do systemd — para recriar o servidor "como era" |
| Dados da aplicação | /home/<USERNAME>/app/data, /var/lib/<serviço> | o que não pode ser reinstalado: conteúdo do usuário, uploads |
| Dumps do BD | /var/backups/*.sql.gz | dump via pg_dump/.backup antes do restic (o arquivo vivo do BD pode estar corrompido) |
| Segredos | .env, /root/.config/* | são copiados de forma criptografada (restic criptografa o repo). A senha de criptografia fica fora do servidor |
O que NÃO fazer backup (pode ser reinstalado, só aumenta o backup):
node_modules, .venv/venv, imagens Docker (docker pull), pacotes do sistema (apt),
caches, /var/log, /tmp. Esses caminhos já estão em --exclude no script (veja backups.md).
Quanto Tempo Manter (Retenção) — Escolha uma Política
O restic armazena snapshots desduplicados, portanto "mais cópias" é barato em espaço.
Comando: restic forget --prune --keep-daily N --keep-weekly N --keep-monthly N.
| Política | Comando | Para Quem | Justificativa |
|---|---|---|---|
| Básica (padrão) | --keep-daily 7 --keep-weekly 4 --keep-monthly 6 | A maioria dos servidores de produção | Reversão para qualquer um dos últimos 7 dias + histórico de até seis meses. Detecta tanto erros recentes quanto "quando foi que isso quebrou". Já está configurada em backups.md. |
| Leve | --keep-daily 7 --keep-weekly 4 | Sites/bots sem dados acumulados valiosos | ~1 mês de histórico. Mínimo de espaço, mais simples. Adequado se a perda de dados antigos não for crítica. |
| Longa | --keep-daily 14 --keep-weekly 8 --keep-monthly 12 | BDs com dados importantes, finanças, requisitos legais | Um ano de histórico + 2 semanas de snaps diários. Mais caro em espaço, mas protege contra corrupção "silenciosa" de dados percebida tardiamente. |
Recomendação para iniciantes: comece com a Básica — ela cobre ambos os cenários (reversão rápida e longo histórico) com um custo de armazenamento irrisório. Mudar para a Longa pode ser feito a qualquer momento, apenas alterando os números no
forget— os snaps antigos não são perdidos retroativamente.
Importante sobre BDs: a retenção é inútil se você fizer backup do arquivo "vivo" do BD — ele pode estar corrompido. Sempre faça primeiro um dump (
pg_dump/ SQLite.backup), e o restic fará backup do dump.
6. Monitoramento
...