SSD Nodes Learn
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-07-24

Eseguire Claude Code su VPS con tmux

Evita che la sessione SSH interrompa Claude Code. Scopri come usare tmux su Linux VPS per mantenere l'agent attivo anche dopo la chiusura della connessione.

Il problema è il coperchio del laptop, non la CLI

Claude Code funziona correttamente sul laptop finché non viene chiuso: la sessione SSH si interrompe, la shell riceve un SIGHUP e l'agent si arresta dopo tre minuti di esecuzione di un test. Esegui la CLI su una macchina che non entra mai in standby, all'interno di un multiplexer terminale i cui processi non siano figli della sessione SSH. Questo è il punto fondamentale: l'elemento essenziale è tmux, non l'installazione.

Questa pagina riguarda l'utilizzo di un server su cui vengono lasciati agent in esecuzione. Se non disponi di un server Linux che possa rimanere acceso, queste istruzioni non sono applicabili. Questo è l'unico prerequisito necessario.

Cosa fa effettivamente tmux

Quando effettui l'accesso tramite SSH, sshd crea un processo fork di una shell e le assegna un pseudo-terminale; ogni processo avviato da quella shell è un suo figlio. Se la connessione cade, il kernel chiude il pty, la shell riceve un segnale SIGHUP e interrompe a sua volta i suoi processi figli. I processi in foreground che richiedono molto tempo vengono terminati.

tmux inverte la gestione della proprietà. Il comando tmux che digiti è un client leggero che comunica tramite un unix socket a un tmux server che gira in modo indipendente dal tuo terminale. Le shell all'interno di una sessione sono processi figli di quel server, non di sshd. Se chiudi la connessione SSH, il client viene rimosso mentre il server, la sessione e l'agent continuano a lavorare. Riconnettiti, tmux attach, e tornerai nella stessa shell con lo stesso buffer di scorrimento. Anche nohup sopravvive alla chiusura della connessione, ma non permette il rientro — non è possibile effettuare il re-attach a una TUI in background. Claude Code è interattivo; tmux (o screen) è lo strumento corretto.

Dimensionamento del server

La CLI è un processo Node; non è questo che occupa le risorse della macchina. Ciò che occupa la macchina è qualsiasi processo eseguito dall'agent per tuo conto: una build, una suite completa di test, tsc, un language server o un database in Docker. Dimensiona il sistema in base alla toolchain, non alla CLI. Aggiungi dello swap anche se non intendi utilizzarlo: trasforma un crash OOM improvviso 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

Monitora anche il disco: repository, node_modules e immagini Docker si accumulano rapidamente. Se la toolchain richiede l'uso di macchine virtuali complete — un guest KVM o un nodo Kubernetes locale — verifica che il piano includa le estensioni di virtualizzazione CPU prima di procedere. Eseguire la virtualizzazione nidificata su un VPS è una funzione che il provider deve abilitare; non è un'opzione attivabile dall'interno del guest.

Un utente non-root per primo

Crea un utente dedicato con la propria home directory e inserisci la tua 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

Appositamente, agent non fa parte del gruppo sudo. Se è necessario un pacchetto di sistema, installalo manualmente. Questa scelta elimina la maggior parte delle modalità con cui un comando shell errato può compromettere l'host.

Igiene SSH per un server sempre attivo

L'autenticazione tramite password su una macchina esposta su internet, che contiene un agent e il tuo codice sorgente, rappresenta un rischio non necessario. Disabilitala. Su Ubuntu 24.04 e Debian 13, /etc/ssh/sshd_config include /etc/ssh/sshd_config.d/*.conf, quindi è preferibile creare un nuovo file invece di modificare la configurazione principale:

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

Valida e ricarica — mantieni la sessione corrente aperta mentre testi una nuova sessione da un secondo terminale:

sudo sshd -t && sudo systemctl restart ssh

