SSD Nodes Learn Hosting plans →
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-29

How to Run Gemini CLI for Headless VPS Work

Run Gemini CLI for headless VPS work with current Node, no-sudo global npm install, API-key auth without browser login, and tmux for SSH drops.

Wetin you dey build

Na an always-on Gemini CLI for server wey you own, wey you fit reach through SSH, and wey dey run long agent tasks even after you close your laptop. The installation na three commands. The work dey come from things wey assume say desktop dey available: Google's CLI want open browser make you log in, but your server no get browser. So most of this guide na the headless path, current Node wey distro no go provide, global npm install wey no need root, browserless auth with API key wey you keep away from shell history, and tmux so dropped SSH session no go stop task wey dey run.

Gemini CLI na open-source (Apache-2.0) Node program (@google/gemini-cli) wey dey talk to Google's Gemini models and fit read and write files, run shell commands, and use tools for the working directory. For VPS, e be small, always-available agent wey you fit leave to work. Na why the account wey e run as, and the credentials wey dey for the server, matter pass any single setting for here.

Prerequisites and the real things wey fit cause problem

  • Fresh Ubuntu 24.04 KVM VPS wey get root or sudo. Any KVM plan go work; the CLI itself light, e dey use few hundred MB RAM when e dey idle.
  • Node.js 20 or newer. Na this one get strict version floor, and distro package dey below am. See next section.
  • Outbound HTTPS (port 443) go Google's APIs. No inbound port dey needed; na client be this, no be server, so you no need open firewall hole for am.
  • One way to authenticate wey no need browser for server: either Gemini API key from Google AI Studio, or SSH tunnel back to browser for your own machine. API-key method na the one wey fit scale for scripts and unattended runs.
  • Docker or Podman, only if you want the --sandbox isolation. E optional, and we go cover am near the end.

The problem wey dey catch everybody be say: the friendly gemini first-run login flow na desktop work. E dey try open browser and, for headless box, e fit fail or give you link wey no work. Decide the auth method before you start.

Node: distro package too old

Ubuntu 24.04 dey ship Node 18.19.1 for im own repositories, together with npm 9.2.0. Gemini CLI's package.json dey declare engines: { node: ">=20" }, and npm no dey stop mismatch by default. E go install am anyway and print warning wey show the difference:

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 }

If you ignore that warning, the CLI go run on runtime wey no support am. E fit misbehave or crash once e reach Node 20+ API wey e expect to dey available. Node 18 still reach end-of-life for April 2025, so either way e no be good option. Install current LTS before you install the CLI. The two clean options na NodeSource, wey be system-wide signed apt repo, or nvm, wey be per-user version manager. Pick one.

NodeSource, if you want Node make e dey available to every user for the machine:

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 suppose print v20.x or higher. v24.x na the current active LTS. Check NodeSource page for the current setup script. The setup_24.x inside the URL na the line to increase when newer LTS release.

nvm, if you prefer keep Node inside one user's home and never use sudo touch am:

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

The v0.40.1 for that URL na the one wey dey current when dem write this. Check nvm's README for the latest release and replace the version before you run am. nvm get one real advantage for this work: e dey install Node and im global packages under ~/.nvm, so the global-install permission problem for the next section no go happen at all. If you choose nvm, you fit skip the npm-prefix step.

Install CLI without sudo npm -g

The command wey fit tempt person na sudo npm install -g @google/gemini-cli. No run am. Global prefix wey root own go cause permission errors for every install wey follow, and e go leave root-owned files for your npm cache wey fit cause problem months later. Run normal (without sudo) npm install -g against system Node, and you go see the other error:

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'

This one mean say npm dey try write inside /usr/lib, but your user no get permission. The fix no be sudo. Set npm global prefix to your home directory, so global installs go enter place wey you own:

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, no be ~/.profile, na deliberate choice. tmux, wey you go run the CLI inside two sections from now, starts non-login shell wey dey read ~/.bashrc and skip ~/.profile. So if you put PATH line for wrong file, gemini go remain invisible exactly where you need am. If gemini --version print version number, the test don pass. But if you see gemini: command not found instead, your PATH export no take effect; check the failure modes. For nvm, skip the prefix lines completely: e already installs globals under your home.

If you run sudo npm before and you now see Your cache folder contains root-owned files, repair am once with sudo chown -R $(id -u):$(id -g) ~/.npm.

Problem wey happen for headless auth, and how to pass am

Run gemini interactively the first time and e go offer make you log in with your Google account. For desktop, e go open browser tab. For headless VPS, browser no dey, so the flow either go print localhost URL wey e expect you to open, or e go fail directly with something like:

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

The trap na redirect_uri=http://localhost:PORT. Even if you open that URL for your laptop and approve am, Google go redirect to http://localhost:PORT, localhost for the server, na port wey nothing for your laptop fit reach. The login no go complete.

Two proper ways dey to pass this problem.

