Skip to content

Instantly share code, notes, and snippets.

@luizomf
Last active June 1, 2026 01:31
Show Gist options
  • Select an option

  • Save luizomf/b16a0f3318dbf1193e871bf6c6660725 to your computer and use it in GitHub Desktop.

Select an option

Save luizomf/b16a0f3318dbf1193e871bf6c6660725 to your computer and use it in GitHub Desktop.
Claude Code on VPS with Remote Control Enabled (Safe Mode and YOLO Mode).

Claude Code no VPS + Hardening do zero (ou quase)

Roteiro para configurar um VPS Ubuntu 24.04 a partir do root até um ambiente endurecido com usuário não-root, SSH key-only, fail2ban, UFW e diretório de deploy pronto.

Otávio aqui: isso foi 99% escrito pelo Claude usando meu DEV_GUIDE de outros vídeos.


Pré-requisitos

VPS já provisionado e acesso via root.


Fase 1: Como root

Acesse o VPS conforme o provedor permitir. Na Hostinger isso pode ser feito na tela do hPanel em VPS -> Visão Geral -> Terminal.

Depois crie o seu usuário

apt update && apt upgrade -y

useradd -m -s /bin/bash luizotavio
usermod -aG sudo luizotavio

# Isso é para configurar a senha do seu usuário
passwd luizotavio

O que acontece:

  • apt update && upgrade aplica patches de segurança pendentes.
  • useradd -m -s /bin/bash cria o usuário com home (-m) e shell bash (-s).
  • usermod -aG sudo coloca no grupo sudo (no Ubuntu, esse grupo é o que recebe permissão via /etc/sudoers).
  • passwd define a senha que o sudo vai pedir mais tarde (mesmo com NOPASSWD configurado depois, é boa prática ter senha local pra console).

(Opcional) dotfiles para o root:

Isso é pra configurar tudo em modo vim. TUDO MESMO, incluindo o terminal. Se você não entendeu algo do que está escrito nessa frase, pule este trecho.

# De novo, modo vim, se não sabe o que é, não aplique isso.
GIST_BASE='https://gist.githubusercontent.com/luizomf/9a52ba5b7b43aa69cc9a7121795bb9fa/raw/30d935ffe0eeb8e191b3bb98582edcd8d074e378'
curl -fsSL "$GIST_BASE/.vimrc" -o /root/.vimrc
curl -fsSL "$GIST_BASE/bash_minimal_setup" >> /root/.bashrc

Substitua o gist pelo seu. Esse aí é o do Otávio (jjEsc, KISS auto session, etc.). Se não quiser personalizar, pula.

Atenção: se for adicionar ao grupo docker, só rode se o docker estiver instalado - caso contrário, usermod -aG docker é silencioso e o grupo nem existe:

# só se docker estiver instalado:
# usermod -aG docker luizotavio

Depois, vira o user (ou desconecta e reconecta como ele):

su luizotavio

Gere sua chave SSH:

Esses comandos são no seu computador:

# NO SEU COMPUTADOR
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/nome_da_chave -C "luizotavio"
# ENTER ENTER ENTER
ssh-copy-id -i ~/.ssh/nome_da_chave.pub luizotavio@IP_OU_HOST_DO_VPS
# Digite sua senha e tente acessar
ssh luizotavio@IP_OU_HOST_DO_VPS -i ~/.ssh/nome_da_chave.pub

Fase 2 — Como o novo usuário

2.1 Dotfiles

# Modo vim (opcional), se não sabe o que é, não aplique isso.
# Basta pular.
GIST_BASE='https://gist.githubusercontent.com/luizomf/9a52ba5b7b43aa69cc9a7121795bb9fa/raw/30d935ffe0eeb8e191b3bb98582edcd8d074e378'
curl -fsSL "$GIST_BASE/.vimrc" -o ~/.vimrc
curl -fsSL "$GIST_BASE/bash_minimal_setup" >> ~/.bashrc
. ~/.bashrc

2.2 sudo sem senha (NOPASSWD)

echo "luizotavio ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/luizotavio
sudo chmod 440 /etc/sudoers.d/luizotavio

Tradeoff honesto: isso libera sudo sem senha. Conforto enorme pra workflow com agente, mas se sua chave SSH vazar, é root direto. Se tu não roda agente, considere deixar sem NOPASSWD e digitar a senha quando preciso. Mas, vale lembrar que o agente não vai conseguir instalar nada se sua senha ou sem você permitir de alguma forma. Segurança vs Conforto, escolha.

A permissão 440 no arquivo é exigida pelo visudo — sem ela, sudo se recusa a ler o sudoers e pode até travar tudo.


Fase 3 — Hardening

3.1 Pacotes

sudo apt install -y vim curl ca-certificates htop python3 python3-dev acl \
  build-essential tree just fail2ban ufw
  • acl → ACLs estendidas (precisamos pra setfacl em /projects).
  • fail2ban + ufw → as duas ferramentas centrais do hardening.
  • just → command runner moderno (Justfile), opcional mas útil.

3.2 Timezone

sudo timedatectl set-timezone America/Sao_Paulo

Não é segurança, é sanidade. Logs em UTC viram quebra-cabeça depois.

3.3 SSH endurecido

sudo tee /etc/ssh/sshd_config.d/01_sshd_settings.conf >/dev/null <<'EOF'
# Hardened SSH config (drop-in, loaded before cloud-init defaults)
# Authentication
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
UsePAM no
AuthenticationMethods publickey

