如何用 tmux 在遠端 VPS 持續執行 Claude Code
Claude Code 會因 SSH 中斷收到 SIGHUP 而停止。本文示範在永不睡眠的 Linux VPS 以 tmux 執行、設定安全環境,並說明斷線後如何重新連回工作階段。
問題出在筆電上蓋,不是 CLI
Claude Code 在筆電上執行正常,但闔上上蓋後就會停止:SSH 工作階段中斷,shell 收到 SIGHUP,執行測試 3 分鐘的 agent 也會隨之結束。請在永不進入睡眠的機器上執行 CLI,並放在終端機多工器中,讓相關程序不再是 SSH 工作階段的子程序。這就是全部的訣竅;真正關鍵的是 tmux,而不是安裝程序。
本頁說明如何操作一台讓 agent 持續執行的主機。如果沒有可持續開機的 Linux 伺服器,以下內容就不適用。這是唯一不可省略的前提。
tmux 實際上做了什麼
當您透過 SSH 登入時,sshd 會分叉出一個 shell,並提供虛擬終端機;您從該 shell 啟動的所有程式,都是它的子程序。連線中斷後,核心會拆除 pty,shell 會收到 SIGHUP,接著依序對其子程序發出掛斷訊號。以前景執行的長時間程序會終止。
tmux 會反轉這個擁有關係。您輸入的 tmux 命令是精簡用戶端,透過 Unix socket 與脫離終端機執行的 tmux server 通訊。工作階段中的 shell 是該 server 的子程序,而不是 sshd 的子程序。SSH 連線中斷時,用戶端會結束,但 server、工作階段及正在執行工作的代理程式仍會繼續執行。重新連線後,執行 tmux attach,即可回到相同的 shell,並保留相同的回捲內容。nohup 同樣能在掛斷後繼續執行,但無法讓您重新連回工作階段;背景執行的 TUI 也無法重新附加。Claude Code 具有互動性;tmux(或 screen)才是適合的工具。
Sizing the box
CLI 是 Node 程序;它不會耗盡整台機器的資源。真正耗用資源的是代理程式代表你執行的內容:建置、完整測試套件、tsc、語言伺服器或 Docker 中的資料庫。請依工具鏈而不是 CLI 規模配置資源。即使計畫永遠不使用,也應加入 swap。這能將直接觸發的 OOM 終止轉為較慢的建置:
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也要監控磁碟:儲存庫、node_modules 與 Docker 映像檔很快就會累積。如果工具鏈會從容器延伸至完整虛擬機器,例如 KVM guest 或本機 Kubernetes 節點,請在確定方案前確認其公開 CPU 虛擬化擴充功能。因為在 VPS 上執行巢狀虛擬化需要由供應商啟用,不能從 guest 內部自行開啟。
先建立非 root 使用者
建立專用使用者及其個人家目錄,並放置您的公開金鑰:
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在繼續之前,先從第二個終端機測試登入。此時仍保留密碼驗證,作為備援。如果出現 遭拒絕(publickey),問題通常是該 .ssh 目錄的擁有者或模式設定,而不是金鑰本身。
刻意不讓 agent 加入 sudo 群組。如果需要系統套件,就安裝該套件。這項決定能排除大多數因誤下 shell 命令而損壞主機的可能性。
長時間運作伺服器的 SSH 安全維護
對於全天候連線至公開網際網路、保存 agent 與原始碼的機器,保留密碼驗證並不值得承擔風險。請將其停用。在 Ubuntu 24.04 與 Debian 13 上,/etc/ssh/sshd_config 包含 /etc/ssh/sshd_config.d/*.conf,因此請新增檔案,而不要直接編輯主要設定檔:
# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no先驗證設定並重新載入。測試時保留目前的工作階段開啟,並從第二個終端機建立一個新的工作階段:
sudo sshd -t && sudo systemctl restart sshUbuntu 24.04 有一項細節需要注意:sshd 由 socket 啟用。驗證設定會套用至 systemctl restart ssh,但若變更監聽的 Port,還需要執行 systemctl daemon-reload 並重新啟動 ssh.socket。
接著設定防火牆。啟用防火牆之前先允許 SSH,否則會把自己鎖在伺服器外:
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable安裝 fail2ban 前,請先確認它能帶來什麼效益:密碼驗證停用後,暴力破解本來就無法成功;它的作用是讓失敗的嘗試不再寫入 journal。
# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h最後,使用 sudo apt install unattended-upgrades 與 sudo dpkg-reconfigure -plow unattended-upgrades 自動套用修補程式。請注意它與 tmux 的互動:啟用 Unattended-Upgrade::Automatic-Reboot 後,核心更新會重新啟動伺服器,所有工作階段也會隨之中斷。將其維持停用,並在沒有工作正在執行時,依自己的排程重新啟動。版本升級也有相同的注意事項:將伺服器從 Ubuntu 24.04 升級至 26.04 會重新啟動 sshd 與核心,因此應安排在沒有 tmux 工作階段執行重要工作的時段。
在 Ubuntu 上安裝 Node.js 與 Claude Code
Claude Code 是 Node CLI,因此需要目前版本的 Node。Ubuntu 和 Debian 的發行版套件通常較舊;NodeSource 是常用的安裝方式,並提供已簽署的套件庫(不需要 apt-key,該工具已淘汰):
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version接下來是最容易出錯的部分:以你的 agent 使用者安裝 CLI,絕不要使用 sudo npm -g。 由 root 擁有的全域 prefix 之後會造成權限錯誤,也會在 npm 快取中留下由 root 擁有的檔案。先將 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 @anthropic-ai/claude-code
claude --version請將 export 放在 ~/.bashrc,不要放在 ~/.profile;而且必須放在檔案開頭附近「If not running interactively, don't do anything」這個防護條件的上方:tmux 可能啟動非 login shell,而這類 shell 會讀取 ~/.bashrc 並略過 ~/.profile;~/.profile 只會在 login shell 中執行。使用 nvm 這類版本管理工具安裝每位使用者專用的 Node,也能達到相同效果;無論採用哪種方式,目標都是讓 npm install -g 不需要 sudo。npm 仍可正常使用,也可以改用 Anthropic 的原生安裝指令碼,這是目前文件記載的預設方式。貼上指令前,請先查看 Anthropic 的安裝文件,因為安裝方式可能變更。
在 repo 中執行 claude 即可啟動。第一次執行時會引導你完成驗證;無頭伺服器沒有瀏覽器,因此流程會提供一個 URL,讓你在自己的電腦上開啟,並提供一組代碼,讓你帶回終端機。(另一種方式是在環境中設定 API key。)無論採用哪種方式,該憑證現在都會儲存在伺服器上。接下來就是大家常略過的部分。
爆炸半徑的討論
具備 shell 存取權的代理程式就是一個 shell。它能讀取執行身分可讀取的所有內容,也能推送到該身分可推送的任何位置。這不是工具的缺點,而是工具的定義。因此,執行代理程式所使用的帳號,比任何單一設定都更重要。
- 專用的非特權使用者。 不加入
sudo群組,也不與自己的帳號共用家目錄。 - 伺服器上不要存放正式環境憑證。 不要有
~/.aws/credentials存放正式環境金鑰,不要從正式環境複製.env,也不要提供可寫入重要資源的資料庫密碼。請提供 staging 或唯讀憑證給代理程式。 - 限制權限範圍的 token。 使用僅限單一 repository 的細部權限 GitHub token;若唯讀權限已足夠,則使用 deploy key。
Claude Code 提供可完全略過權限提示的 flag。在筆記型電腦或可隨時丟棄的專案上,是否使用由你決定。但在存放 token 的伺服器上,這會移除防止錯誤解讀指令導致 git push --force 的最後一道防線。你略過的提示也不是只能全部接受或全部拒絕。隨著 自動模式成為新的預設值,了解未持續監看之伺服器應固定使用哪種權限模式相當重要。該 flag 實際變更的內容,以及如何從內建 sandbox 一路到可丟棄的 VPS,限制使用啟用該 flag 的代理程式,請參閱 在伺服器上安全執行 Claude Code。
Deploy key 與 SSH agent forwarding
你可能會想要 ssh -A,讓 git 使用筆記型電腦上的金鑰。請先了解這會授予哪些權限:agent forwarding 會將本機 SSH agent 的 socket 提供給伺服器上以該使用者身分執行的程序。只要你保持連線,任何以 agent 身分執行的程序,包括代理程式,都能要求你的金鑰為它可連線的任何主機簽章。這遠不只是「讓 git pull 這個 repository」。
請改為在伺服器上產生金鑰,將其登錄為單一 repository 的 deploy key(只有代理程式需要推送時才授予寫入權限),並設定 git identity,讓從伺服器建立的 commit 能清楚辨識:
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"tmux 工作流程
安裝 tmux(sudo apt install tmux),然後建立最基本的 ~/.tmux.conf:
set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"日常使用只需掌握 4 個指令:
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 claudetmux new -A -s claude 是最需要記住的指令。工作階段存在時,它會連接工作階段;不存在時,則會建立工作階段。因此,開始工作與連線中斷後恢復工作,都能使用同一個指令。請為它建立別名。在工作階段中,Ctrl-b c 會開啟視窗,Ctrl-b n 和 Ctrl-b p 可在視窗間切換,Ctrl-b [ 會進入複製模式以向上捲動查看內容(q 可離開複製模式)。
有一點必須了解:不要長時間保留不再使用的工作階段。agent 每次互動都會重新傳送完整對話,因此,在讓工作階段持續執行一週前,請先閱讀長時間執行的 Claude Code 工作階段會如何消耗 token。
故障模式
「我的工作階段不見了。」 tmux ls 顯示 no server running on /tmp/tmux-1000/default。這幾乎總是表示程序從未在 tmux 內執行;你透過 SSH 登入後直接執行 claude,而中斷連線使程序終止。沒有可復原的內容。避免此問題的習慣是:每次登入後,第一個執行的命令都應是 tmux new -A -s <project>。
面板縮成很小的方框。 tmux 會依所有已附加用戶端中最小的尺寸設定工作階段,因此另一台機器上仍附加的過期用戶端會壓縮顯示畫面。附加時強制中斷其他用戶端:tmux attach -d -t claude。
建置程序顯示 Killed。 只有一行訊息,沒有堆疊追蹤。使用 sudo dmesg -T | grep -i -E 'out of memory|killed process' 確認;這表示核心的 OOM killer 選中了占用資源最多的程序。從 Node 可能會改為看到 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory。依序可採取以下修正措施:加入 swap(如上所述)、限制測試與編譯器的平行處理數、使用 NODE_OPTIONS=--max-old-space-size=... 提高 Node 的 heap 大小,或升級 VPS。OOM killer 也可能選中 tmux server 而非建置程序,導致工作階段一併中斷;如果正在執行 systemd-oomd,它也可能終止整個使用者 slice,造成相同結果。
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'。 這表示全域安裝到 root 擁有的 prefix。使用上方的 ~/.npm-global prefix。如果你先前曾執行 sudo npm,也可能看到 Your cache folder contains root-owned files;請使用 sudo chown -R $(id -u):$(id -g) ~/.npm 修復。
claude: command not found,但只有部分情況會發生。 你的 PATH export 位於 ~/.bashrc 中「If not running interactively, don't do anything」防護條件的下方,因此非互動式 shell 會略過它。將 export 移到該防護條件上方,並保留在 ~/.bashrc 中,不要放在 ~/.profile:tmux 可能啟動非 login shell;這類 shell 會讀取 ~/.bashrc,不會處理 ~/.profile。
附加後顏色顯示異常。 這是 TERM 不一致所致;上方的 default-terminal 行即可修正。
重新開機後工作階段消失。 這不是錯誤:tmux server 是程序,重新開機會使其終止。請檢查 uptime。
規模擴大後會出現什麼問題
更多專案。 每個儲存庫使用一個以儲存庫命名的 tmux 工作階段;tmux ls 就是你的儀表板。若不維持命名規則,最後會出現 0、1、2 這類工作階段。多個工作階段同時執行後,不必讓它們彼此隔離,因為同一台主機上的一個工作階段可以向另一個工作階段傳送訊息。例如,負責長時間重構的 agent 可以要求另一個工作階段執行測試。連接埠也會以相同方式失控。當 6 個儲存庫都需要 :3000 時,就不應再手動分配連接埠,而應讓Traefik reverse proxy 在 Docker Compose 下依主機名稱將多個應用程式分流。
更多使用者。 tmux socket 以使用者為單位,因此同一台主機上的兩名開發人員各自擁有自己的 tmux server,彼此看不到對方的工作階段。透過共用 socket 共用一個工作階段,表示所有人都會以同一個 Unix 使用者身分,在同一個 shell 中輸入指令,也會承擔相應的稽核與權限後果。分開使用不同使用者,是不花俏但正確的做法。
無人值守的工作。 tmux 用於可供使用者連線的互動式工作階段。按照排程執行且無人監看的工作,應放入 systemd unit 與 timer,這樣即可取得日誌、重新啟動原則,以及開機後持續執行的能力。使用 tmux 執行類似 cron 的工作,表示該工作其實應該成為服務。
最後補充一點:agent 啟動的開發伺服器應繫結至 127.0.0.1,而不是 0.0.0.0,並透過 SSH tunnel(ssh -L 3000:127.0.0.1:3000 agent@your-server)連線,不要在 ufw 中開放連接埠。當你需要轉送約 6 個連接埠,或手機與筆記型電腦都要存取同一個預覽環境時,應改為在 VPS 前方放置自架的 WireGuard VPN:開發伺服器繫結至私有介面,而 ufw 繼續拒絕來自公開介面的所有連線。只有停止在防火牆上開洞,防火牆才有幫助。
Claude Code 不是唯一選擇:在 VPS 上執行 coding AI agent時,也可以評估 Aider 與 Goose。
FAQ
SSH 連線中斷後,Claude Code 會繼續執行嗎?
只有在 tmux 中啟動時才會。直接從 SSH shell 啟動的程序是該 shell 的子程序,連線中斷時會隨著 pty 一起終止。在 tmux 中,shell 隸屬於已分離的 tmux server,因此 agent 會繼續執行目前的工作,而 tmux attach 會讓你回到相同的捲動內容。每次登入後都將 tmux new -A -s <project> 設為第一個命令,即可避免這個問題。
我應該使用 sudo npm install -g 安裝 CLI 嗎?
不應該。由 root 擁有的全域 prefix 會導致後續安裝出現 EACCES 錯誤,並在 npm cache 中建立由 root 擁有的檔案。將 npm 的 prefix 設為 ~/.npm-global,或使用 nvm 等版本管理工具;以未具特權的 agent 使用者安裝,並將 ~/.npm-global/bin 從 ~/.bashrc 匯出至 PATH,位置要在互動式防護條件之前。如果你已經執行過一次 sudo npm,請使用 sudo chown -R $(id -u):$(id -g) ~/.npm 修復 cache。
在執行 agent 的主機上使用 ssh -A agent forwarding 安全嗎?
它授予的權限遠超過工作所需。Forwarding 會將本機 SSH agent 的 socket 暴露給該使用者執行的每個程序,因此只要你仍保持連線,主機上的任何程序都能要求你的金鑰,為它可連線的任何主機進行簽署。在伺服器上產生 ed25519 金鑰,並將其註冊為每個 repository 專用的 deploy key。只有 agent 確實需要 push 時,才授予寫入權限。
為什麼我的建置只輸出 Killed?
這個沒有 stack trace 的單字表示 kernel OOM killer 已終止程序。使用 sudo dmesg -T | grep -i -E 'out of memory|killed process' 確認;在 Node 中也可能看到 JavaScript heap out of memory。請依序處理:新增 swapfile、限制測試與編譯器的平行處理數量、提高 NODE_OPTIONS=--max-old-space-size=...,最後再升級 VPS 規格。請注意,OOM killer 可能終止 tmux server,而不是建置程序,導致整個工作階段一併中斷。
應該使用 tmux,還是 systemd service?
tmux 適合需要連線、查看輸出並輸入命令的互動式工作階段,agent session 正是這類工作。依排程執行且無人監看的工作,應使用 systemd unit 與 timer;這樣可直接取得日誌、restart policy 及開機後持續執行的能力。如果你需要使用 tmux 執行類似 cron 的工作,表示這項工作應該設為 service。