SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

Claude Code su VPS con tmux: sessioni SSH persistenti

Esegui Claude Code su un VPS Linux sempre acceso con tmux: le sessioni sopravvivono alla disconnessione SSH. Installazione, sicurezza ed errori da evitare.

Il problema è il coperchio del laptop, non la CLI

Claude Code funziona correttamente sul laptop finché non lo chiudi: la sessione SSH termina, la shell riceve un segnale SIGHUP e l’agent si interrompe dopo tre minuti di esecuzione del test. Esegui la CLI su una macchina che non entra mai in sospensione, all’interno di un multiplexer di terminale i cui processi non siano figli della sessione SSH. Questo è tutto il meccanismo. Il componente essenziale è tmux, non l’installazione.

Questa pagina riguarda la gestione di una macchina su cui lasciare gli agent in esecuzione. Se non hai un server Linux che puoi lasciare acceso, queste istruzioni non si applicano. Questo è l’unico prerequisito da dichiarare con chiarezza.

Che cosa fa realmente tmux

Quando stabilisci una connessione SSH, sshd crea un processo figlio con una shell e gli assegna un terminale pseudoterminale; tutto ciò che avvii da quella shell è un suo processo figlio. Se la connessione cade, il kernel elimina il pty, la shell riceve SIGHUP e a sua volta chiude la connessione con i processi figli. I processi in esecuzione in primo piano e di lunga durata terminano.

tmux inverte la gerarchia dei processi. Il comando tmux che digiti è un client leggero che comunica tramite un socket Unix con un server tmux, eseguito separatamente dal terminale. Le shell all'interno di una sessione sono processi figli del server, non di sshd. Se termini la connessione SSH, il client scompare, mentre il server, la sessione e l'agente impegnato nell'attività continuano a funzionare. Riconnettiti, esegui tmux attach e tornerai alla stessa shell con lo stesso scrollback. Anche nohup sopravvive a un hangup, ma non offre alcun modo per rientrare nella sessione: non puoi ricollegarti a una TUI eseguita in background. Claude Code è interattivo; tmux (o screen) è lo strumento appropriato.

Dimensionamento del server

La CLI è un processo Node; non è la CLI a riempire la macchina. A occupare spazio e risorse è ciò che l’agent esegue per conto dell’utente: una build, una suite completa di test, tsc, un language server o un database in Docker. Dimensionare il server in base alla toolchain, non alla CLI. Aggiungere anche lo swap, persino se si prevede di non usarlo mai: trasforma un arresto forzato per OOM in una build 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

Monitorare anche il disco: repository, node_modules e immagini Docker si accumulano rapidamente. Se la toolchain arriva oltre i container e utilizza macchine virtuali complete, ad esempio un guest KVM o un nodo Kubernetes locale, verificare che il piano esponga le estensioni di virtualizzazione della CPU prima di sottoscriverlo, perché eseguire la virtualizzazione annidata su un VPS è un’opzione che deve abilitare il provider, non un’impostazione attivabile dall’interno del guest.

Prima un utente non-root

Creare un utente dedicato con la propria home e installare la chiave pubblica:

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

Prima di procedere, verificare l'accesso da un secondo terminale, mentre l'autenticazione tramite password è ancora disponibile come fallback. Se viene restituito Accesso negato (publickey), il problema è solitamente il proprietario o la modalità della directory .ssh, non la chiave.

Per scelta, agent non appartiene al gruppo sudo. Se serve un pacchetto di sistema, lo si installa. Questa decisione elimina la maggior parte dei casi in cui un comando di shell eseguito per errore può danneggiare l'host.

Igiene SSH per un server lasciato sempre in esecuzione

