SSD Nodes Learn
Guias Matt ConnorPor Matt Connor · Atualizado 2026-07-24

Como rodar Claude Code no VPS com tmux

Evite que o SIGHUP encerre o Claude Code ao cair o SSH. Aprenda a usar tmux em um VPS Linux para manter sessões do agente ativas e persistentes.

O problema é a tampa do laptop, não o CLI

O Claude Code funciona normalmente no seu laptop até você fechá-lo: a sessão SSH cai, o shell recebe um SIGHUP e o agente morre junto após três minutos de execução de um teste. Execute o CLI em uma máquina que não entra em modo sleep, dentro de um multiplexador de terminal cujos processos não sejam filhos da sua sessão SSH. Esse é o segredo — o tmux, e não a instalação, é a parte essencial.

Esta página trata sobre operar um servidor onde você deixa agentes em execução. Se você não possui um servidor Linux que possa deixar ligado, nada disso se aplica. Esse é o único pré-requisito real.

O que o tmux realmente faz

Ao realizar o SSH, o sshd inicia um shell e atribui a ele um pseudo-terminal; todos os processos iniciados a partir desse shell são filhos dele. Se a conexão cair, o kernel encerra o pty, o shell recebe um SIGHUP e encerra seus processos filhos. Processos de primeiro plano de longa duração morrem.

O tmux inverte essa hierarquia. O comando tmux que você digita é um cliente leve que se comunica via unix socket com um tmux server que roda de forma independente do seu terminal. Os shells dentro de uma sessão são filhos desse servidor, não do sshd. Se a conexão SSH cair, o cliente é encerrado, mas o servidor, a sessão e o agente continuam rodando a tarefa. Ao reconectar, use tmux attach e você voltará para o mesmo shell com o mesmo histórico de rolagem. O nohup também sobrevive à queda da conexão, mas não permite o retorno — você não consegue re-anexar a uma TUI em segundo plano. O Claude Code é interativo; o tmux (ou screen) é a ferramenta correta.

Dimensionando a máquina

A CLI é um processo Node; ela não é o que consome os recursos da máquina. O que consome os recursos é o que o agent executa por você: um build, uma suíte completa de testes, tsc, um language server ou um banco de dados em Docker. Dimensionar para o toolchain, não para a CLI. Adicione swap mesmo que não pretenda usá-lo — isso transforma um OOM kill crítico em um build lento:

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Monitore o disco também: repositórios, node_modules e imagens Docker acumulam rápido. E se o toolchain ultrapassar os containers e chegar a máquinas virtuais completas — um guest KVM ou um nó local de Kubernetes — verifique se o plano oferece extensões de virtualização de CPU antes de contratar, pois rodar virtualização aninhada em um VPS é algo que o provedor habilita para você, e não algo que você ativa de dentro do guest.

Um usuário sem privilégios de root primeiro

Crie um usuário dedicado com seu próprio home e adicione sua chave pública:

sudo adduser --disabled-password --gecos "" agent
sudo install -d -m 700 -o agent -g agent /home/agent/.ssh
sudo cp ~/.ssh/authorized_keys /home/agent/.ssh/authorized_keys
sudo chown agent:agent /home/agent/.ssh/authorized_keys
sudo chmod 600 /home/agent/.ssh/authorized_keys

Propositalmente, agent não está no grupo sudo. Se um pacote do sistema for necessário, instale-o. Essa decisão remove a maioria das formas pelas quais um comando de shell isolado pode comprometer o host.

Higiene de SSH para máquinas ligadas continuamente

