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

Gemini CLI su VPS headless: guida a SSH e tmux

Configura Gemini CLI su un VPS senza desktop: Node aggiornato, installazione npm globale senza sudo, autenticazione con API key e tmux per sopravvivere alle disconnessioni SSH.

Cosa stai configurando

Un Gemini CLI sempre attivo su un server di tua proprietà, accessibile tramite SSH, che esegue attività lunghe dell’agente anche dopo la chiusura del laptop. L’installazione richiede tre comandi. La parte più complessa riguarda tutto ciò che presuppone un desktop: la CLI di Google tenta di aprire un browser per l’autenticazione, ma sul server non è disponibile. Per questo, gran parte della guida descrive il funzionamento headless, una versione recente di Node che la distribuzione non fornisce, un’installazione globale tramite npm che non richiede root, l’autenticazione senza browser con una chiave API che non viene salvata nella cronologia della shell e tmux, in modo che una sessione SSH interrotta non termini un’attività in esecuzione.

Gemini CLI è un programma Node open source (Apache-2.0) (@google/gemini-cli) che comunica con i modelli Gemini di Google e può leggere e scrivere file, eseguire comandi shell e utilizzare strumenti nella directory di lavoro. Su un VPS è un agente piccolo e sempre disponibile che può continuare a lavorare, quindi l’account con cui viene eseguito e le credenziali presenti sul server sono più importanti di qualsiasi singola impostazione descritta qui.

Prerequisiti e aspetti da considerare

  • Un VPS KVM Ubuntu 24.04 appena installato, con accesso root o sudo. Qualsiasi piano KVM è adatto; la CLI richiede poche risorse e, a riposo, usa poche centinaia di MB di RAM.
  • Node.js 20 o versione successiva. Questo è l'unico requisito minimo rigido; il pacchetto della distribuzione è precedente. Vedere la sezione successiva.
  • Accesso HTTPS in uscita (porta 443) alle API di Google. Non sono necessarie porte in ingresso; questo è un client, non un server, quindi non occorre aprire porte aggiuntive nel firewall.
  • Un metodo di autenticazione che non richieda un browser sul server: una chiave API Gemini ottenuta da Google AI Studio oppure un tunnel SSH verso un browser sul proprio computer. Il metodo con chiave API è quello più adatto per gli script e le esecuzioni non interattive.
  • Docker o Podman, solo se si desidera l'isolamento --sandbox. È facoltativo e viene descritto verso la fine.

L'aspetto che causa più problemi è il seguente: il flusso di accesso iniziale gemini è progettato per un desktop. Tenta di aprire un browser e, su un sistema headless, può non riuscire oppure fornire un link non utilizzabile. Scegliere il metodo di autenticazione prima di iniziare.

Node: il pacchetto della distribuzione è troppo vecchio

Ubuntu 24.04 include Node 18.19.1 nei propri repository, insieme a npm 9.2.0. Gemini CLI package.json dichiara engines: { node: ">=20" }. Per impostazione predefinita, npm non interrompe l'installazione quando rileva una versione incompatibile: procede comunque e stampa un avviso che indica la differenza:

npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE   package: '@google/gemini-cli@0.50.0',
npm WARN EBADENGINE   required: { node: '>=20' },
npm WARN EBADENGINE   current: { node: 'v18.19.1', npm: '9.2.0' }
npm WARN EBADENGINE }

Se si ignora l'avviso, la CLI viene eseguita su un runtime non supportato. Può quindi comportarsi in modo errato o terminare appena raggiunge un'API di Node 20+ che si aspetta di trovare. Inoltre, Node 18 ha raggiunto la fine del ciclo di vita ad aprile 2025, quindi non è una soluzione praticabile. Installare una versione LTS corrente prima di installare la CLI. Le due opzioni corrette sono NodeSource, un repository apt firmato disponibile a livello di sistema, oppure nvm, un gestore delle versioni per singolo utente. Sceglierne una.

NodeSource, se si vuole rendere Node disponibile a tutti gli utenti del server:

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

node --version deve restituire v20.x o una versione successiva. v24.x è l'attuale versione LTS. Consultare la pagina di NodeSource per lo script di configurazione corrente. Il valore setup_24.x nell'URL è quello da aggiornare quando viene pubblicata una nuova versione LTS.

nvm, se si preferisce mantenere Node nella home di un solo utente e non modificarlo mai con sudo:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
node --version