The first one na API key, and na the correct default for server. Create key for Google AI Studio (aistudio.google.com), then give am to the CLI as environment variable; e dey read GEMINI_API_KEY and skip the browser flow completely. Now, for the part wey concern "keep am out of history and files wey everybody fit read". No type export GEMINI_API_KEY=AIza... for the prompt, because e go enter ~/.bash_history as cleartext. Also, no put am for file wey other users fit read. Write am to file with mode-600 wey the shell go source when e start:

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 mean say na only your user fit read the file. Confirm say the key reach the environment with printenv GEMINI_API_KEY; if that command print nothing, the CLI go fall back to browser flow and fail. E also dey read .env file for ~/.gemini/ if you prefer that layout. The same rule apply, so chmod 600 ~/.gemini/.env.

The second way keep the personal-Google-account login, including the free tier, by tunnelling the OAuth callback back to your laptop. The problem be say the CLI loopback server dey bind to a random port for every run. So, nothing stable dey to forward unless you first pin am with the OAUTH_CALLBACK_PORT environment variable, then forward exactly that port:

# 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

The CLI no fit open browser, so e go print the auth URL. Open am for your laptop browser and approve am. When Google redirect to http://localhost:8085/..., the SSH forward go carry the request to the loopback server for the VPS, and the login go complete. If you no pin the port, e go use fresh random port for every run. Any ssh -L wey you set up before time no go catch am. E work, but you need dey near browser, so e no good for scripts. For anything wey you leave running, use the API key.

For Vertex AI or Google Cloud project instead of AI Studio, set GOOGLE_API_KEY together with GOOGLE_GENAI_USE_VERTEXAI=true, or use GOOGLE_CLOUD_PROJECT for a Code Assist licence. Follow the same environment-variable rule and use the same mode-600 file.

Run am inside tmux so dropped SSH session no go kill am

A gemini process wey you launch direct from your SSH shell na child of that shell. If connection drop, laptop close, Wi-Fi cut, or idle timeout happen, sshd go tear down the pseudo-terminal, shell go receive SIGHUP, then e go hang up the CLI too. Task wey don spend ten minutes editing files go die with am, and when you reconnect, no process dey to recover.

tmux solve this by owning the shell instead of sshd owning am. Na the same pattern as running AI coding agent for remote VPS inside tmux, and e work the same way here:

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 go attach to session wey dem name gemini if e dey exist, and create am if e no dey. So na the one command to run immediately after every login. The shell inside belong to detached tmux server, no be your SSH session, so if connection drop, the CLI go continue working. Reconnect, attach, and you go return to the same scrollback. If you later run several agent sessions for one box, use one tmux session for each one. Dem no get any way to talk to each other here, unlike Claude Code, where one session fit hand text to another for the same VPS, so make each Gemini job independent or coordinate dem through files for disk.

For non-interactive, scripted runs, Gemini CLI get headless mode: gemini -p "summarise the failing tests in this repo" go print answer and exit, while --output-format json go provide machine-readable output wey you fit pipe go another place. Headless mode with API key na exactly wetin you need inside tmux session wey dey run long batch job, or when cron entry start am. But one thing dey: cron job no source any of your login files. So give the crontab line its own GEMINI_API_KEY, or make the command source ~/.gemini_env. Otherwise, the CLI go fall back to browser flow and fail.

Sandboxing and permissions on one box wey still dey run production

Agent wey get shell access na shell. Gemini CLI fit run commands, and by default e dey ask before each risky one. But people dey use --yolo (auto-approve every tool call), then e fit delete files, push to git, or reach internal services with the full authority of the user wey run am. For box wey still dey run production, this na real blast radius, no be hypothetical matter.

Three controls, from the one wey give you the most protection:

  • Run am as dedicated, unprivileged user. No be root, and no be member of sudo. Create agent user with its own home, install Node and the CLI there, and if instruction get misread, the effect go stay inside that account. Na this single decision get the highest value.
  • Keep production credentials off the box. No prod ~/.aws/credentials, no .env copied from production, and no database password wey get write access to anything important. Give am staging credential or read-only credential.
  • Use the built-in sandbox. If Docker or Podman dey installed, gemini --sandbox (or GEMINI_SANDBOX=docker) runs the agent tool calls inside container wey isolate am from host filesystem and network. E no replace the unprivileged user, but e add strong second layer when the same VPS dey do real work.

If you dey run Gemini CLI beside other self-hosted tools, for example an MCP server wey expose tools to the agent for the same VPS, treat every extra capability as more surface wey agent fit reach. Limit the tokens wey you give am to exactly one job.

Quota, cost, and the auth path wey you choose

The auth path na im dey decide how dem go bill you. Personal Google account (the OAuth path) dey use free Gemini Code Assist tier, but e get real per-minute and per-day limits. If you pass dem, requests go return rate-limit error until the window reset. API key from AI Studio fit dey free-tier or billed, depending on the project. Billed key go raise the limits and charge per token. Vertex and Cloud-project auth dey bill through Google Cloud.

Two practical notes. Unattended agent wey dey run for loop fit finish quota quickly, so monitor am the first few times before you trust am with cron job. And if the reason you want server-side model na privacy or unmetered inference, instead of Google's hosted models, that one na different tool. self-hosting open LLM with Ollama on VPS go keep the weights and prompts for your own box, but you go need run model wey much smaller than Gemini.

E dey updated

Gemini CLI dey release often. Since you install am inside prefix wey user own, updates no ever need sudo:

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

Release channels dey: @latest na stable, @preview na weekly preview, @nightly na bleeding edge. Pin anything wey you rely on to @latest. For nvm, global packages dey under the active Node version. So after nvm use to switch Node, you fit need install the CLI again. Read the release notes instead of chasing every patch.

Failure modes, with the exact strings

npm WARN EBADENGINE Unsupported engine ... required: { node: '>=20' }, then the CLI crashing at runtime. Node too old, and the distro own version na 18.19.1, wey don pass end-of-life too. Install Node 20+ from NodeSource or nvm. Confirm am with node --version. If you install several Nodes, check say which node dey point to the new one, no be /usr/bin/node.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. You install globally inside prefix wey root own. No use sudo. Set npm config set prefix ~/.npm-global, put ~/.npm-global/bin for PATH, then reinstall as your normal user. If earlier sudo npm leave cache files wey root own (Your cache folder contains root-owned files), run sudo chown -R $(id -u):$(id -g) ~/.npm.

Failed to open browser, login wey dey hang, or redirect_uri=http://localhost:PORT wey you no fit reach. The OAuth flow need browser wey the server no get, and e localhost callback point to the server, no be your laptop. Use the API-key path (GEMINI_API_KEY), or pin OAUTH_CALLBACK_PORT. Forward am over SSH with ssh -L, then open the URL locally.

The process vanish when SSH disconnect. You run gemini directly from the SSH shell, so na child of that shell e be, and e die with the pty when connection break. Nothing dey to recover. Start every session with tmux new -A -s gemini and run the CLI inside am.

Auth still fail even after you set the key, the CLI return to its auth picker, or request return API key not valid with HTTP 400. The key no dey inside the environment wey the CLI see. Confirm with printenv GEMINI_API_KEY. If e empty, your ~/.gemini_env never source. Check say the line dey inside ~/.bashrc. Interactive shells, including tmux, read am, but cron and other non-interactive shells no dey read am. Extra space or quote inside the key value fit also produce API key not valid.

429 / RESOURCE_EXHAUSTED / rate-limit message. You don reach the quota for the tier wey your auth dey use. Wait make the window reset, slow down the agent, or move to billed API key. If agent dey stuck for retry loop, stop am and check wetin e dey do.

FAQ

How I go authenticate Gemini CLI for headless server?

Use API key, no be browser login. Create the key for Google AI Studio, put am inside file wey permission na 600 and your shell dey source (export GEMINI_API_KEY=...), then the CLI go skip OAuth browser flow completely. If you specifically want the personal-account free tier, pin the loopback port with OAUTH_CALLBACK_PORT=8085, forward am back to your laptop with ssh -L 8085:localhost:8085 user@server, and open the URL wey e print locally. But you must dey present for browser, so e no good for scripts.

Why npm global install dey ask for sudo, and how I fit avoid am?

Because npm default global prefix na /usr/lib/node_modules, and your user no fit write there, so plain npm install -g go fail with EACCES. The wrong fix na sudo npm -g, because e go leave files wey root own and dem go spoil later installs. The correct fix na to point the prefix to your home (npm config set prefix ~/.npm-global) and add the prefix bin to PATH, or use nvm. nvm dey install global packages under your home automatically.

How I fit keep Gemini CLI running after I disconnect?

Run am inside tmux. Process wey start from your SSH shell go die when connection drop because na child of that shell e be. tmux dey run the shell under detached server wey go survive the disconnect. Use tmux new -A -s gemini, run gemini inside, detach with Ctrl-b d, then reattach later with tmux attach -t gemini.

E safe to run Gemini CLI for production box?

Only if you take care, because agent wey get shell access fit do anything the user wey run am fit do. Run am as dedicated unprivileged user with no sudo, keep production credentials away from the machine, avoid --yolo auto-approval, and use --sandbox (Docker or Podman) to isolate tool calls from the host. The account wey run am matter pass any single flag wey you set.

I need open any firewall ports for Gemini CLI?

No. Na client wey dey make outbound HTTPS calls to Google's APIs, so e need outbound port 443 but e no need inbound ports. If you use the OAuth tunnel, the pinned callback port (for example 8085) dey on localhost and your SSH forward dey reach am; e no be open inbound port. Keep inbound traffic locked down.

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