Nota tecnica su Ubuntu 24.04: sshd è attivato tramite socket. Le impostazioni di autenticazione si applicano su systemctl restart ssh, ma una modifica al listening Port richiede anche systemctl daemon-reload e il riavvio di ssh.socket.

Passiamo al firewall. Abilita SSH prima di disabilitare le password, altrimenti perderai l'accesso al server:

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

Installa fail2ban valutando i vantaggi: una volta disabilitata l'autenticazione tramite password, gli attacchi brute force non avranno successo; l'installazione serve a evitare che i tentativi falliti riempiano il journal.

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

Infine, applica le patch automaticamente con sudo apt install unattended-upgrades e sudo dpkg-reconfigure -plow unattended-upgrades. Nota l'interazione con tmux: se attivi Unattended-Upgrade::Automatic-Reboot, un aggiornamento del kernel riavvierà il server, chiudendo ogni sessione. Se lo lasci disattivato, puoi pianificare i riavvii manualmente quando non ci sono processi in esecuzione.

Install Node.js and Claude Code on Ubuntu

Claude Code è un'interfaccia CLI basata su Node, quindi è necessaria una versione recente di Node. I pacchetti della distribuzione ufficiale sono spesso obsoleti; su Ubuntu e Debian si utilizza solitamente NodeSource, che fornisce un repository firmato (senza apt-key — lo strumento non è più disponibile):

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

L'errore comune è il seguente: installa la CLI come utente agent, mai con sudo npm -g. Un prefix globale di proprietà di root causa errori di permessi in seguito e lascia file di proprietà di root nella cache di npm. Imposta il prefix 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 della clausola "If not running interactively, don't do anything" situata all'inizio del file: tmux può avviare shell non-login, che leggono ~/.bashrc e saltano ~/.profile~/.profile viene eseguito solo per le shell di login. Utilizzare Node per singolo utente tramite un version manager come nvm produce lo stesso risultato; l'obiettivo è che npm install -g non richieda mai sudo. npm funziona correttamente, oppure è possibile utilizzare lo script di installazione nativo di Anthropic, l'opzione predefinita attualmente documentata. Controlla la documentazione di installazione di Anthropic prima di procedere — i metodi di installazione cambiano.

Esegui claude all'interno di una repository per avviarlo. La prima esecuzione guida l'utente nell'autenticazione; un server headless non ha un browser, quindi il processo fornisce un URL da aprire sulla propria macchina e un codice da reinserire nel terminale. (Un'altra opzione è l'uso di una API key nell'ambiente.) In entrambi i casi, la credenziale viene salvata sul server — il che ci porta alla sezione che molti saltano.

Il concetto di blast radius

Un agent con accesso alla shell è una shell. Può leggere tutto ciò che l'utente che lo esegue può leggere, e può inviare dati ovunque quell'utente possa inviarli. Questa non è una critica allo strumento, è la sua definizione — ed è per questo che l'account sotto cui viene eseguito è più importante di qualsiasi singola impostazione.

  • Utente dedicato e non privilegiato. Nessun gruppo sudo, nessuna home directory condivisa con il proprio account.
  • Nessuna credenziale di produzione sulla macchina. Nessun ~/.aws/credentials che contenga chiavi di produzione, nessun .env copiato dalla produzione, nessuna password di database con permessi di scrittura su risorse critiche. Fornisci all'agent una credenziale di staging o di sola lettura.
  • Token limitati (scoped tokens). Un token GitHub a granularità fine limitato a un singolo repository; una deploy key quando l'accesso in sola lettura è sufficiente.

Claude Code include un flag che salta completamente le richieste di autorizzazione. Su un laptop, su un progetto temporaneo, la decisione spetta a te. Su un server che contiene token, questo flag rimuove l'ultimo ostacolo tra un'istruzione errata e un git push --force. Cosa cambia effettivamente il flag e come contenere un agent che lo utilizza, dal sandbox integrato fino a un VPS usa e getta, è trattato in eseguire Claude Code in sicurezza su un server.