Il valore v0.40.1 nell'URL era quello corrente al momento della stesura di questo documento. Consultare il README di nvm per conoscere l'ultima release e sostituire la versione nell'URL prima di eseguirlo. nvm offre un vantaggio concreto in questo scenario: installa Node e i relativi pacchetti globali in ~/.nvm. Il problema dei permessi durante l'installazione globale descritto nella sezione successiva non si verifica quindi mai. Se si sceglie nvm, si può saltare il passaggio relativo al prefisso di npm.

Installare la CLI senza sudo npm -g

Il comando più immediato è sudo npm install -g @google/gemini-cli. Non usarlo. Un prefisso globale di proprietà di root causa errori di autorizzazione a ogni installazione successiva e lascia file di proprietà di root nella cache di npm, che possono creare problemi anche mesi dopo. Eseguendo un semplice npm install -g, senza sudo, su un'installazione di Node gestita dal sistema, si verifica l'altro errore:

npm error code EACCES
npm error syscall mkdir
npm error path /usr/lib/node_modules/@google
npm error errno -13
npm error Error: EACCES: permission denied, mkdir '/usr/lib/node_modules/@google'

npm sta tentando di scrivere in /usr/lib, dove il tuo utente non dispone delle autorizzazioni necessarie. La soluzione non è usare sudo, ma impostare il prefisso globale di npm nella directory home, in modo che le installazioni globali finiscano in una posizione di tua proprietà:

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

~/.bashrc, e non ~/.profile, è intenzionale: tra due sezioni da questo punto, eseguirai la CLI all'interno di tmux. tmux avvia una shell non di login che legge ~/.bashrc e ignora ~/.profile. Una riga PATH inserita nel file errato rende quindi gemini invisibile esattamente nel contesto in cui ti serve. L'output di gemini --version con un numero di versione è l'unico test necessario. Se invece ottieni gemini: command not found, l'esportazione PATH non è stata applicata; consulta le modalità di errore. Con nvm, ignora completamente le righe relative al prefisso: nvm installa già i pacchetti globali nella tua home.

Se in precedenza hai eseguito sudo npm e ora visualizzi Your cache folder contains root-owned files, correggi il problema una volta sola con sudo chown -R $(id -u):$(id -g) ~/.npm.

Il problema dell'autenticazione senza interfaccia grafica e come superarlo

Esegui gemini in modo interattivo la prima volta: il comando propone l'accesso con il tuo account Google. Su un desktop apre una scheda del browser. Su un VPS headless non è disponibile alcun browser, quindi il flusso stampa un URL localhost che dovresti aprire oppure termina con un errore simile a questo:

Failed to open browser. Please visit the following URL to authorize:
https://accounts.google.com/o/oauth2/v2/auth?...&redirect_uri=http://localhost:PORT

Il problema è redirect_uri=http://localhost:PORT. Anche se apri quell'URL sul laptop e autorizzi l'accesso, Google reindirizza a http://localhost:PORT, cioè a localhost sul server, su una porta che il laptop non può raggiungere. L'accesso non viene completato.

Esistono due soluzioni corrette.

La prima consiste nell'usare una chiave API ed è la scelta predefinita corretta per un server. Crea una chiave in Google AI Studio (aistudio.google.com) e passala alla CLI tramite una variabile d'ambiente: la CLI legge GEMINI_API_KEY e salta completamente il flusso del browser. Resta da evitare che la chiave finisca nella cronologia dei comandi o in file leggibili da altri. Non digitare export GEMINI_API_KEY=AIza... al prompt: il valore viene salvato in ~/.bash_history in chiaro. Non inserirlo neppure in un file leggibile da altri. Scrivilo in un file con permessi 600 che la shell carica all'avvio:

umask 077
printf 'export GEMINI_API_KEY=%s\n' 'AIzaSyYOUR_KEY_HERE' > ~/.gemini_env
chmod 600 ~/.gemini_env
echo '[ -f ~/.gemini_env ] && . ~/.gemini_env' >> ~/.bashrc
source ~/.bashrc

chmod 600 indica che solo il tuo utente può leggere il file. Verifica che la chiave sia stata caricata nell'ambiente con printenv GEMINI_API_KEY. Se il comando non stampa nulla, la CLI ripiega sul flusso del browser, che sul server non funziona. La CLI legge anche un file .env in ~/.gemini/, se preferisci questa struttura. Vale la stessa regola: chmod 600 ~/.gemini/.env.

La seconda soluzione mantiene l'accesso con l'account Google personale, incluso il relativo piano gratuito, inoltrando il callback OAuth al laptop tramite un tunnel. Il problema è che il server loopback della CLI si mette in ascolto su una porta casuale a ogni esecuzione. Non esiste quindi una porta stabile da inoltrare, a meno di fissarla prima con la variabile d'ambiente OAUTH_CALLBACK_PORT e inoltrare esattamente quella porta:

# from your laptop, forward the callback port into the SSH session:
ssh -L 8085:localhost:8085 user@your-server
# then, on the server, pin the callback to the same port and start the CLI:
export OAUTH_CALLBACK_PORT=8085
gemini

La CLI non può aprire un browser, quindi stampa l'URL di autenticazione. Aprilo nel browser del laptop e autorizza l'accesso. Quando Google reindirizza a http://localhost:8085/..., il forwarding SSH inoltra la richiesta al server loopback sul VPS e l'accesso viene completato. Se non fissi la porta, a ogni esecuzione viene scelta una nuova porta casuale, che nessun ssh -L configurato in anticipo può intercettare. La procedura funziona, ma richiede la presenza di un utente davanti a un browser. Non è quindi adatta agli script. Per i processi che devono restare in esecuzione, usa la chiave API.

Per usare Vertex AI o un progetto Google Cloud invece di AI Studio, imposta GOOGLE_API_KEY insieme a GOOGLE_GENAI_USE_VERTEXAI=true oppure GOOGLE_CLOUD_PROJECT per una licenza Code Assist. Applica le stesse regole per le variabili d'ambiente e usa lo stesso file con permessi 600.

Eseguilo all’interno di tmux per evitare che una sessione SSH interrotta termini il processo

Un processo gemini avviato direttamente dalla shell SSH è figlio di quella shell. Se perdi la connessione, chiudi il laptop, si interrompe il Wi-Fi o scade un timeout di inattività, sshd elimina il pseudo-terminale, la shell riceve SIGHUP e a sua volta chiude la connessione della CLI. Un’attività già impegnata da dieci minuti nella modifica dei file termina insieme alla sessione e, alla riconnessione, non c’è alcun processo da recuperare.

tmux risolve il problema gestendo la shell invece di essere gestito da sshd. È lo stesso modello usato per eseguire un agente di coding AI su un VPS remoto all’interno di tmux e qui funziona nello stesso modo:

sudo apt install -y tmux
tmux new -A -s gemini
# inside the session:
gemini
# detach with Ctrl-b then d — the task keeps running
# reconnect later from any machine:
tmux attach -t gemini

tmux new -A -s gemini si collega a una sessione denominata gemini se esiste e la crea in caso contrario. Per questo è il comando da eseguire subito dopo ogni accesso. La shell interna appartiene al server tmux scollegato, non alla sessione SSH, quindi l’interruzione della connessione lascia la CLI in esecuzione. Riconnettiti, collegati alla sessione e ritroverai lo stesso scrollback. Se finisci per eseguire più sessioni dell’agente sullo stesso server, una per sessione tmux, qui non possono comunicare tra loro, a differenza di Claude Code, dove una sessione può passare testo a un’altra sullo stesso VPS. Mantieni quindi indipendenti i singoli lavori Gemini oppure coordinali tramite file sul disco.

Per le esecuzioni non interattive e basate su script, Gemini CLI dispone di una modalità headless: gemini -p "summarise the failing tests in this repo" stampa una risposta e termina, mentre --output-format json produce un output leggibile dalle macchine da inoltrare tramite pipe. La modalità headless con una API key è esattamente ciò che serve all’interno di una sessione tmux che esegue un lungo job batch o avviata da una voce cron, con una precisazione: un job cron non carica alcun file di login. Assegna quindi alla riga crontab il suo GEMINI_API_KEY oppure fai in modo che il comando carichi ~/.gemini_env; in caso contrario la CLI ricade nel flusso tramite browser e termina con un errore.

Sandboxing e permessi su un server che esegue anche servizi di produzione

Un agente con accesso alla shell dispone di una shell. Gemini CLI può eseguire comandi e, per impostazione predefinita, chiede conferma prima di ogni operazione rischiosa. Tuttavia, si può usare --yolo (approvazione automatica di ogni chiamata agli strumenti); in questo caso l'agente può eliminare file, eseguire il push su git o accedere ai servizi interni con tutte le autorizzazioni dell'utente con cui viene eseguito. Su un server che esegue anche servizi di produzione, il raggio d'azione di un incidente è concreto, non ipotetico.