# Attack surface reduction
PermitUserEnvironment no
PermitUserRC no
X11Forwarding no
AllowStreamLocalForwarding no
AllowAgentForwarding no

# Session management
MaxAuthTries 4
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no

# Show ip on login/logout
PrintLastLog no
PrintMotd no
LogLevel ERROR
Banner none

# Allow tunnels
# Se não usar tunnels, mude para o trecho comentado
# AllowTcpForwarding no
# PermitOpen none
# PermitListen none
# GatewayPorts no
AllowTcpForwarding yes
PermitOpen any
PermitListen any
GatewayPorts yes
EOF
sudo chmod 600 /etc/ssh/sshd_config.d/01_sshd_settings.conf
sudo sshd -t && sudo systemctl restart ssh

3.4 fail2ban

sudo tee /etc/fail2ban/jail.local >/dev/null <<'EOF'
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
allowipv6 = auto

[sshd]
enabled = true
port = ssh
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
bantime.increment = true
bantime.factor = 2
bantime.max = 24h
EOF
sudo systemctl enable --now fail2ban

Tradução: depois de 5 falhas em 10 minutos, banido por 1h. A cada nova reincidência o tempo dobra (factor = 2), até no máximo 24h. Bom contra brute-force estúpido — não é solução final, mas reduz ruído absurdo.

backend = systemd lê os logs direto do journald (Ubuntu 24.04 não escreve mais em /var/log/auth.log por padrão).

3.5 UFW (firewall)

CRÍTICO: libere SSH antes de habilitar o UFW. Se não, o enable pode te trancar fora.

sudo ufw disable
yes | sudo ufw reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
# For static IP (if you have it)
# sudo ufw allow from YOUR_IP_ADDRESS_HERE to any port 22 proto tcp comment 'SSH Home'
# If you use the rule above, you should consider not using the rule below.
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
echo y | sudo ufw enable
  • Tudo que entra é negado por padrão.
  • Tudo que sai é liberado (necessário pro Claude Code Remote, apt, etc.).
  • Apenas 22, 80, 443 ficam abertos pro mundo.

Se você usa WireGuard, considere fechar até a 22 e abrir só pra rede VPN:

sudo ufw delete allow ssh
sudo ufw allow from 10.0.0.0/24 to any port 22

O Claude Code Remote continua funcionando porque é conexão outbound do VPS — você não precisa expor porta nenhuma pro mundo pra controlar pelo navegador/celular.

3.6 Diretório de deploy /projects

# NEW
# Just a link to /projects directory
sudo install -d /projects -o luizotavio -g luizotavio -m 2775
git config --global --add safe.directory /projects
sudo setfacl -m d:g:luizotavio:rwx /projects
ln -sfn /projects ~/projects

Resumo: qualquer arquivo criado em /projects (por você, por container, por agente) é acessível pelo grupo luizotavio sem chmod manual depois.


Fase 4 — Claude Code (opcional)

Se o template já vem com o Claude instalado em /root/.local, você só precisa copiar pra sua home e ajustar PATH. Se quer instalar do zero:

curl -fsSL claude.ai/install.sh | bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
. ~/.bashrc
claude   # primeiro login (interativo)

Always-on de verdade

O modo CLI normal (claude) morre quando você fecha o terminal. Para sobreviver à desconexão:

Opção A — tmux (rápido):

tmux new -s claude
claude --remote-control
# Ctrl+b d → detach (agente continua vivo)
# tmux attach -t claude → volta depois

Reboot do VPS mata tmux. Pra sobreviver a reboot, precisa systemd.

Opção B — systemd user service (produção):

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/claude-remote.service <<'EOF'
[Unit]
Description=Claude Code Remote Control
After=network-online.target

[Service]
ExecStart=%h/.local/bin/claude --remote-control
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
EOF
loginctl enable-linger "$USER"
systemctl --user daemon-reload
systemctl --user enable --now claude-remote

loginctl enable-linger é essencial — sem ele, o systemd user mata seus serviços quando você desloga.


Validação

Depois de rodar tudo, confirme:

# SSH endurecido?
sudo sshd -T | grep -iE 'passwordauth|permitroot|pubkeyauth'

# fail2ban ativo e watching SSH?
sudo fail2ban-client status sshd

# UFW ativo e regras certas?
sudo ufw status verbose

# Timezone OK?
timedatectl | grep -i 'time zone'

# /projects com setgid + ACL?
ls -ld /projects && getfacl /projects

O que não está coberto aqui

Coisas que valem estudo, fora do escopo deste roteiro mínimo:

  • unattended-upgrades — patches de segurança automáticos. Linha única: sudo dpkg-reconfigure -plow unattended-upgrades.
  • WireGuard — fechar 22/80/443 e expor tudo só pra VPN. Casa perfeito com o Claude Code Remote (que é outbound).
  • Traefik / reverse proxy — Solicitar claude para configurar, liberar no UFW e fazer checagens de segurança. Mantenha um registro A com valor * no DNS do domínio apontando para o IP do no VPS. Isso permite ter quantos subdomínios você quiser no mesmo servidor.

Créditos

Roteiro construído numa sessão Claude Code Remote rodando dentro do próprio VPS Hostinger. O conteúdo da conversa original está em claude-vps.txt nesta pasta — vale ler se quiser ver a investigação e o "porquê" de cada decisão.

By Claude com base no DEV_GUIDE.md e revisão do Otávio 🤝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment