SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-24

Rodar Claude Code em VPS remoto com tmux

Mantenha sessões do Claude Code ativas em um VPS Linux mesmo após perder o SSH. Veja instalação, medidas de segurança, tmux e falhas esperadas.

O problema é a tampa do portátil, não a CLI

O Claude Code funciona corretamente no portátil até fechar a tampa: a sessão SSH termina, a shell recebe um SIGHUP e o agente, que estava há três minutos a executar um teste, termina também. Execute a CLI numa máquina que nunca entra em suspensão, dentro de um multiplexador de terminal cujos processos não sejam filhos da sessão SSH. Esse é todo o truque. O tmux, e não a instalação, é a parte essencial.

Esta página explica como operar uma máquina onde deixa agentes em execução. Se não tem um servidor Linux que possa deixar ligado, nada disto se aplica. Esse é o único pré-requisito que é importante declarar.

O que o tmux faz na prática

Quando inicia uma sessão SSH, sshd cria um processo filho para a shell e atribui-lhe um pseudo-terminal; tudo o que iniciar nessa shell será um processo filho dela. Se a ligação cair, o kernel destrói o pty, a shell recebe SIGHUP e, por sua vez, envia o sinal de término aos seus processos filhos. Os processos em execução em primeiro plano terminam.

O tmux altera esta relação de propriedade. O comando tmux que introduz é um cliente leve que comunica, através de um socket Unix, com um servidor tmux executado de forma independente do seu terminal. As shells dentro de uma sessão são processos filhos desse servidor, não de sshd. Se terminar a ligação SSH, o cliente desaparece, mas o servidor, a sessão e o agente que está a executar a tarefa continuam ativos. Ligue-se novamente, execute tmux attach e regressará à mesma shell, com o mesmo histórico de terminal. nohup também sobrevive a uma interrupção da ligação, mas não permite voltar à sessão; não é possível voltar a associar-se a uma TUI executada em segundo plano. O Claude Code é interativo; o tmux (ou screen) é a ferramenta adequada.

Dimensionar o servidor

A CLI é um processo Node; não é ela que preenche a máquina. O que ocupa a máquina é tudo o que o agente executa em seu nome: uma compilação, uma suíte completa de testes, tsc, um servidor de linguagem ou uma base de dados no Docker. Dimensione o servidor para a cadeia de ferramentas, não para a CLI. Adicione swap mesmo que planeie nunca a utilizar. Isso transforma uma terminação forçada por falta de memória numa compilação lenta:

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

Monitorize também o disco: repositórios, node_modules e imagens Docker acumulam-se rapidamente. E, se a cadeia de ferramentas ultrapassar os contentores e chegar a máquinas virtuais completas, como um convidado KVM ou um nó Kubernetes local, confirme, antes de avançar, que o plano expõe as extensões de virtualização da CPU, porque executar virtualização aninhada numa VPS é algo que o fornecedor ativa por si, e não algo que possa ativar a partir do convidado.

Primeiro, um utilizador sem root

Crie um utilizador dedicado com o seu próprio diretório pessoal e coloque a sua chave pública no local correto:

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

Teste o início de sessão a partir de um segundo terminal antes de avançar, enquanto a autenticação por palavra-passe ainda está disponível como alternativa. Se aparecer Permissão negada (chave pública), a causa costuma ser o proprietário ou o modo desse diretório .ssh, e não a própria chave.

Por decisão deliberada, agent não pertence ao grupo sudo. Se for necessário um pacote do sistema, instale-o. Esta decisão elimina a maioria das formas pelas quais um comando de shell executado por engano pode danificar o host.

Higiene do SSH para um servidor que fica sempre ligado