Tre controlli, in ordine di efficacia:

  • Eseguilo con un utente dedicato e senza privilegi. Non root e non un membro di sudo. Crea un utente agent con una propria home, installa Node e la CLI in quell'ambiente: un'istruzione interpretata in modo errato resterà confinata a quell'account. Questa è la decisione con il maggiore impatto sulla sicurezza.
  • Non conservare sul server le credenziali di produzione. Nessun ~/.aws/credentials di produzione, nessun .env copiato dall'ambiente di produzione e nessuna password del database con accesso in scrittura a risorse importanti. Fornisci invece una credenziale per staging o in sola lettura.
  • Usa la sandbox integrata. Con Docker o Podman installato, gemini --sandbox (oppure GEMINI_SANDBOX=docker) esegue le chiamate agli strumenti dell'agente all'interno di un container isolato dal filesystem e dalla rete dell'host. Non sostituisce l'utente senza privilegi, ma costituisce un secondo livello efficace quando lo stesso VPS esegue servizi reali.

Se esegui Gemini CLI insieme ad altri strumenti self-hosted, ad esempio un server MCP che espone strumenti all'agente sullo stesso VPS, considera ogni nuova funzionalità come una superficie aggiuntiva raggiungibile dall'agente e limita i token che gli fornisci a un'unica attività.

Quote, costi e percorso di autenticazione scelto

Il percorso di autenticazione determina il modo in cui vengono addebitati i costi. Un account Google personale, ovvero il percorso OAuth, usa il livello gratuito di Gemini Code Assist, con limiti effettivi al minuto e al giorno; se li superi, le richieste restituiscono un errore di limitazione della frequenza fino al ripristino della finestra. Una chiave API di AI Studio può appartenere al livello gratuito oppure a un progetto con fatturazione attiva. Una chiave associata alla fatturazione aumenta i limiti e addebita i token utilizzati. L'autenticazione tramite Vertex e tramite un progetto Cloud viene addebitata attraverso Google Cloud.

Due note pratiche. Un agente non presidiato inserito in un ciclo può consumare rapidamente la quota, quindi monitoralo le prime volte prima di affidarlo a un cron job. Se invece ti serve un modello sul server per motivi di privacy o per eseguire inferenza senza limiti di consumo, anziché usare i modelli ospitati da Google, si tratta di uno strumento diverso: self-hosting di un LLM open con Ollama su un VPS mantiene i pesi e i prompt sul tuo server, ma richiede di eseguire un modello molto più piccolo di Gemini.

Mantenerlo actualizado

Gemini CLI se actualiza con frecuencia. Como lo instalaste en un prefijo propiedad del usuario, las actualizaciones no requieren nunca sudo:

npm install -g @google/gemini-cli@latest
gemini --version

Hay varios canales de lanzamiento: @latest es el estable, @preview es la versión preliminar semanal y @nightly es la versión más reciente y menos probada; fija @latest en cualquier entorno del que dependas. Con nvm, los paquetes globales se almacenan nella versione attiva di Node, por lo que después de nvm use para cambiar de versión de Node puede ser necesario reinstalar la CLI. Consulta las notas de la versión en lugar de perseguir cada parche.

Modalità di errore, con le stringhe esatte

npm WARN EBADENGINE Unsupported engine ... required: { node: '>=20' }, quindi l'arresto della CLI durante l'esecuzione. Node è troppo vecchio: la distribuzione include la versione 18.19.1, che ha anche superato il termine del supporto. Installare Node 20+ da NodeSource o nvm e verificare con node --version. Se sono installate più versioni di Node, controllare che which node punti a quella nuova e non a /usr/bin/node.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. È stata eseguita un'installazione globale in un prefisso di proprietà di root. Non usare sudo. Impostare npm config set prefix ~/.npm-global, aggiungere ~/.npm-global/bin a PATH e reinstallare con il proprio utente. Se un precedente sudo npm ha creato file di cache di proprietà di root (Your cache folder contains root-owned files), eseguire sudo chown -R $(id -u):$(id -g) ~/.npm.