Deploy key vs SSH agent forwarding

È tentante usare ssh -A affinché git possa utilizzare la chiave sul proprio laptop. Comprendi cosa comporta: l'agent forwarding espone il socket del proprio SSH agent locale ai processi in esecuzione come quell'utente sulla macchina. Qualsiasi processo eseguito come agent — incluso l'agent — può richiedere alla propria chiave di firmare per qualsiasi host raggiungibile, finché la connessione rimane attiva. Questo va ben oltre il semplice "permettere a git di fare il pull di questo repository".

Genera invece una chiave sul server, registrala come deploy key specifica per repository (accesso in sola scrittura solo se l'agent deve eseguire il push) e imposta un'identità git in modo che i commit dalla macchina 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 workflow di tmux

Installalo (sudo apt install tmux), poi configura un ~/.tmux.conf minimale:

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 connette se la sessione esiste e la crea se non esiste. Un unico comando serve sia per iniziare la giornata che per riprendere dopo una disconnessione. Crea un alias. All'interno di una sessione, Ctrl-b c apre una window, Ctrl-b n e Ctrl-b p ciclan le window, e Ctrl-b [ entra in copy mode per scorrere verso l'alto (q esce).

Una nota sulle sessioni che non si chiudono mai: l'agent invia nuovamente l'intera conversazione a ogni turno. Leggi su cosa consumano i token una sessione di Claude Code a lunga durata prima di lasciare una sessione attiva per una settimana.

Failure modes

"My session is gone." tmux ls stampa no server running on /tmp/tmux-1000/default. Questo significa quasi sempre che il processo non era dentro tmux — hai effettuato l'SSH, hai eseguito claude direttamente e la disconnessione lo ha terminato. Non c'è nulla da recuperare. L'abitudine per evitarlo: tmux new -A -s <project> è il primo comando dopo ogni login.

Il pane si rimpicciolisce in un piccolo riquadro. tmux dimensiona la sessione in base al client collegato più piccolo; un client rimasto collegato da un'altra macchina comprime il display. Forza la disconnessione degli altri durante l'attacco: tmux attach -d -t claude.

Un build stampa Killed. Una sola parola, nessuna stack trace. Verifica con sudo dmesg -T | grep -i -E 'out of memory|killed process' — il kernel OOM killer ha selezionato il processo più grande. Da Node potresti invece vedere FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Soluzioni, in ordine: aggiungi swap (sopra), limita i test e il parallelismo del compilatore, aumenta l'heap di Node con NODE_OPTIONS=--max-old-space-size=..., o aumenta le risorse del VPS. L'OOM killer potrebbe selezionare il tmux server invece del build, terminando la sessione; se systemd-oomd è in esecuzione, può terminare un intero user slice con lo stesso effetto.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Un'installazione globale in un prefix di proprietà root. Usa il prefix ~/.npm-global indicato sopra. Se hai già eseguito sudo npm in passato potresti vedere anche Your cache folder contains root-owned files — ripara con sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found — ma solo a volte. L'export PATH si trova in ~/.bashrc sotto il controllo "If not running interactively, don't do anything", quindi le shell non interattive lo saltano. Sposta l'export sopra quel controllo e mantienilo in ~/.bashrc, non in ~/.profile: tmux può avviare shell non-login, che leggono ~/.bashrc e non toccano mai ~/.profile.

Colori distorti dopo l'attacco. Un mismatch TERM — la riga default-terminal sopra è la soluzione.

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

Cosa si rompe con la crescita del sistema

Più progetti. Utilizza una sessione tmux per ogni repository, rinominandola in base ad esso; tmux ls diventa così la tua dashboard. Se non mantieni la disciplina nella denominazione, otterrai sessioni 0, 1, 2. Anche le porte seguono questo schema: se sei in presenza di sei repository che richiedono tutte la :3000, è il momento di smettere di assegnarle manualmente e lasciare che un reverse proxy Traefik instradi più app sotto Docker Compose gestisca il routing tramite hostname.

Più persone. I socket di tmux sono per utente; pertanto, due sviluppatori sulla stessa macchina avranno ciascuno il proprio server tmux e non potranno vedere le sessioni altrui. Condividere una sessione tramite un socket condiviso significa che tutti scriveranno nella stessa shell dello stesso utente Unix, con le relative conseguenze su audit e permessi. La soluzione corretta è utilizzare utenti separati.

Lavoro non supervisionato. tmux è progettato per sessioni interattive a cui ci si connette. I job che devono essere eseguiti a intervalli programmati senza supervisione devono essere gestiti tramite una unit e un timer di systemd; in questo modo si ottengono automaticamente logging, policy di restart e avvio automatico al boot. Utilizzare tmux per eseguire un job di tipo cron indica che il job deve essere trasformato in un servizio.

Un'ultima nota: vincola i dev server affinché l'agent punti a 127.0.0.1 e 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 le porte in ufw. Quando devi inoltrare mezza dozzina di porte, o quando un telefono e un laptop richiedono entrambi la stessa anteprima, installa una VPN WireGuard self-hosted sul VPS davanti ad essi: i dev server si vincolano a un'interfaccia privata e ufw continua a negare tutto traffico proveniente dall'interfaccia pubblica. Il firewall è utile solo se smetti di aprire porte su di esso.

Claude Code non è l'unica scelta: eseguire un agent AI per il coding su un VPS richiede di valutare anche Aider e Goose.

FAQ

Claude Code continua a girare dopo la disconnessione SSH?

Solo se è stato avviato all'interno di tmux. Un processo lanciato direttamente dalla shell SSH è un figlio di quella shell e termina quando il pty si chiude. All'interno di tmux la shell appartiene al server tmux detached, quindi l'agent continua il lavoro e tmux attach ti riporta nello stesso buffer di scorrimento. Usa tmux new -A -s <project> come primo comando dopo ogni login per risolvere il problema.

Devo installare la CLI con sudo npm install -g?

No. Un prefix globale di proprietà root causa errori EACCES durante le installazioni successive e file di proprietà root nella cache npm. Imposta il prefix di npm su ~/.npm-global (o usa un version manager come nvm), installa come utente non privilegiato agent ed esporta ~/.npm-global/bin su PATH da ~/.bashrc, sopra il guard interattivo. Se hai già eseguito sudo npm, ripara la cache con sudo chown -R $(id -u):$(id -g) ~/.npm.

L'agent forwarding di ssh -A è sicuro su un server che esegue un agent?

Concede molti più permessi di quelli necessari. Il forwarding espone il socket del tuo SSH agent locale a ogni processo eseguito come quell'utente; ogni processo sul server può quindi usare la tua chiave per firmare per qualsiasi host raggiungibile finché la sessione è attiva. Genera una chiave ed25519 sul server e registrala come deploy key specifica per il repository, con accesso in scrittura solo se l'agent deve effettuare il push.

Perché il mio build stampa solo Killed?

Un messaggio singolo senza stack trace indica il kernel OOM killer. Conferma il problema con sudo dmesg -T | grep -i -E 'out of memory|killed process'; da Node potresti invece vedere JavaScript heap out of memory. Applica le correzioni in ordine: aggiungi uno swapfile, limita il parallelismo di test e compilazione, aumenta NODE_OPTIONS=--max-old-space-size=... e infine aumenta le risorse del VPS. Attenzione: l'OOM killer può terminare il server tmux invece del build, chiudendo l'intera sessione.

tmux o un servizio systemd?

tmux è adatto per sessioni interattive da agganciare, monitorare e usare per digitare, che è esattamente ciò che è una sessione agent. I lavori pianificati senza supervisione devono essere gestiti tramite unit e timer di systemd, che includono nativamente logging, policy di riavvio e persistenza al boot. Se usi tmux per eseguire un job di tipo cron, il job deve essere un servizio.