SSD Nodes Learn
指南 Matt Connor作者: Matt Connor · 更新于 2026-07-19

在无头 VPS 上运行 Gemini CLI

在无头 VPS 上运行 Google 的 Gemini CLI:装好当前版 Node、免 sudo 全局安装、用 API 密钥完成无浏览器认证,并用 tmux 让长任务在 SSH 掉线后仍继续运行。

您将搭建的内容

在一台您自己拥有的服务器上,运行一个常驻的 Gemini CLI,通过 SSH 连接,执行那些在您合上笔记本后仍能继续工作的长时间智能体(agent)任务。安装只需三条命令。真正费工夫的是所有假设您有桌面环境的部分:Google 的 CLI 想打开浏览器来让您登录,而您的服务器没有浏览器。因此本指南的大部分内容都是无头(headless)方案:安装发行版不会提供的当前版本 Node、一次不需要 root 权限的全局 npm 安装、用一个不留在 shell 历史里的 API 密钥完成无浏览器认证,以及用 tmux 让掉线的 SSH 会话不会连带杀掉正在运行的任务。

Gemini CLI 是一个开源(Apache-2.0)的 Node 程序(@google/gemini-cli),它与 Google 的 Gemini 模型对话,能读写文件、运行 shell 命令,并在工作目录里驱动各种工具。在 VPS 上,它是一个小巧、随时可用、可以让它自己持续工作的智能体,正因如此,运行它所用的账户、以及留在机器上的凭据,比这里任何单项设置都更重要。

前置条件与几个需要老实说明的坑

  • 一台全新的 Ubuntu 24.04 KVM VPS,拥有 root 或 sudo 权限。任何 KVM 套餐都可以;CLI 本身很轻,空闲时占用几百 MB 内存。
  • Node.js 20 或更新版本。这是唯一一条硬性的版本下限,而发行版自带的软件包低于它,详见下一节。
  • 到 Google API 的出站 HTTPS(443 端口)。不需要任何入站端口;它是客户端,不是服务器,因此您不必为它在防火墙上开洞。
  • 一种不需要服务器上有浏览器的认证方式:要么用一个来自 Google AI Studio 的 Gemini API 密钥,要么用一条通回您自己机器上浏览器的 SSH 隧道。API 密钥这条路才能扩展到脚本和无人值守的运行。
  • Docker 或 Podman,仅当您想要 --sandbox 隔离时才需要。可选,本文末尾会讲。

人人都会踩的坑是:友好的 gemini 首次运行登录流程是为桌面环境设计的。它会尝试打开浏览器,而在无头机器上,要么直接失败,要么给您一个打不开的链接。请在开始之前就决定好认证方案。

Node:发行版自带的软件包太旧

Ubuntu 24.04 在自己的仓库里提供 Node 18.19.1,搭配 npm 9.2.0。Gemini CLI 的 package.json 声明了 engines: { node: ">=20" },而 npm 默认不会因版本不符而硬性中止,它照样安装,并打印一条点明差距的警告:

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 }

无视这条警告继续下去,CLI 就跑在一个不受支持的运行时上,一旦它用到某个它以为存在的 Node 20+ API,就会行为异常或直接崩溃。Node 18 也已在 2025 年 4 月停止维护(end-of-life),所以无论如何都是死路一条。请在安装 CLI 之前先装好一个当前的 LTS 版本。两条干净的路线是 NodeSource(一个系统级、已签名的 apt 仓库)或 nvm(一个按用户管理的版本管理器)。二选一。

用 NodeSource,如果您想让机器上每个用户都能用到 Node:

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 必须打印 v20.x 或更高,v24.x 是当前活跃的 LTS。请到 NodeSource 页面查看当前的安装脚本;URL 里的 setup_24.x 就是当有更新的 LTS 发布时需要改动的地方。

用 nvm,如果您更愿意把 Node 保留在某一个用户的家目录里、永远不用 sudo 去碰它:

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

那条 URL 里的 v0.40.1 是撰写本文时的当前版本;请到 nvm 的 README 查看最新发布版,运行前先把版本号换掉。nvm 对这项工作有一个实实在在的好处:它把 Node 及其全局包都装在 ~/.nvm 下,因此下一节讲的全局安装权限问题根本不会发生。如果您走 nvm 这条路,可以跳过设置 npm prefix 那一步。

不用 sudo npm -g 来安装 CLI

诱人的命令是 sudo npm install -g @google/gemini-cli。别这么做。一个 root 所有的全局 prefix 会让之后每一次安装都冒出权限错误,还会在您的 npm 缓存里留下 root 所有的文件,几个月后来咬您一口。而对着一个系统 Node 运行普通的(不带 sudo 的)npm install -g,您会遇到另一种失败:

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 在尝试往 /usr/lib 写入,而您的用户没有权限。解决办法不是 sudo,而是把 npm 的全局 prefix 指向您的家目录,让全局安装落在一个您拥有的地方:

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 而不是 ~/.profile 是有意为之:tmux(两节之后您就会在它里面运行 CLI)启动的是一个读取 ~/.bashrc、跳过 ~/.profile 的非登录 shell,因此把 PATH 那行放错文件,会让 gemini 恰恰在您需要它的地方消失不见。gemini --version 能打印出版本号,就是全部的验证。如果您反而得到 gemini: command not found,说明您的 PATH 导出没生效,请看后面的故障排查一节。在 nvm 上,完全跳过设置 prefix 那几行:它本来就把全局包装在您的家目录下。

如果您在更早某个时刻运行过 sudo npm,现在看到 Your cache folder contains root-owned files,用 sudo chown -R $(id -u):$(id -g) ~/.npm 修复一次即可。

无头认证难题,以及如何绕过它

第一次交互式运行 gemini,它会提出用您的 Google 账户帮您登录。在桌面上这会打开一个浏览器标签页。在无头 VPS 上没有浏览器,于是这个流程要么打印一个它指望您去打开的 localhost URL,要么干脆失败,报出类似这样的内容:

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

陷阱在于 redirect_uri=http://localhost:PORT。即使您在笔记本上打开那个 URL 并批准了,Google 也会重定向到 http://localhost:PORT,这是服务器上的 localhost、一个您笔记本上什么都够不着的端口。登录永远无法完成。

有两条老实的路可以走通。

第一条是用 API 密钥,对服务器来说它是正确的默认选择。在 Google AI Studio(aistudio.google.com)创建一个密钥,作为环境变量交给 CLI;它会读取 GEMINI_API_KEY 并彻底跳过浏览器流程。接下来是"别让它进历史记录、别放进人人可读的文件"这部分。不要在提示符下敲 export GEMINI_API_KEY=AIza...,那会以明文落进 ~/.bash_history;也不要把它放进别人能读的文件里。把它写进一个 shell 启动时会 source 的、权限为 600 的文件:

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 意味着只有您的用户能读这个文件。用 printenv GEMINI_API_KEY 确认密钥进入了环境;如果它什么都没打印,CLI 就会退回浏览器流程并失败。如果您更喜欢那种布局,它也会读取 ~/.gemini/ 里的一个 .env 文件,规则相同,所以要 chmod 600 ~/.gemini/.env

第二条路保留个人 Google 账户登录(及其免费额度),做法是把 OAuth 回调隧道通回您的笔记本。麻烦在于 CLI 的回环服务器每次运行都绑定一个随机端口,除非您先用 OAUTH_CALLBACK_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

CLI 无法打开浏览器,于是它打印出认证 URL;在您笔记本的浏览器里打开它、批准,当 Google 重定向到 http://localhost:8085/... 时,SSH 转发会把它送到 VPS 上的回环服务器,登录随即完成。若不固定端口,它每次运行都落在一个全新的随机端口上,任何事先设好的 ssh -L 都抓不住。这条路行得通,但需要您坐在浏览器前,所以不适合脚本。任何要让它自己跑着的场景,都用 API 密钥。

若您用的是 Vertex AI 或某个 Google Cloud 项目而不是 AI Studio,请设置 GOOGLE_API_KEY 并搭配 GOOGLE_GENAI_USE_VERTEXAI=true,或者为 Code Assist 许可设置 GOOGLE_CLOUD_PROJECT,同样的环境变量纪律、同样的权限 600 文件。

在 tmux 里运行它,让掉线的 SSH 会话不会杀掉它

您直接从 SSH shell 启动的 gemini 进程是那个 shell 的子进程。一旦连接断开(合上笔记本、Wi-Fi 掉线、空闲超时),sshd 就拆掉伪终端,shell 收到 SIGHUP,接着它又挂断了 CLI。一个已经编辑文件编辑了十分钟的任务随之死掉,重连后也没有进程可以恢复。

