igor-batrakov/new-vps-setup

Guia passo a passo e checklist seguro para configurar um novo VPS Ubuntu para produção, voltado para iniciantes: hardening, atualizações automáticas, UFW, fail2ban, Docker, nginx+HTTPS, backups automáticos com restic e monitoramento.

O que é new-vps-setup?

Este skill (guia + checklist) conduz um iniciante em administração de sistemas por toda a configuração de um servidor Ubuntu limpo para produção, garantindo que ele opere de forma autônoma e segura. O foco está em defaults seguros (sem atalhos perigosos), automação de atualizações de segurança, backups e monitoramento, e verificação de cada passo antes de prosseguir. Ele cobre desde o diagnóstico inicial do VPS, criação de usuário sudo, configuração de chaves SSH, hardening do SSH, firewall UFW, fail2ban, instalação do Docker com rotação de logs, configuração do nginx como proxy reverso com HTTPS via Let's Encrypt, até backups automáticos com restic para armazenamento externo e monitoramento básico. O skill é compatível com Ubuntu 22.04/24.04 LTS e pode ser usado com assistentes de IA como Claude Code, Cursor, Codex e Aider para executar os comandos de forma interativa, sempre exigindo confirmação do usuário antes de aplicar mudanças críticas.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add igor-batrakov/new-vps-setup

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

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)

  1. Backup do config: sudo cp /caminho/config /caminho/config.bak.$(date +%F-%H%M)
  2. Verifique o estado: sudo systemctl status <serviço> / sudo docker ps
  3. Após a alteração, verifique o resultado (logs, status, conexão de teste).
  4. 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 .env ou 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 local permite o redirecionamento local (ssh -L — para acessar um serviço em 127.0.0.1 antes de configurar o domínio) e proíbe o remoto (-R). Se o encaminhamento não for necessário, use no.

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 é melhor true + 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 de docker 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:

  1. 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.).
  2. 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.
  3. 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").
  4. 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:

CategoriaExemplos de CaminhosPor Quê
Configs do sistema/etc inteironginx, 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.gzdump 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íticaComandoPara QuemJustificativa
Básica (padrão)--keep-daily 7 --keep-weekly 4 --keep-monthly 6A maioria dos servidores de produçãoReversã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 4Sites/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 12BDs com dados importantes, finanças, requisitos legaisUm 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

...

Habilidades Relacionadas