Failed to open browser, un accesso che resta bloccato oppure un redirect_uri=http://localhost:PORT non raggiungibile. Il flusso OAuth richiede un browser che il server non dispone e il callback localhost punta al server, non al laptop. Usare il percorso con API key (GEMINI_API_KEY) oppure fissare OAUTH_CALLBACK_PORT, inoltrarlo tramite SSH con ssh -L e aprire l'URL localmente.

Il processo è scomparso quando la connessione SSH è caduta. Hai eseguito gemini direttamente dalla shell SSH, quindi il processo era figlio di quella shell ed è terminato insieme al pty alla disconnessione. Non c'è nulla da recuperare. Avviare ogni sessione con tmux new -A -s gemini ed eseguire la CLI al suo interno.

L'autenticazione continua a non funzionare anche dopo aver impostato la chiave, la CLI torna al selettore di autenticazione oppure una richiesta restituisce API key not valid con HTTP 400. La chiave non è presente nell'ambiente visibile alla CLI. Verificare con printenv GEMINI_API_KEY. Se il risultato è vuoto, il file ~/.gemini_env non è mai stato caricato. Controllare che la riga si trovi in ~/.bashrc, che viene letto dalle shell interattive, incluso tmux, ma non da cron e dalle altre shell non interattive. Anche uno spazio o una virgoletta aggiuntivi all'interno del valore della chiave producono API key not valid.

429 / RESOURCE_EXHAUSTED / un messaggio di superamento del limite di frequenza. È stata raggiunta la quota del livello utilizzato dall'autenticazione. Attendere il ripristino della finestra, ridurre la velocità dell'agent oppure passare a una API key a pagamento. Un agent bloccato in un ciclo di retry continua a raggiungere questo limite: arrestarlo e verificare cosa sta eseguendo.

FAQ

Come autentico Gemini CLI su un server headless?

Usa una API key, non il login tramite browser. Crea una chiave in Google AI Studio, inseriscila in un file con modalità 600 che la shell carica (export GEMINI_API_KEY=...) e la CLI salta completamente il flusso OAuth tramite browser. Se vuoi specificamente il piano gratuito per account personale, fissa la porta di loopback con OAUTH_CALLBACK_PORT=8085, inoltrala al laptop con ssh -L 8085:localhost:8085 user@server e apri localmente l'URL visualizzato, ma devi essere presente davanti a un browser, quindi questa soluzione non è adatta agli script.

Perché l'installazione globale di npm richiede sudo e come posso evitarlo?

Perché il prefisso globale predefinito di npm è /usr/lib/node_modules, su cui il tuo utente non ha permessi di scrittura; per questo un semplice npm install -g fallisce con EACCES. La soluzione errata è sudo npm -g, che lascia file di proprietà di root e causa errori nelle installazioni successive. La soluzione corretta consiste nell'impostare il prefisso nella tua home (npm config set prefix ~/.npm-global) e aggiungerlo a PATH tramite bin, oppure usare nvm, che installa automaticamente i pacchetti globali nella tua home.

Come posso mantenere Gemini CLI in esecuzione dopo la disconnessione?

Eseguila dentro tmux. Un processo avviato dalla shell SSH termina quando la connessione cade, perché è figlio di quella shell; tmux esegue la shell sotto un server scollegato che sopravvive alla disconnessione. Usa tmux new -A -s gemini, esegui gemini al suo interno, scollegati con Ctrl-b d e riconnettiti in seguito con tmux attach -t gemini.

È sicuro eseguire Gemini CLI su un server di produzione?

Solo con cautela, perché un agente con accesso alla shell può eseguire qualsiasi operazione consentita all'utente con cui è in esecuzione. Eseguilo con un utente dedicato senza privilegi e senza sudo, non conservare le credenziali di produzione sul computer, evita l'approvazione automatica --yolo e usa --sandbox (Docker o Podman) per isolare le chiamate agli strumenti dal sistema host. L'account con cui viene eseguito è più importante di qualsiasi singolo flag impostato.

Devo aprire porte del firewall per Gemini CLI?

No. È un client che effettua chiamate HTTPS in uscita alle API di Google, quindi richiede la porta 443 in uscita ma non porte in ingresso. Se usi il tunnel OAuth, la porta di callback fissata (ad esempio 8085) risiede su localhost ed è raggiunta tramite il port forwarding SSH, non tramite una porta in ingresso aperta. Mantieni chiuso il traffico in ingresso.

#gemini-cli#node#tmux#headless#ai#vps