tmux 通过让它自己持有 shell、而不是让 sshd 持有来解决这个问题。这与在远程 VPS 上于 tmux 内运行 AI 编码智能体是同一套模式,在这里也完全一样奏效:

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 会在名为 gemini 的会话存在时附加上去、不存在时创建它,因此它是每次登录后要运行的唯一一条命令。里面的 shell 属于那个已分离的 tmux 服务器,而不属于您的 SSH 会话,所以连接断开也不影响 CLI 继续工作。重新连接、附加上去,您就回到了同一片滚动历史里。

对于非交互式的脚本化运行,Gemini CLI 有一个无头模式:gemini -p "summarise the failing tests in this repo" 打印一个答案然后退出,而 --output-format json 给出可管道传给别处的机器可读输出。带 API 密钥的无头模式,正是您想在 tmux 会话里运行长批处理任务、或从 cron 条目触发时所需要的,只有一个要注意的地方:cron 作业不会 source 您的任何登录文件,所以要么给 crontab 那一行单独提供 GEMINI_API_KEY(或让命令 source ~/.gemini_env),否则 CLI 会退回浏览器流程并失败。

在一台同时跑生产的机器上做沙箱与权限控制

一个拥有 shell 访问权的智能体本身就是一个 shell。Gemini CLI 能运行命令,默认情况下每遇到一个有风险的命令都会先询问,但人们会去用 --yolo(自动批准每一次工具调用),于是它就能以其运行用户的全部权限删除文件、推送到 git,或访问内部服务。在一台同时跑生产的机器上,这是真实的杀伤半径,而非假想。

三种控制手段,按其价值高低排序:

  • 以一个专用的、无特权的用户身份运行它。 不是 root,也不是 sudo 组成员。创建一个有自己家目录的 agent 用户,把 Node 和 CLI 装在那里,一条被误读的指令就被限制在那个账户里。这是单项价值最高的决定。
  • 让生产凭据远离这台机器。 没有生产的 ~/.aws/credentials,没有从生产复制下来的 .env,没有能对任何要紧东西写入的数据库密码。给它一个预发布环境或只读的凭据。
  • 使用内置沙箱。 装好 Docker 或 Podman 后,gemini --sandbox(或 GEMINI_SANDBOX=docker)会把智能体的工具调用放进一个与宿主文件系统和网络隔离的容器里运行。它不能替代无特权用户,但当同一台 VPS 还在干真活时,它是一层有力的第二道防线。

如果您在其他自托管工具旁边运行 Gemini CLI,比如一个在同一台 VPS 上向智能体暴露工具的 MCP 服务器,请把每一项新增能力都当作智能体能触及的更多攻击面,并把交给它的令牌(token)严格限定在恰好一件事上。

配额、成本,以及您选了哪条认证路径

认证路径决定了您如何被计费。个人 Google 账户(OAuth 路径)使用免费的 Gemini Code Assist 层级,有真实的每分钟和每天限额;超过之后请求会返回限流错误,直到时间窗重置。来自 AI Studio 的 API 密钥可能是免费层级也可能是计费的,取决于项目,一个计费密钥会提高限额并按 token 收费。Vertex 和 Cloud 项目认证通过 Google Cloud 计费。

两条实用提示。一个陷入循环的无人值守智能体能很快烧掉配额,所以头几次先盯着它,再放心把它交给 cron 作业。还有,如果您想要服务器端模型的理由是隐私或不计量的推理、而非 Google 的托管模型,那是另一种工具,用 Ollama 在 VPS 上自托管一个开源 LLM把权重和提示词都留在您自己的机器上,代价是运行一个比 Gemini 小得多的模型。

保持更新

Gemini CLI 发布很频繁。因为您把它装进了一个用户所有的 prefix,更新永远不需要 sudo:

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

有几个发布通道:@latest 是稳定版,@preview 是每周预览版,@nightly 是最前沿版本,任何您依赖的东西都固定到 @latest。在 nvm 上,全局包存在于当前活跃的 Node 版本之下,所以用 nvm use 切换 Node 之后,您可能需要重新安装 CLI。请读发布说明,而不是追着每个补丁跑。

故障模式,附确切的报错字符串