Manter a autenticação por senha em uma máquina exposta à internet pública, que contém um agent e seu código-fonte, é um risco desnecessário. Desative-a. No Ubuntu 24.04 e Debian 13, /etc/ssh/sshd_config inclui /etc/ssh/sshd_config.d/*.conf, então crie um arquivo em vez de editar o arquivo de configuração principal:

# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no

Valide e recarregue — mantenha sua sessão atual aberta enquanto testa uma nova sessão em um segundo terminal:

sudo sshd -t && sudo systemctl restart ssh

Um detalhe no Ubuntu 24.04: o sshd é ativado via socket. Configurações de autenticação aplicam-se em systemctl restart ssh, mas alterações no Port exigem systemctl daemon-reload e o reinício do ssh.socket.

Depois, o firewall. Libere o SSH antes de ativá-lo, ou você perderá o acesso:

sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

Instale o fail2ban sabendo exatamente o que ele oferece: uma vez desativada a autenticação por senha, ataques de brute force não terão sucesso — o software apenas evita que tentativas falhas poluam seu journal.

# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h

Por fim, aplique patches automaticamente com sudo apt install unattended-upgrades e sudo dpkg-reconfigure -plow unattended-upgrades. Note a interação com o tmux: se o Unattended-Upgrade::Automatic-Reboot estiver ativo e uma atualização de kernel reiniciar a máquina, todas as sessões serão encerradas. Deixe-o desativado e reinicie manualmente quando não houver processos em execução.

Instalar Node.js e Claude Code no Ubuntu

O Claude Code é uma CLI de Node, portanto você precisa de uma versão atual do Node. O pacote da distro costuma estar desatualizado; o NodeSource é o caminho padrão no Ubuntu e Debian, e ele fornece um repositório assinado (sem apt-key — essa ferramenta não existe mais):

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version

Agora a parte que as pessoas erram: instale a CLI como seu usuário agent, nunca com sudo npm -g. Um prefixo global pertencente ao root causa erros de permissão posteriormente e deixa arquivos do cache do npm pertencentes ao root. Aponte o prefixo do npm para o home do usuário primeiro:

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code
claude --version

O export deve ir no ~/.bashrc, não no ~/.profile, e deve ficar acima da trava "If not running interactively, don't do anything" no topo do arquivo: o tmux pode iniciar shells não-login, que leem ~/.bashrc e ignoram ~/.profile — o ~/.profile só é executado em shells de login. Usar um Node por usuário via um gerenciador de versões como o nvm alcança o mesmo resultado; o objetivo em ambos os casos é que o npm install -g nunca precise de sudo. O npm continua funcionando normalmente, ou você pode usar o script de instalação nativo da Anthropic, que é o padrão documentado atualmente. Verifique a documentação de instalação da Anthropic antes de colar — métodos de instalação mudam.

Execute claude dentro de um repositório para iniciar. A primeira execução guia você pela autenticação; um servidor headless não possui browser, então o fluxo fornece uma URL para abrir em sua própria máquina e um código para retornar ao terminal. (Uma API key no ambiente é a outra opção.) De qualquer forma, essa credencial agora reside no servidor — o que nos leva à parte que as pessoas pulam.

O conceito de blast radius

Um agente com acesso ao shell é um shell. Ele pode ler qualquer coisa que o usuário que o executa pode ler, e enviar dados para qualquer lugar que esse usuário possa enviar. Isso não é uma crítica à ferramenta, é a definição dela — e é por isso que a conta sob a qual ela é executada importa mais do que qualquer configuração individual.

  • Usuário dedicado e sem privilégios. Sem grupo sudo, sem diretório home compartilhado com sua própria conta.
  • Sem credenciais de produção na máquina. Sem ~/.aws/credentials contendo chaves de produção, sem .env copiadas da produção, sem senhas de banco de dados com acesso de escrita para algo importante. Forneça ao agente uma credencial de staging ou de apenas leitura.
  • Tokens com escopo limitado. Um token do GitHub de granularidade fina limitado a um repositório; uma deploy key quando o acesso de leitura for suficiente.

O Claude Code possui uma flag que pula completamente os prompts de permissão. Em um laptop, em um projeto temporário, a decisão é sua. Em um servidor que armazena tokens, isso remove a última barreira entre uma instrução mal interpretada e um git push --force. O que a flag realmente altera, e como conter um agente que a utiliza, desde o sandbox integrado até uma VPS descartável, é abordado em executando Claude Code com segurança em um servidor.

Deploy key vs SSH agent forwarding

É tentador usar ssh -A para que o git possa usar a chave no seu laptop. Entenda o que isso concede: o agent forwarding expõe o socket do seu agente SSH local para processos executados como esse usuário na máquina. Qualquer coisa executada como agent — incluindo o agente — pode solicitar que sua chave assine para qualquer host que ele consiga alcançar, desde que você permaneça conectado. Isso é muito mais do que "permitir que o git faça pull deste único repositório".

Gere uma chave no servidor em vez disso, registre-a como uma deploy key por repositório (acesso de escrita apenas se o agente precisar de push) e configure uma identidade git para que os commits da máquina sejam reconhecíveis:

ssh-keygen -t ed25519 -C "agent deploy key" -f ~/.ssh/id_ed25519_repo
cat ~/.ssh/id_ed25519_repo.pub   # paste into the repo's Deploy Keys
git config --global user.name "Agent (build box)"
git config --global user.email "agent@example.com"

O workflow do tmux

Instale-o (sudo apt install tmux) e depois use um ~/.tmux.conf mínimo:

set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"

Quatro comandos cobrem o uso diário:

tmux new -A -s claude     # attach to session "claude", creating it if absent
# ...run `claude` inside it, work normally...
# Ctrl-b then d           -> detach; everything keeps running
tmux ls                   # list sessions
tmux attach -t claude     # reattach, from this machine or any other
tmux kill-session -t claude

O tmux new -A -s claude é o que você deve memorizar — ele anexa a sessão se ela existir ou a cria se não existir. Assim, um único comando serve para iniciar o dia ou retomar o trabalho após uma queda. Crie um alias para ele. Dentro de uma sessão, Ctrl-b c abre uma janela, Ctrl-b n e Ctrl-b p alternam entre elas, e Ctrl-b [ entra no modo de cópia para rolar o histórico (q sai do modo).

Um detalhe sobre sessões que você nunca encerra: o agente reenvia toda a conversa em cada turno. Leia em que uma sessão longa do Claude Code gasta seus tokens antes de deixar uma sessão rodando por uma semana.

Modos de falha

"Minha sessão sumiu." tmux ls exibe no server running on /tmp/tmux-1000/default. Isso quase sempre significa que o processo nunca esteve dentro do tmux — você fez SSH, executou claude diretamente, e a desconexão o encerrou. Não há o que recuperar. O hábito que evita isso: tmux new -A -s <project> deve ser o primeiro comando após cada login.

O painel encolhe para uma caixa minúscula. O tmux ajusta o tamanho da sessão ao menor cliente conectado; portanto, um cliente inativo ainda conectado de outra máquina espreme a exibição. Force a desconexão dos outros ao se conectar: tmux attach -d -t claude.

Um build exibe Killed. Apenas uma palavra, sem stack trace. Confirme com sudo dmesg -T | grep -i -E 'out of memory|killed process' — o OOM killer do kernel selecionou o maior processo. No Node, você pode ver FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory em vez disso. Soluções, em ordem: adicione swap (acima), limite o teste e o paralelismo do compilador, aumente o heap do Node com NODE_OPTIONS=--max-old-space-size=..., ou aumente o tamanho da VPS. O OOM killer também pode selecionar o tmux server em vez do build, levando sua sessão junto; se systemd-oomd estiver rodando, ele pode matar um slice de usuário inteiro com o mesmo efeito.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Uma instalação global em um prefixo de propriedade do root. Use o prefixo ~/.npm-global acima. Se você já executou sudo npm em algum momento, também pode ver Your cache folder contains root-owned files — corrija com sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found — mas apenas às vezes. Seu export PATH está em ~/.bashrc abaixo da guarda "If not running interactively, don't do anything", então shells não interativos o ignoram. Mova o export para cima dessa guarda e mantenha-o em ~/.bashrc, não em ~/.profile: o tmux pode iniciar shells que não são login, os quais leem ~/.bashrc e nunca acessam ~/.profile.

Cores corrompidas após o attach. Um mismatch de TERM — a linha default-terminal acima é a solução.

Sessões desaparecem após um reboot. Não é um bug: o tmux server é um processo, e um reboot o encerra. Verifique uptime.

O que falha conforme isso cresce

Mais projetos. Uma sessão tmux por repo, nomeada conforme ele; tmux ls torna-se seu dashboard. Sem disciplina na nomenclatura, você terá sessões 0, 1, 2. Portas sofrem o mesmo problema — seis repositórios querendo a :3000 é o ponto para parar de atribuí-las manualmente e deixar um reverse proxy Traefik rotear múltiplos apps via Docker Compose fazer o despacho por hostname.

Mais pessoas. Sockets do tmux são por usuário, então dois desenvolvedores na mesma máquina possuem seus próprios servidores tmux e não conseguem ver as sessões um do outro. Compartilhar uma sessão via socket compartilhado significa que todos digitam no mesmo shell do mesmo usuário Unix, com todas as consequências de auditoria e permissão que isso implica. Usuários separados é a resposta tediosa e correta.

Trabalho não supervisionado. O tmux é para sessões interativas às quais você se conecta. Jobs que rodam em cronogramas sem supervisão devem estar em uma unit e timer do systemd, onde ganham logging, política de restart e sobrevivência ao boot gratuitamente. Usar o tmux para rodar um job estilo cron indica que o job deve ser um serviço.

Uma última nota: vincule os dev servers que o agent inicia para 127.0.0.1, não 0.0.0.0, e acesse-os via túnel SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server) em vez de abrir portas no ufw. Quando você estiver encaminhando meia dúzia de portas, ou quando um celular e um laptop precisarem da mesma preview simultaneamente, coloque uma VPN WireGuard self-hosted na VPS na frente deles: os dev servers vinculam-se a uma interface privada, e o ufw continua negando tudo da interface pública. O firewall só ajuda se você parar de abrir exceções nele.

Claude Code não é a única escolha: rodar um agente de IA de codificação em uma VPS envolve também Aider e Goose.

FAQ

O Claude Code continua rodando após a queda da conexão SSH?

Apenas se você o iniciou dentro do tmux. Um processo iniciado diretamente no shell SSH é um processo filho desse shell e morre quando o pty é encerrado. Dentro do tmux, o shell pertence ao servidor tmux detached, então o agente continua trabalhando na tarefa e o tmux attach retorna você para o mesmo scrollback. Use tmux new -A -s <project> como o primeiro comando após cada login para resolver o problema.

Devo instalar a CLI com sudo npm install -g?

Não. Um prefixo global com permissão de root causa erros de EACCES em instalações futuras e arquivos com dono root no cache do npm. Defina o prefixo do npm para ~/.npm-global (ou use um gerenciador de versão como o nvm), instale como o usuário sem privilégios agent, e exporte ~/.npm-global/bin para PATH a partir de ~/.bashrc, antes do guardião interativo. Se você já executou o sudo npm uma vez, repare o cache com o sudo chown -R $(id -u):$(id -g) ~/.npm.

O agent forwarding do ssh -A é seguro em uma máquina que já roda um agente?

Ele concede muito mais do que o processo precisa. O forwarding expõe o socket do seu agente SSH local para todos os processos rodando com esse usuário. Assim, qualquer processo na máquina pode solicitar que sua chave assine para qualquer host acessível enquanto você estiver conectado. Gere uma chave ed25519 no servidor e registre-a como uma deploy key por repositório, com acesso de escrita apenas se o agente realmente precisar realizar o push.

Por que meu build apenas imprime Killed?

Uma única palavra sem stack trace indica o kernel OOM killer. Confirme com o sudo dmesg -T | grep -i -E 'out of memory|killed process'; no Node, você pode ver JavaScript heap out of memory em vez disso. Aplique as correções nesta ordem: adicione um swapfile, limite o paralelismo de testes e do compilador, aumente o NODE_OPTIONS=--max-old-space-size=... e, por fim, aumente o tamanho da VPS. Cuidado, pois o OOM killer pode encerrar o servidor tmux em vez do build, derrubando toda a sua sessão.

tmux ou um serviço systemd?

O tmux é ideal para sessões interativas que você anexa, monitora e utiliza para digitar, que é exatamente o que uma sessão de agente exige. Trabalhos que rodam em horários agendados sem supervisão devem ser configurados em uma unit e timer do systemd, onde logging, política de reinicialização e persistência no boot já estão inclusos. Se você está tentando usar o tmux para rodar um job estilo cron, o job deve ser um serviço.