Cách chạy Gemini CLI trên headless VPS
Hướng dẫn cài đặt Gemini CLI trên VPS không giao diện. Cách dùng Node, API key và tmux để chạy tác vụ agent dài hơi mà không lo mất kết nối SSH.
Những gì bạn đang xây dựng
Một Gemini CLI luôn chạy trên server riêng của bạn, có thể truy cập qua SSH, chạy các tác vụ agent dài hơi ngay cả khi bạn đã gập laptop. Việc cài đặt chỉ mất ba lệnh. Phần khó nhất là xử lý mọi thứ vốn được thiết kế cho máy tính có giao diện desktop: CLI của Google muốn mở trình duyệt để đăng nhập, nhưng server của bạn thì không có. Vì vậy, phần lớn hướng dẫn này sẽ đi theo hướng headless — cài đặt một bản Node hiện đại mà distro mặc định không có, cài đặt npm global mà không cần quyền root, xác thực không cần trình duyệt bằng API key để tránh lưu vào shell history, và dùng tmux để tránh việc mất kết nối SSH làm ngắt quãng tác vụ đang chạy.
Gemini CLI là một chương trình Node mã nguồn mở (Apache-2.0) (@google/gemini-cli) dùng để giao tiếp với các model Gemini của Google; nó có thể đọc/ghi file, chạy shell commands và điều khiển các công cụ trong thư mục làm việc. Trên một VPS, nó là một agent nhỏ gọn, luôn sẵn sàng để bạn để nó tự chạy — đó là lý do tại sao tài khoản chạy nó và các credentials lưu trên máy quan trọng hơn bất kỳ thiết lập đơn lẻ nào ở đây.
Điều kiện tiên quyết và những lỗi thường gặp
- Một VPS Ubuntu 24.04 KVM mới có quyền root hoặc sudo. Mọi gói KVM đều dùng được; bản thân CLI rất nhẹ, chỉ tốn vài trăm MB RAM khi ở trạng thái nghỉ.
- Node.js 20 hoặc mới hơn. Đây là yêu cầu bắt buộc về phiên bản, và package của distro sẽ thấp hơn mức này — xem phần tiếp theo.
- Kết nối HTTPS outbound (port 443) đến các API của Google. Không cần mở port inbound; đây là client chứ không phải server, nên bạn không cần mở firewall cho nó.
- Một phương thức xác thực không cần trình duyệt trên server: hoặc là Gemini API key từ Google AI Studio, hoặc là dùng SSH tunnel quay về trình duyệt trên máy cá nhân của bạn. Cách dùng API-key là cách tối ưu để chạy script và các tác vụ tự động.
- Docker hoặc Podman, chỉ nếu bạn muốn có sự cô lập
--sandbox. Đây là phần tùy chọn, sẽ nói ở cuối.
Lỗi mà ai cũng gặp phải: luồng đăng nhập gemini thân thiện khi chạy lần đầu được thiết kế cho desktop. Nó cố gắng mở trình duyệt và trên một máy headless, nó sẽ thất bại hoặc đưa cho bạn một link không hoạt động. Hãy quyết định phương thức xác thực trước khi bắt đầu.
Node: package của distro quá cũ
Ubuntu 24.04 đi kèm Node 18.19.1 trong repository riêng, cùng với npm 9.2.0. Gemini CLI's package.json yêu cầu engines: { node: ">=20" }, và npm mặc định không chặn việc sai lệch phiên bản — nó vẫn cài đặt và in ra một cảnh báo về sự chênh lệch:
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 }Nếu bạn bỏ qua cảnh báo đó, CLI sẽ chạy trên một runtime không được hỗ trợ, dẫn đến lỗi hoặc crash ngay khi nó gọi đến một Node 20+ API mà nó mong đợi. Node 18 cũng đã hết vòng đời (end-of-life) vào tháng 4 năm 2025, nên dù thế nào thì nó cũng là ngõ cụt. Hãy cài đặt một bản LTS mới nhất trước khi cài đặt CLI. Có hai cách sạch sẽ: NodeSource (một apt repo có chữ ký toàn hệ thống) hoặc nvm (trình quản lý phiên bản cho từng user). Hãy chọn một cái.
Dùng NodeSource, nếu bạn muốn Node có sẵn cho mọi user trên máy:
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 --versionnode --version phải in ra v20.x hoặc cao hơn — v24.x là bản LTS đang hoạt động. Kiểm tra trang NodeSource để lấy script cài đặt mới nhất; setup_24.x trong URL là phần cần cập nhật khi có bản LTS mới hơn.
Dùng nvm, nếu bạn muốn giữ Node trong home của một user và không bao giờ dùng sudo:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.bashrc
nvm install --lts
node --versionv0.40.1 trong URL này là bản mới nhất tại thời điểm viết bài; hãy kiểm tra README của nvm để lấy bản mới nhất và thay thế phiên bản trước khi chạy. nvm có lợi thế lớn cho việc này: nó cài đặt Node và các global packages dưới ~/.nvm, nên vấn đề quyền hạn global-install ở phần sau sẽ không bao giờ xảy ra. Nếu bạn dùng nvm, bạn có thể bỏ qua bước npm-prefix.
Cài đặt CLI mà không cần sudo npm -g
Lệnh rất dễ gây nhầm lẫn là sudo npm install -g @google/gemini-cli. Đừng dùng nó. Một global prefix thuộc quyền sở hữu của root sẽ gây lỗi permission khi cài đặt các thứ sau này và để lại các file thuộc quyền root trong npm cache, gây rắc rối nhiều tháng sau. Nếu bạn chạy npm install -g (không sudo) với Node của hệ thống, bạn sẽ gặp lỗi khác:
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'Đó là do npm đang cố ghi vào /usr/lib mà user của bạn không có quyền. Cách sửa không phải là dùng sudo — mà là trỏ npm's global prefix về thư mục home của bạn để các bản cài đặt global nằm ở nơi bạn có quyền sở hữu:
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, chứ không phải ~/.profile, là chủ ý: tmux — thứ bạn sẽ dùng để chạy CLI sau hai phần nữa — khởi chạy một non-login shell sẽ đọc ~/.bashrc và bỏ qua ~/.profile, nên một dòng PATH trong sai file sẽ khiến gemini bị ẩn đúng chỗ bạn cần nó nhất. gemini --version in ra số phiên bản là bài kiểm tra duy nhất. Nếu bạn nhận được gemini: command not found, nghĩa là lệnh export PATH của bạn không có tác dụng — xem các lỗi thường gặp. Với nvm, hãy bỏ qua hoàn toàn các dòng prefix: nó đã cài đặt globals dưới home của bạn rồi.
Nếu bạn đã chạy sudo npm trước đó và bây giờ thấy Your cache folder contains root-owned files, hãy sửa nó một lần bằng sudo chown -R $(id -u):$(id -g) ~/.npm.
Vấn đề xác thực headless, và cách xử lý
Chạy gemini tương tác lần đầu tiên, nó sẽ đề nghị đăng nhập bằng tài khoản Google. Trên desktop, nó sẽ mở một tab trình duyệt. Trên VPS headless, không có trình duyệt, nên nó sẽ in ra một URL localhost để bạn tự mở, hoặc thất bại hoàn toàn với lỗi kiểu như:
Failed to open browser. Please visit the following URL to authorize:
https://accounts.google.com/o/oauth2/v2/auth?...&redirect_uri=http://localhost:PORTCái bẫy nằm ở redirect_uri=http://localhost:PORT. Ngay cả khi bạn mở URL đó trên laptop và đồng ý, Google sẽ redirect về http://localhost:PORT — tức là localhost trên server, một port mà laptop của bạn không thể chạm tới. Việc đăng nhập sẽ không bao giờ hoàn tất.
Có hai cách thực tế để giải quyết.
Cách thứ nhất là dùng API key, đây là cách mặc định chuẩn cho server. Tạo một key trong Google AI Studio (aistudio.google.com) và truyền nó cho CLI dưới dạng biến môi trường; nó sẽ đọc GEMINI_API_KEY và bỏ qua hoàn toàn luồng trình duyệt. Bây giờ là phần "giữ nó tránh khỏi history và các file có quyền đọc chung": Đừng gõ export GEMINI_API_KEY=AIza... tại prompt — nó sẽ nằm trong ~/.bash_history dưới dạng cleartext, và đừng để nó trong file mà người khác có thể đọc. Hãy viết nó vào một file mode-600 mà shell sẽ source khi bắt đầu:
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 ~/.bashrcchmod 600 nghĩa là chỉ user của bạn mới có quyền đọc file. Xác nhận key đã vào môi trường bằng printenv GEMINI_API_KEY; nếu nó không in ra gì, CLI sẽ quay lại luồng trình duyệt và thất bại. Nó cũng đọc file .env trong ~/.gemini/ nếu bạn thích cách đó — quy tắc tương tự, tức là chmod 600 ~/.gemini/.env.
Cách thứ hai là giữ đăng nhập tài khoản Google cá nhân (và gói miễn phí của nó) bằng cách tunnel OAuth callback về laptop của bạn. Điểm yếu là loopback server của CLI sẽ bind một port ngẫu nhiên mỗi lần chạy, nên không có gì cố định để forward trừ khi bạn pin nó trước bằng biến môi trường OAUTH_CALLBACK_PORT, sau đó forward đúng 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
geminiCLI không thể mở trình duyệt nên nó sẽ in ra auth URL; hãy mở nó trên trình duyệt laptop, đồng ý, và khi Google redirect về http://localhost:8085/..., SSH forward sẽ chuyển nó đến loopback server trên VPS và việc đăng nhập hoàn tất. Nếu bạn không pin port, nó sẽ rơi vào một port ngẫu nhiên mới mỗi lần chạy, thứ mà không có ssh -L nào thiết lập trước đó có thể bắt được. Cách này hoạt động, nhưng yêu cầu bạn phải ngồi trước trình duyệt, nên không dùng được cho script. Với bất kỳ tác vụ nào bạn để chạy tự động, hãy dùng API key.
Nếu bạn dùng Vertex AI hoặc Google Cloud project thay vì AI Studio, hãy set GOOGLE_API_KEY cùng với GOOGLE_GENAI_USE_VERTEXAI=true, hoặc GOOGLE_CLOUD_PROJECT cho license Code Assist — quy tắc biến môi trường và file mode-600 vẫn tương tự.
Chạy trong tmux để tránh mất kết nối SSH làm chết process
Một process gemini bạn chạy trực tiếp từ shell SSH là con của shell đó. Mất kết nối — gập laptop, rớt Wi-Fi, hoặc hết thời gian idle — và sshd sẽ đóng pseudo-terminal, shell nhận tín hiệu SIGHUP, và CLI cũng bị ngắt theo. Một tác vụ đang sửa file được 10 phút sẽ chết cùng nó, và khi kết nối lại, sẽ không còn process nào để khôi phục.
tmux giải quyết việc này bằng cách sở hữu shell thay vì để sshd sở hữu nó. Đây là cùng một pattern như chạy một AI coding agent trên remote VPS trong tmux, và nó hoạt động tương tự ở đây:
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 geminitmux new -A -s gemini sẽ attach vào một session tên là gemini nếu nó đã tồn tại, hoặc tạo mới nếu chưa có, vì vậy đây là lệnh duy nhất cần chạy ngay sau mỗi lần login. Shell bên trong thuộc về tmux server đã được detach, không thuộc về session SSH của bạn, nên khi mất kết nối, CLI vẫn tiếp tục làm việc. Kết nối lại, attach, và bạn sẽ quay lại đúng vị trí cũ.
Đối với các lần chạy script không tương tác, Gemini CLI có chế độ headless: gemini -p "summarise the failing tests in this repo" in ra câu trả lời và thoát, và --output-format json đưa ra output dạng máy đọc được để pipe đi nơi khác. Chế độ headless với API key là thứ bạn cần khi chạy một batch job dài trong tmux, hoặc chạy từ cron — với một lưu ý: cron không source các file login của bạn, nên hãy cung cấp cho dòng crontab một GEMINI_API_KEY riêng (hoặc để command source ~/.gemini_env), nếu không CLI sẽ quay lại luồng trình duyệt và thất bại.
Sandboxing và quyền hạn trên máy chạy cả production
Một agent có quyền truy cập shell chính là một shell. Gemini CLI có thể chạy commands, và mặc định nó sẽ hỏi trước mỗi lệnh nguy hiểm — nhưng người dùng thường dùng --yolo (tự động approve mọi tool call), và khi đó nó có thể xóa file, push lên git, hoặc tác động vào các dịch vụ nội bộ với toàn bộ quyền hạn của user đang chạy. Trên một máy chạy cả production, đó là một phạm vi ảnh hưởng (blast radius) thực sự, không phải giả định.
Ba lớp kiểm soát, theo thứ tự mức độ bảo mật tăng dần:
- Chạy nó dưới một user chuyên dụng, không có đặc quyền. Không dùng root, không dùng user thuộc nhóm
sudo. Tạo một useragentvới home riêng, cài Node và CLI tại đó; một lệnh sai lầm sẽ chỉ nằm gọn trong tài khoản đó. Đây là quyết định quan trọng nhất. - Giữ credentials production tách biệt khỏi máy này. Không có prod
~/.aws/credentials, không có.envcopy từ production, không có password database có quyền write vào bất cứ thứ gì quan trọng. Hãy cấp cho nó credential của môi trường staging hoặc chỉ có quyền read-only. - Sử dụng sandbox tích hợp sẵn. Nếu đã cài Docker hoặc Podman,
gemini --sandbox(hoặcGEMINI_SANDBOX=docker) sẽ chạy các tool calls của agent bên trong một container cô lập với filesystem và network của host. Đây không phải là sự thay thế cho việc dùng user không đặc quyền, nhưng là lớp bảo vệ thứ hai mạnh mẽ khi cùng một VPS đang chạy các công việc thực tế.
Nếu bạn đang chạy Gemini CLI cạnh các công cụ tự host khác — ví dụ như một MCP server cung cấp tools cho agent trên cùng một VPS — hãy coi mỗi khả năng được thêm vào là một bề mặt tấn công mới mà agent có thể chạm tới, và giới hạn token được cấp cho nó đúng với một job duy nhất.
Quota, chi phí, và phương thức xác thực bạn đã chọn
Phương thức xác thực sẽ quyết định cách bạn bị tính phí. Tài khoản Google cá nhân (đường OAuth) sử dụng gói Gemini Code Assist miễn phí, với giới hạn theo phút và theo ngày; nếu vượt quá, các request sẽ trả về lỗi rate-limit cho đến khi chu kỳ reset. Một API key từ AI Studio có thể miễn phí hoặc tính phí tùy vào project — key tính phí sẽ có limit cao hơn và tính phí theo token. Xác thực qua Vertex và Cloud-project sẽ tính phí qua Google Cloud.
Hai lưu ý thực tế. Một agent chạy tự động trong vòng lặp có thể đốt quota rất nhanh, nên hãy theo dõi nó trong vài lần đầu trước khi tin tưởng giao cho cron. Và nếu lý do bạn dùng model phía server là vì quyền riêng tư hoặc muốn inference không giới hạn thay vì dùng model của Google, thì đó là một công cụ khác — tự host một open LLM với Ollama trên VPS sẽ giữ weights và prompts trên máy của chính bạn, đổi lại là bạn phải chạy một model nhỏ hơn nhiều so với Gemini.
Cập nhật
Gemini CLI cập nhật thường xuyên. Vì bạn đã cài đặt nó vào một prefix thuộc sở hữu của user, việc cập nhật không bao giờ cần sudo:
npm install -g @google/gemini-cli@latest
gemini --versionCó các kênh release: @latest là bản stable, @preview là bản preview hàng tuần, @nightly là bản bleeding edge — hãy chốt ở @latest cho bất kỳ thứ gì bạn đang dựa vào. Với nvm, các global packages nằm dưới phiên bản Node đang active, nên sau khi dùng nvm use để đổi Node, bạn có thể cần cài lại CLI. Hãy đọc release notes thay vì chạy theo mọi bản patch.
Các lỗi thường gặp, kèm theo chuỗi ký tự chính xác
npm WARN EBADENGINE Unsupported engine ... required: { node: '>=20' }, sau đó CLI crash khi đang chạy. Node quá cũ — distro dùng bản 18.19.1, vốn đã hết vòng đời. Hãy cài Node 20+ từ NodeSource hoặc nvm, xác nhận bằng node --version, và nếu bạn cài nhiều bản Node, hãy kiểm tra xem which node đã trỏ đúng vào bản mới thay vì /usr/bin/node chưa.
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Cài đặt global vào một prefix thuộc quyền root. Đừng dùng sudo — hãy set npm config set prefix ~/.npm-global, đặt ~/.npm-global/bin vào PATH, và cài đặt lại bằng user bình thường của bạn. Nếu một lệnh sudo npm trước đó đã để lại các file cache thuộc quyền root (Your cache folder contains root-owned files), hãy chạy sudo chown -R $(id -u):$(id -g) ~/.npm.
Failed to open browser, đăng nhập bị treo, hoặc redirect_uri=http://localhost:PORT không thể truy cập. Luồng OAuth yêu cầu trình duyệt mà server không có, và localhost callback trỏ về server chứ không phải laptop của bạn. Hãy dùng cách API-key (GEMINI_API_KEY), hoặc pin OAUTH_CALLBACK_PORT, forward nó qua SSH bằng ssh -L, và mở URL tại máy local.
Process biến mất khi SSH bị ngắt. Bạn đã chạy gemini trực tiếp từ shell SSH, nên nó là con của shell đó và chết cùng pty khi mất kết nối. Không có gì để khôi phục. Hãy bắt đầu mọi session bằng tmux new -A -s gemini và chạy CLI bên trong đó.
Xác thực vẫn lỗi dù đã set key — CLI quay lại trình chọn xác thực, hoặc request trả về API key not valid với HTTP 400. Key không nằm trong môi trường mà CLI nhìn thấy. Xác nhận bằng printenv GEMINI_API_KEY; nếu nó trống, nghĩa là ~/.gemini_env của bạn chưa được source — hãy kiểm tra xem dòng đó đã nằm trong ~/.bashrc chưa (các interactive shell bao gồm cả tmux sẽ đọc nó, nhưng cron và các non-interactive shell khác thì không). Một khoảng trắng hoặc dấu ngoặc kép thừa trong giá trị key cũng gây ra lỗi API key not valid.
429 / RESOURCE_EXHAUSTED / thông báo rate-limit. Bạn đã chạm quota của tier mà bạn đang dùng. Hãy đợi đến khi chu kỳ reset, giảm tốc độ của agent, hoặc chuyển sang API key có tính phí. Một agent bị kẹt trong vòng lặp retry sẽ liên tục gặp lỗi này — hãy dừng nó và kiểm tra xem nó đang làm gì.
FAQ
Làm thế nào để xác thực Gemini CLI trên server headless?
Hãy dùng API key, đừng dùng đăng nhập trình duyệt. Tạo một key trong Google AI Studio, đưa nó vào một file mode-600 mà shell của bạn sẽ source (export GEMINI_API_KEY=...), và CLI sẽ bỏ qua hoàn toàn luồng trình duyệt OAuth. Nếu bạn đặc biệt muốn dùng gói miễn phí của tài khoản cá nhân, hãy pin port loopback bằng OAUTH_CALLBACK_PORT=8085, forward nó về laptop bằng ssh -L 8085:localhost:8085 user@server, và mở URL được in ra tại máy local — nhưng cách này cần bạn phải ngồi trước trình duyệt, nên không dùng được cho script.
Tại sao cài đặt npm global lại đòi sudo, và làm sao để tránh nó?
Vì global prefix mặc định của npm là /usr/lib/node_modules, user của bạn không có quyền ghi vào đó, nên lệnh npm install -g thông thường sẽ lỗi EACCES. Cách sửa sai là dùng sudo npm -g, vì nó để lại các file thuộc quyền root làm hỏng các lần cài đặt sau. Cách sửa đúng là trỏ prefix về thư mục home (npm config set prefix ~/.npm-global) và thêm bin vào PATH, hoặc dùng nvm, thứ sẽ tự động cài đặt global packages dưới thư mục home của bạn.
Làm thế nào để giữ Gemini CLI tiếp tục chạy sau khi tôi ngắt kết nối?
Hãy chạy nó bên trong tmux. Một process được khởi chạy từ shell SSH sẽ chết khi kết nối bị ngắt vì nó là con của shell đó; tmux chạy shell dưới một server đã được detach, giúp nó sống sót qua việc ngắt kết nối. Hãy dùng tmux new -A -s gemini, chạy gemini bên trong, detach bằng Ctrl-b d, và reattach lại sau bằng tmux attach -t gemini.
Chạy Gemini CLI trên máy production có an toàn không?
Chỉ an toàn nếu bạn cẩn thận, vì một agent có quyền truy cập shell có thể làm bất cứ điều gì user đó có thể làm. Hãy chạy nó dưới một user chuyên dụng không có sudo, giữ các credentials production tách biệt khỏi máy, tránh dùng --yolo auto-approval, và dùng --sandbox (Docker hoặc Podman) để cô lập các tool calls khỏi host. Tài khoản mà nó chạy quan trọng hơn bất kỳ flag nào bạn thiết lập.
Tôi có cần mở port firewall nào cho Gemini CLI không?
Không. Đây là một client thực hiện các cuộc gọi HTTPS outbound đến các API của Google, nên nó cần port outbound 443 nhưng không cần bất kỳ port inbound nào. Nếu bạn dùng tunnel OAuth, port callback đã được pin (ví dụ 8085) nằm ở localhost và được truy cập thông qua SSH forward, chứ không phải qua một port inbound mở. Hãy giữ inbound được khóa chặt.