npm WARN EBADENGINE Unsupported engine ... required: { node: '>=20' },接着 CLI 在运行时崩溃。 Node 太旧,是发行版的 18.19.1,它也已经停止维护。从 NodeSource 或 nvm 安装 Node 20+,用 node --version 确认,如果您装了好几个 Node,检查 which node 指向的是新的那个而不是 /usr/bin/node

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...' 一次装进 root 所有 prefix 的全局安装。别用 sudo,设置 npm config set prefix ~/.npm-global,把 ~/.npm-global/bin 放进 PATH,然后以您的普通用户身份重新安装。如果更早的 sudo npm 留下了 root 所有的缓存文件(Your cache folder contains root-owned files),运行 sudo chown -R $(id -u):$(id -g) ~/.npm

Failed to open browser、一次卡住的登录,或一个您够不着的 redirect_uri=http://localhost:PORT OAuth 流程想要一个服务器没有的浏览器,而它的 localhost 回调指向的是服务器、不是您的笔记本。改用 API 密钥路径(GEMINI_API_KEY),或者固定 OAUTH_CALLBACK_PORT、用 ssh -L 通过 SSH 转发它、并在本地打开那个 URL。

SSH 掉线时进程消失了。 您直接从 SSH shell 运行了 gemini,所以它是那个 shell 的子进程,随伪终端一起在断连时死掉。没有东西可恢复。每次会话都用 tmux new -A -s gemini 开始,并在它里面运行 CLI。

密钥已设置但认证仍然失败,CLI 退回它的认证选择器,或一个请求返回 API key not valid 及 HTTP 400 密钥不在 CLI 看到的环境里。用 printenv GEMINI_API_KEY 确认;如果它是空的,说明您的 ~/.gemini_env 从没被 source 过,检查那一行是否在 ~/.bashrc 里,交互式 shell(包括 tmux)会读它,而 cron 和其他非交互式 shell 不会。密钥值里夹了一个多余的空格或引号,也会产生 API key not valid

429 / RESOURCE_EXHAUSTED / 一条限流消息。 您触及了所用认证层级的配额。等时间窗重置、把智能体放慢,或换成一个计费的 API 密钥。一个卡在重试循环里的智能体会反复撞上它,停掉它,看看它在干什么。

FAQ

在无头服务器上如何给 Gemini CLI 做认证?

用 API 密钥,而不是浏览器登录。在 Google AI Studio 创建一个密钥,放进一个您的 shell 会 source 的权限 600 文件里(export GEMINI_API_KEY=...),CLI 就会彻底跳过 OAuth 浏览器流程。如果您特别想要个人账户的免费层级,用 OAUTH_CALLBACK_PORT=8085 固定回环端口,用 ssh -L 8085:localhost:8085 user@server 把它转发回您的笔记本,并在本地打开打印出来的 URL,但这需要您守在浏览器前,所以不适合脚本。

npm 全局安装为什么要 sudo,我该如何避免?

因为 npm 默认的全局 prefix 是 /usr/lib/node_modules,您的用户无法写入,所以普通的 npm install -g 会以 EACCES 失败。错误的解法是 sudo npm -g,它会留下 root 所有的文件、破坏之后的安装。正确的解法是把 prefix 指向您的家目录(npm config set prefix ~/.npm-global)并把它的 bin 加进 PATH,或者用 nvm,它会自动把全局包装在您的家目录下。

断开连接后如何让 Gemini CLI 继续运行?

在 tmux 里运行它。从您的 SSH shell 启动的进程会在连接断开时死掉,因为它是那个 shell 的子进程;tmux 把 shell 跑在一个能挺过断连的分离服务器之下。用 tmux new -A -s gemini,在里面运行 gemini,用 Ctrl-b d 分离,之后用 tmux attach -t gemini 重新附加。

在生产机器上运行 Gemini CLI 安全吗?

只有小心行事才安全,因为一个拥有 shell 访问权的智能体能做它运行用户所能做的任何事。以一个没有 sudo 的专用无特权用户身份运行它,让生产凭据远离这台机器,避免 --yolo 自动批准,并用 --sandbox(Docker 或 Podman)把工具调用与宿主隔离开。它运行所在的账户,比您设置的任何单个标志都更重要。

我需要为 Gemini CLI 开放任何防火墙端口吗?

不需要。它是一个向 Google API 发出站 HTTPS 调用的客户端,所以它需要出站 443 端口,但不需要任何入站端口。如果您用 OAuth 隧道,那个固定的回调端口(比如 8085)位于 localhost 上、通过您的 SSH 转发抵达,而不是一个开放的入站端口。把入站保持锁死。

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