L’autenticazione tramite password su una macchina esposta tutto il giorno a Internet, che contiene un agent e il codice sorgente, comporta un rischio non necessario. Disattivala. Su Ubuntu 24.04 e Debian 13, /etc/ssh/sshd_config include /etc/ssh/sshd_config.d/*.conf, quindi crea un file dedicato invece di modificare la configurazione principale:

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

Verifica la configurazione e ricaricala. Mantieni aperta la sessione corrente mentre testi una nuova sessione da un secondo terminale:

sudo sshd -t && sudo systemctl restart ssh

Su Ubuntu 24.04 c’è una particolarità: sshd viene attivato tramite socket. Le impostazioni di autenticazione si applicano su systemctl restart ssh, ma una modifica a Port richiede anche systemctl daemon-reload e il riavvio di ssh.socket.

Poi configura il firewall. Consenti SSH prima di abilitarlo, altrimenti perdi l’accesso:

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

Installa fail2ban sapendo esattamente cosa offre: dopo aver disattivato l’autenticazione tramite password, un attacco brute force non può comunque avere successo; il pacchetto impedisce però che i tentativi falliti finiscano nel journal.

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

Infine, applica automaticamente le patch con sudo apt install unattended-upgrades e sudo dpkg-reconfigure -plow unattended-upgrades. Considera l’interazione con tmux: abilita Unattended-Upgrade::Automatic-Reboot e un aggiornamento del kernel riavvierà il server, terminando tutte le sessioni. Lascialo disabilitato e riavvia secondo il tuo programma, quando non ci sono attività in esecuzione. La stessa cautela vale per un aggiornamento di release: passare il server da Ubuntu 24.04 a 26.04 riavvia sshd e il kernel, quindi va pianificato in una finestra in cui nessuna sessione tmux stia eseguendo attività importanti.

Installa Node.js e Claude Code su Ubuntu

Claude Code è una CLI Node, quindi serve una versione aggiornata di Node. Il pacchetto della distribuzione è spesso meno recente; su Ubuntu e Debian il percorso consueto è NodeSource, che fornisce un repository firmato (senza apt-key, perché quello strumento non è più disponibile):

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

Ora la parte che spesso viene gestita in modo errato: installa la CLI con l'utente agent, mai con sudo npm -g. Un prefisso globale di proprietà di root causa in seguito errori di autorizzazione e lascia file di proprietà di root nella cache di npm. Configura prima il prefisso di npm nella home dell'utente:

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

L'export va inserito in ~/.bashrc, non in ~/.profile, e deve trovarsi prima del controllo "If not running interactively, don't do anything" nella parte iniziale del file: tmux può avviare shell non di login, che leggono ~/.bashrc e saltano ~/.profile; ~/.profile viene eseguito soltanto dalle shell di login. Un'installazione di Node per singolo utente tramite un gestore di versioni come nvm risolve lo stesso problema; in entrambi i casi, l'obiettivo è che npm install -g non debba mai usare sudo. npm continua a funzionare correttamente; in alternativa, usa lo script di installazione nativo di Anthropic, che è il metodo predefinito documentato attualmente. Controlla la documentazione di installazione di Anthropic prima di incollare i comandi, perché i metodi di installazione cambiano.

Esegui claude all'interno di un repository per avviarlo. Al primo avvio verrai guidato nella procedura di autenticazione; un host headless non dispone di un browser, quindi la procedura fornisce un URL da aprire sul tuo computer e un codice da riportare nel terminale. L'altra possibilità è usare una API key nell'ambiente. In entrambi i casi, quella credenziale viene ora memorizzata sul server; questo ci porta alla parte che spesso viene trascurata.

La discussione sul raggio d'azione di un incidente

Un agente con accesso alla shell è una shell. Può leggere tutto ciò che l'utente con cui viene eseguito può leggere e inviare modifiche ovunque quell'utente abbia i permessi per farlo. Non è una critica allo strumento: è la sua definizione. Per questo l'account con cui viene eseguito è più importante di qualsiasi singola impostazione.

  • Utente dedicato senza privilegi. Nessun gruppo sudo e nessuna home directory condivisa con il proprio account.
  • Nessuna credenziale di produzione sul server. Nessun ~/.aws/credentials contenente chiavi di produzione, nessun .env copiato dall'ambiente di produzione e nessuna password del database con accesso in scrittura a risorse importanti. Assegnare all'agente una credenziale di staging o di sola lettura.
  • Token con ambito limitato. Un token GitHub con autorizzazioni granulari limitato a un solo repository; una deploy key quando è sufficiente l'accesso in lettura.

Claude Code include un flag che ignora completamente le richieste di autorizzazione. Su un laptop o in un progetto temporaneo, la scelta spetta a voi. Su un server che contiene token, elimina l'ultimo ostacolo tra un'istruzione interpretata in modo errato e una git push --force. Le richieste di autorizzazione che verrebbero ignorate non sono nemmeno una scelta binaria e, con la modalità automatica che diventa la nuova impostazione predefinita, è utile sapere a quale modalità di autorizzazione vincolare un server che non state monitorando. Le conseguenze effettive del flag e i metodi per contenere un agente eseguito con questa opzione, dalla sandbox integrata fino a un VPS temporaneo, sono descritti in eseguire Claude Code in sicurezza su un server.

Deploy key e inoltro dell'agente SSH

È invitante ssh -A in modo che git possa usare la chiave sul laptop. È importante capire cosa comporta: l'inoltro dell'agente espone il socket dell'agente SSH locale ai processi eseguiti come quell'utente sul server. Qualsiasi processo eseguito come agent, incluso l'agente, può chiedere alla chiave di firmare per qualsiasi host raggiungibile, finché la connessione resta attiva. È molto più di «consentire a git di eseguire il pull di questo unico repository».

Generate invece una chiave sul server, registratela come deploy key per il singolo repository, assegnando l'accesso in scrittura solo se l'agente deve eseguire il push, quindi configurate un'identità git in modo che i commit provenienti dal server siano riconoscibili:

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"

Il flusso di lavoro con tmux

Installalo (sudo apt install tmux), quindi crea una configurazione minima ~/.tmux.conf:

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

Quattro comandi coprono l'uso quotidiano:

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 è il comando da memorizzare: si collega alla sessione se esiste e la crea se non esiste. Un solo comando copre quindi sia l'avvio della giornata sia la ripresa del lavoro dopo una disconnessione. Creane un alias. All'interno di una sessione, Ctrl-b c apre una finestra, mentre Ctrl-b n e Ctrl-b p permettono di scorrerle. Ctrl-b [ attiva la modalità di copia per scorrere indietro (q la chiude).

È importante sapere una cosa sulle sessioni che non vengono mai terminate: l'agente invia nuovamente l'intera conversazione a ogni turno. Prima di lasciare una sessione attiva per una settimana, leggi come una sessione persistente di Claude Code consuma i token.

Modalità di errore

"La mia sessione è scomparsa." tmux ls restituisce no server running on /tmp/tmux-1000/default. Quasi sempre significa che il processo non era mai stato avviato dentro tmux: hai eseguito claude direttamente dopo aver effettuato l’accesso via SSH e la disconnessione lo ha terminato. Non c’è nulla da recuperare. Per evitarlo, esegui tmux new -A -s <project> come primo comando dopo ogni accesso.

Il pane si riduce a una casella molto piccola. tmux adatta le dimensioni della sessione al client collegato più piccolo. Un client obsoleto ancora collegato da un’altra macchina può quindi ridurre l’area visualizzata. Forza la disconnessione degli altri client durante il collegamento con tmux attach -d -t claude.

Una compilazione restituisce Killed. Compare una sola parola, senza stack trace. Verifica con sudo dmesg -T | grep -i -E 'out of memory|killed process': il kernel OOM killer ha terminato il processo che consumava più memoria. Da Node potresti invece visualizzare FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Procedi nell’ordine seguente: aggiungi swap (come indicato sopra), limita il parallelismo dei test e del compilatore, aumenta l’heap di Node con NODE_OPTIONS=--max-old-space-size=... oppure passa a un VPS con più risorse. L’OOM killer può terminare anche il server tmux invece della compilazione, chiudendo la sessione. Se è in esecuzione systemd-oomd, può terminare un’intera slice utente con lo stesso risultato.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. È stata eseguita un’installazione globale in un prefisso di proprietà di root. Usa il prefisso ~/.npm-global indicato sopra. Se in precedenza hai eseguito sudo npm, potresti visualizzare anche Your cache folder contains root-owned files. Correggi il problema con sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found, ma solo in alcuni casi. L’export PATH si trova in ~/.bashrc, sotto il controllo "If not running interactively, don't do anything"; per questo le shell non interattive lo ignorano. Sposta l’export sopra quel controllo e lascialo in ~/.bashrc, non in ~/.profile: tmux può avviare shell non di login, che leggono ~/.bashrc e non elaborano mai ~/.profile.

Colori alterati dopo il collegamento. Si tratta di una mancata corrispondenza TERM. La riga default-terminal riportata sopra risolve il problema.

Le sessioni scompaiono dopo un riavvio. Non è un bug: il server tmux è un processo e il riavvio lo termina. Controlla uptime.

Cosa si rompe quando il sistema cresce

Più progetti. Usa una sessione tmux per ogni repository e assegnale il nome del repository; tmux ls diventa così il tuo pannello di controllo. Se non mantieni questa convenzione, ottieni sessioni 0, 1, 2. Quando ne esegui diverse contemporaneamente, non devono necessariamente operare in isolamento: una sessione può inviare un messaggio a un'altra sullo stesso host, utile quando l'agent che sta eseguendo un refactoring lungo vuole incaricare un secondo agent di eseguire i test. Anche le porte si moltiplicano nello stesso modo: quando sei arrivato a sei repository che richiedono tutti :3000, devi smettere di assegnarle manualmente e lasciare che un reverse proxy Traefik esegua il routing di più applicazioni tramite Docker Compose usando il nome host.

Più persone. I socket tmux sono specifici dell'utente, quindi due sviluppatori sullo stesso host hanno ciascuno il proprio server tmux e non possono vedere le sessioni dell'altro. Condividere una sessione tramite un socket condiviso significa che tutti digitano nella stessa shell con lo stesso utente Unix, con le conseguenze che ne derivano per auditing e autorizzazioni. Usare utenti separati è la scelta semplice e corretta.

Lavoro non presidiato. tmux è progettato per sessioni interattive a cui ci si collega. I job pianificati che devono essere eseguiti senza supervisione appartengono a una unit e a un timer systemd, che forniscono logging, una policy di riavvio e la continuità dopo il boot. Usare tmux per eseguire un job di tipo cron indica che il job dovrebbe essere un servizio.

Un'ultima nota: associa i dev server avviati dall'agent a 127.0.0.1, non a 0.0.0.0, e raggiungili tramite un tunnel SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server) invece di aprire porte in ufw. Quando inoltri una mezza dozzina di porte, oppure un telefono e un laptop devono accedere entrambi allo stesso ambiente di anteprima, configura invece una VPN WireGuard self-hosted sul VPS davanti a questi servizi: i dev server restano in ascolto su un'interfaccia privata e ufw continua a negare tutte le connessioni provenienti da quella pubblica. Il firewall è utile solo se smetti di aprirvi continuamente nuove eccezioni.

Claude Code non è l'unica scelta: eseguire un agent AI per la programmazione su un VPS significa valutare anche Aider e Goose.

FAQ

Claude Code continua a essere eseguito dopo l'interruzione della connessione SSH?

Solo se lo hai avviato all'interno di tmux. Un processo avviato direttamente dalla shell SSH è figlio di quella shell e termina con il pty quando la connessione cade. All'interno di tmux, la shell appartiene al server tmux scollegato, quindi l'agent continua a lavorare sull'attività in corso e tmux attach ti riporta alla stessa cronologia del terminale. Imposta tmux new -A -s <project> come primo comando dopo ogni accesso e il problema scompare.

Devo installare la CLI con sudo npm install -g?

No. Un prefisso globale di proprietà di root provoca errori EACCES nelle installazioni successive e crea file di proprietà di root nella cache di npm. Imposta il prefisso di npm su ~/.npm-global (oppure usa un version manager come nvm), installa come utente non privilegiato agent ed esporta ~/.npm-global/bin in PATH da ~/.bashrc, prima del controllo della shell interattiva. Se hai già eseguito sudo npm una volta, ripara la cache con sudo chown -R $(id -u):$(id -g) ~/.npm.

Il forwarding dell'agent ssh -A è sicuro su un server che esegue un agent?

Concede molti più privilegi di quelli necessari all'attività. Il forwarding espone il socket dell'agent SSH locale a ogni processo eseguito da quell'utente, quindi qualsiasi processo sul server può chiedere alla tua chiave di firmare per qualunque host raggiungibile, per tutto il tempo in cui resti connesso. Genera una chiave ed25519 sul server e registrala come deploy key per il singolo repository, concedendo l'accesso in scrittura solo se l'agent deve effettivamente eseguire operazioni di push.

Perché la mia build stampa soltanto Killed?

Una sola parola senza stack trace indica l'intervento dell'OOM killer del kernel. Confermalo con sudo dmesg -T | grep -i -E 'out of memory|killed process'; da Node potresti vedere invece JavaScript heap out of memory. Applica le correzioni nell'ordine: aggiungi un file di swap, limita il parallelismo dei test e del compilatore, aumenta NODE_OPTIONS=--max-old-space-size=..., quindi passa a un VPS più grande. Tieni presente che l'OOM killer può terminare il server tmux invece della build, interrompendo l'intera sessione.

tmux o un servizio systemd?

tmux è adatto alle sessioni interattive a cui ti connetti, che osservi e in cui immetti comandi. È esattamente il caso di una sessione dell'agent. Le attività pianificate senza un operatore appartengono a un'unità e a un timer systemd, che offrono logging, una policy di riavvio e la continuità dopo il boot senza configurazioni aggiuntive. Se stai usando tmux per eseguire un'attività simile a un cron job, quell'attività dovrebbe essere un servizio.