A autenticação por palavra-passe numa máquina exposta à Internet pública durante todo o dia, que mantém um agent e o seu código-fonte, é um risco que não vale a pena manter. Desative-a. No Ubuntu 24.04 e no Debian 13, /etc/ssh/sshd_config inclui /etc/ssh/sshd_config.d/*.conf, portanto crie um ficheiro em vez de editar a configuração principal:

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

Valide e recarregue a configuração. Mantenha a sessão atual aberta enquanto testa uma nova sessão a partir de um segundo terminal:

sudo sshd -t && sudo systemctl restart ssh

Há uma particularidade no Ubuntu 24.04: o sshd é ativado por socket. As definições de autenticação aplicam-se a systemctl restart ssh, mas uma alteração ao Port que está à escuta também requer systemctl daemon-reload e um reinício de ssh.socket.

Depois, configure a firewall. Permita o SSH antes de a ativar, ou ficará sem acesso ao servidor:

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

Instale fail2ban sabendo claramente o que isso proporciona: depois de desativar a autenticação por palavra-passe, um ataque de força bruta já não consegue ter êxito. O fail2ban mantém as tentativas falhadas fora do seu journal.

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

Por fim, instale automaticamente as atualizações com sudo apt install unattended-upgrades e sudo dpkg-reconfigure -plow unattended-upgrades. Tenha em conta a interação com o tmux: ative Unattended-Upgrade::Automatic-Reboot e uma atualização do kernel reiniciará o servidor, encerrando todas as sessões. Mantenha-o desativado e reinicie segundo o seu próprio calendário, quando não houver nenhuma tarefa em execução. A mesma precaução aplica-se a uma atualização de versão: atualizar o servidor do Ubuntu 24.04 para 26.04 reinicia o sshd e o kernel, portanto faça-o numa janela em que nenhuma sessão do tmux esteja a executar trabalho importante.

Instale Node.js e Claude Code no Ubuntu

Claude Code é um CLI Node, por isso precisa de uma versão atual do Node. O pacote da distribuição costuma estar desatualizado; NodeSource é a opção habitual no Ubuntu e no Debian e fornece um repositório assinado (sem apt-key, essa ferramenta foi removida):

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

Agora vem a parte que costuma ser feita de forma incorreta: instale o CLI como o seu utilizador agent, nunca com sudo npm -g. Um prefixo global pertencente a root provoca erros de permissões mais tarde e deixa ficheiros pertencentes a root na cache do npm. Defina primeiro o prefixo do npm para o diretório home do utilizador:

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

A exportação deve ficar em ~/.bashrc, não em ~/.profile, e deve ficar acima da proteção "If not running interactively, don't do anything" no início do ficheiro: o tmux pode iniciar shells que não são de login, que leem ~/.bashrc e ignoram ~/.profile; ~/.profile só é executado em shells de login. Um Node por utilizador através de um gestor de versões como nvm obtém o mesmo resultado; em qualquer dos casos, o objetivo é que npm install -g nunca precise de sudo. O npm continua a funcionar corretamente, ou pode usar o script de instalação nativo da Anthropic, que é a opção predefinida atualmente documentada. Consulte a documentação de instalação da Anthropic antes de colar comandos, porque os métodos de instalação mudam.

Execute claude dentro de um repositório para o iniciar. Na primeira execução, é guiado pelo processo de autenticação; um servidor sem interface gráfica não tem navegador, por isso o processo fornece um URL para abrir no seu próprio computador e um código para devolver ao terminal. (Uma chave de API no ambiente é a outra opção.) Em qualquer dos casos, essa credencial passa a ficar no servidor, o que nos leva à parte que muitas pessoas ignoram.

A conversa sobre o raio de impacto

Um agente com acesso à shell tem acesso à shell. Pode ler tudo o que o utilizador com que é executado consegue ler e enviar alterações para qualquer destino onde esse utilizador possa enviar alterações. Isto não é uma crítica à ferramenta. É a sua definição. Por isso, a conta com que o agente é executado importa mais do que qualquer configuração individual.

  • Utilizador dedicado e sem privilégios. Sem o grupo sudo e sem um diretório pessoal partilhado com a sua própria conta.
  • Sem credenciais de produção no servidor. Sem ~/.aws/credentials com chaves de produção, sem .env copiado da produção e sem uma palavra-passe de base de dados com acesso de escrita a algo importante. Dê ao agente uma credencial de staging ou apenas de leitura.
  • Tokens com escopo limitado. Um token do GitHub com permissões granulares limitado a um repositório; uma deploy key quando o acesso de leitura for suficiente.

O Claude Code fornece uma flag que ignora completamente os pedidos de permissão. Num portátil ou num projeto descartável, a decisão é sua. Num servidor que contém tokens, essa opção remove a última barreira entre uma instrução interpretada incorretamente e um git push --force. Os pedidos de permissão ignorados também não são uma escolha binária. Com o modo automático a chegar como novo padrão, é importante saber qual modo de permissão deve ser fixado num servidor que não está a monitorizar. A explicação do que a flag realmente altera e de como conter um agente que é executado com essa opção, desde a sandbox integrada até um VPS descartável, está em executar o Claude Code em segurança num servidor.

Deploy key ou encaminhamento do agente SSH

É tentador fazer ssh -A para que o git possa utilizar a chave do seu portátil. Entenda o que isso permite: o encaminhamento do agente expõe o socket do seu agente SSH local aos processos executados como esse utilizador no servidor. Qualquer processo executado como agent, incluindo o agente, pode pedir à sua chave para assinar para qualquer host a que consiga chegar, enquanto permanecer ligado. Isto é muito mais do que "permitir que o git faça pull deste repositório".

Em vez disso, gere uma chave no servidor, registe-a como deploy key específica do repositório (com acesso de escrita apenas se o agente precisar de fazer push) e configure uma identidade do git para que os commits feitos no servidor sejam identificá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 fluxo de trabalho com tmux

Instale-o (sudo apt install tmux) e crie uma configuração mínima em ~/.tmux.conf:

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

tmux new -A -s claude é o comando que deve memorizar. Liga-se à sessão se ela existir e cria-a se não existir. Assim, um único comando serve para começar o dia e para retomar o trabalho depois de uma interrupção. Crie um alias para este comando. Dentro de uma sessão, Ctrl-b c abre uma janela, Ctrl-b n e Ctrl-b p percorrem as janelas, e Ctrl-b [ entra no modo de cópia para recuar no histórico (q sai desse modo).

Há um aspeto importante sobre as sessões que nunca termina: o agente reenvia toda a conversa a cada interação. Por isso, leia quanto uma sessão prolongada do Claude Code consome de tokens antes de deixar uma sessão em execução durante uma semana.

Modos de falha

"A minha sessão desapareceu." tmux ls apresenta no server running on /tmp/tmux-1000/default. Quase sempre, isto significa que o processo nunca esteve dentro do tmux: iniciou uma sessão SSH, executou claude diretamente e a desconexão terminou o processo. Não há nada para recuperar. O hábito que evita este problema é executar tmux new -A -s <project> como o primeiro comando depois de cada início de sessão.

O painel fica reduzido a uma caixa minúscula. O tmux ajusta uma sessão ao menor cliente ligado. Por isso, um cliente antigo que ainda esteja ligado a partir de outra máquina pode reduzir a área de apresentação. Force a desconexão dos outros clientes ao ligar-se: tmux attach -d -t claude.

Uma compilação apresenta Killed. É uma mensagem de uma palavra, sem rastreio da pilha. Confirme com sudo dmesg -T | grep -i -E 'out of memory|killed process'. O kernel OOM killer selecionou o processo maior. No Node, pode ver FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Correções, por esta ordem: adicione swap (acima), limite o paralelismo dos testes e do compilador, aumente a heap do Node com NODE_OPTIONS=--max-old-space-size=... ou aumente o tamanho da VPS. O OOM killer também pode selecionar o servidor tmux em vez da compilação e terminar a sessão. Se systemd-oomd estiver em execução, pode terminar um slice de utilizador inteiro com o mesmo efeito.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Trata-se de uma instalação global num prefixo pertencente a root. Use o prefixo ~/.npm-global indicado acima. Se já executou sudo npm anteriormente, também pode ver Your cache folder contains root-owned files. Repare com sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found, mas apenas algumas vezes. A exportação PATH está em ~/.bashrc, abaixo da condição "If not running interactively, don't do anything". Por isso, as shells não interativas ignoram-na. Mova a exportação para cima dessa condição e mantenha-a em ~/.bashrc, não em ~/.profile: o tmux pode iniciar shells que não são de login, que leem ~/.bashrc e nunca consultam ~/.profile.

Cores corrompidas depois de ligar-se. Trata-se de uma incompatibilidade TERM. A linha default-terminal acima corrige o problema.

As sessões desaparecem depois de um reboot. Não é um erro: o servidor tmux é um processo e um reboot termina-o. Verifique uptime.

O que deixa de funcionar à medida que isto cresce

Mais projetos. Use uma sessão tmux por repositório, com um nome baseado no repositório; tmux ls passa a ser o seu painel. Se não mantiver uma convenção de nomes, terá sessões 0, 1 e 2. Quando várias estão em execução ao mesmo tempo, não precisam de funcionar de forma isolada, porque uma sessão pode enviar uma mensagem para outra no mesmo host, o que é útil quando o agente que está a executar uma refatoração longa precisa que outro agente execute os testes. As portas proliferam da mesma forma. Se seis repositórios quiserem usar :3000, deixe de as atribuir manualmente e permita que um reverse proxy Traefik encaminhe várias aplicações no Docker Compose com base no nome do host.

Mais pessoas. Os sockets do tmux são específicos de cada utilizador. Assim, dois programadores no mesmo host têm servidores tmux separados e não conseguem ver as sessões uns dos outros. Partilhar uma sessão através de um socket partilhado significa que todos escrevem na mesma shell e usam o mesmo utilizador Unix, com as consequências de auditoria e permissões daí resultantes. Utilizadores separados são a opção mais simples e correta.

Trabalho não supervisionado. O tmux destina-se a sessões interativas às quais se liga. Os trabalhos agendados que são executados sem supervisão devem usar uma unidade e um temporizador do systemd. Assim, obtêm registo, uma política de reinício e continuidade após o arranque sem configuração adicional. Usar tmux para executar um trabalho semelhante a um cron indica que esse trabalho deve ser um serviço.

Uma última nota: faça os servidores de desenvolvimento iniciados pelo agente escutar em 127.0.0.1, não em 0.0.0.0, e aceda a eles através de um túnel SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server), em vez de abrir portas no ufw. Quando estiver a encaminhar meia dúzia de portas, ou quando um telemóvel e um portátil precisarem de aceder à mesma pré-visualização, coloque antes uma VPN WireGuard auto-hospedada no VPS à frente desses serviços. Os servidores de desenvolvimento ficam associados a uma interface privada, e o ufw continua a rejeitar tudo o que venha da interface pública. A firewall só ajuda se deixar de abrir exceções nela.

O Claude Code não é a única opção: executar um agente de IA para programação num VPS também permite avaliar o Aider e o Goose.

FAQ

O Claude Code continua a executar depois de a ligação SSH cair?

Só se o tiver iniciado dentro de tmux. Um processo iniciado diretamente a partir da shell SSH é filho dessa shell e termina com o pty quando a ligação cai. Dentro do tmux, a shell pertence ao servidor tmux desanexado, por isso o agente continua a trabalhar na tarefa atual e tmux attach faz com que volte à mesma saída de terminal. Torne tmux new -A -s <project> o primeiro comando depois de cada login e o problema desaparece.

Devo instalar a CLI com sudo npm install -g?

Não. Um prefixo global pertencente a root provoca EACCES em instalações posteriores e cria ficheiros pertencentes a root na cache do npm. Defina o prefixo do npm como ~/.npm-global (ou use um gestor de versões como nvm), instale como o utilizador sem privilégios agent e exporte ~/.npm-global/bin para PATH a partir de ~/.bashrc, acima da proteção da shell interativa. Se já executou sudo npm uma vez, repare a cache com sudo chown -R $(id -u):$(id -g) ~/.npm.

O encaminhamento do agente ssh -A é seguro num servidor que executa um agente?

Concede muito mais acesso do que a tarefa necessita. O encaminhamento expõe o socket do seu agente SSH local a todos os processos executados como esse utilizador. Assim, qualquer processo no servidor pode pedir à sua chave para assinar ligações para qualquer host que consiga alcançar, enquanto permanecer ligado. Gere uma chave ed25519 no servidor e registe-a como chave de implementação específica do repositório, concedendo acesso de escrita apenas se o agente precisar efetivamente de fazer push.

Porque é que a minha compilação apenas mostra Killed?

Uma única palavra sem stack trace indica o OOM killer do kernel. Confirme com sudo dmesg -T | grep -i -E 'out of memory|killed process'; a partir do Node, poderá ver JavaScript heap out of memory em vez disso. Aplique as correções pela ordem indicada: adicione um swapfile, limite o paralelismo dos testes e do compilador, aumente NODE_OPTIONS=--max-old-space-size=... e, depois, aumente o tamanho do VPS. Tenha em atenção que o OOM killer pode escolher o servidor tmux em vez da compilação, encerrando toda a sua sessão.

tmux ou um serviço systemd?

O tmux é adequado para sessões interativas às quais se liga, que monitoriza e nas quais escreve comandos. É exatamente esse o caso de uma sessão de agente. O trabalho executado segundo um agendamento, sem ninguém a monitorizá-lo, deve pertencer a uma unidade e a um temporizador systemd. Nesse caso, o registo, a política de reinício e a sobrevivência a reboots ficam disponíveis de forma integrada. Se está a recorrer ao tmux para executar uma tarefa semelhante a um cron job, essa tarefa deve ser um serviço.