วิธีรัน Claude Code บน VPS ด้วย tmux ให้ทำงานต่อเนื่อง
เรียนรู้วิธีรัน Claude Code บน Linux VPS ผ่าน tmux เพื่อป้องกันปัญหาเซสชันหลุดเมื่อตัดการเชื่อมต่อ SSH พร้อมขั้นตอนการติดตั้ง การตั้งค่าความปลอดภัย และวิธีรับมือกับข้อผิดพลาดที่พบบ่อย
ปัญหาอยู่ที่ฝาพับแล็ปท็อป ไม่ใช่ที่ CLI
Claude Code ทำงานบนแล็ปท็อปของคุณได้ตามปกติจนกระทั่งคุณปิดฝาพับ: เซสชัน SSH จะถูกตัดการเชื่อมต่อ, shell จะได้รับสัญญาณ SIGHUP และเอเจนต์ที่กำลังรันการทดสอบอยู่จะหยุดทำงานทันที ให้รัน CLI บนเครื่องที่ไม่เข้าสู่โหมดพักเครื่อง (sleep) โดยรันภายใน terminal multiplexer ที่กระบวนการทำงานไม่ได้เป็นลูกของเซสชัน SSH ของคุณ นี่คือเคล็ดลับทั้งหมด และ tmux คือส่วนประกอบสำคัญที่รับภาระงาน ไม่ใช่ขั้นตอนการติดตั้ง
หน้านี้กล่าวถึงการใช้งานเครื่องเซิร์ฟเวอร์ที่คุณปล่อยให้เอเจนต์ทำงานทิ้งไว้ หากคุณไม่มีเซิร์ฟเวอร์ Linux ที่สามารถเปิดทิ้งไว้ได้ตลอดเวลา ข้อมูลทั้งหมดนี้ก็ไม่สามารถนำไปใช้ได้ นี่คือข้อกำหนดเบื้องต้นเพียงประการเดียวที่ต้องมีจริง
การทำงานจริงของ tmux
เมื่อคุณเชื่อมต่อผ่าน SSH ตัว sshd จะทำการ fork shell ขึ้นมาและส่งมอบ pseudo-terminal ให้ ทุกสิ่งที่คุณเริ่มทำงานจาก shell นั้นจะเป็นกระบวนการลูกของมัน หากการเชื่อมต่อหลุด เคอร์เนลจะทำลาย pty นั้นทิ้ง shell จะได้รับสัญญาณ SIGHUP และส่งสัญญาณต่อไปยังกระบวนการลูกทั้งหมด ส่งผลให้กระบวนการที่ทำงานเบื้องหน้า (foreground) ที่ใช้เวลานานต้องยุติลง
tmux จะกลับสถานะความเป็นเจ้าของกระบวนการ คำสั่ง tmux ที่คุณพิมพ์เป็นเพียงไคลเอนต์ขนาดเล็กที่สื่อสารผ่าน unix socket ไปยัง tmux server ซึ่งทำงานแยกส่วนจากเทอร์มินัลของคุณ shell ที่อยู่ภายในเซสชันจะเป็นกระบวนการลูกของเซิร์ฟเวอร์นั้น ไม่ใช่ของ sshd เมื่อคุณตัดการเชื่อมต่อ SSH ไคลเอนต์จะหายไป แต่เซิร์ฟเวอร์ เซสชัน และตัวแทนที่กำลังทำงานอยู่จะยังคงทำงานต่อไป เมื่อเชื่อมต่อใหม่และใช้คำสั่ง tmux attach คุณจะกลับเข้าสู่ shell เดิมพร้อมประวัติการเลื่อนหน้าจอ (scrollback) เหมือนเดิม ทั้งนี้ nohup สามารถอยู่รอดจากการตัดการเชื่อมต่อได้เช่นกัน แต่คุณจะไม่มีทางกลับเข้าไปควบคุมมันได้อีก คุณไม่สามารถแนบ (re-attach) กลับไปยัง TUI ที่ทำงานอยู่เบื้องหลังได้ Claude Code เป็นเครื่องมือแบบโต้ตอบ ดังนั้น tmux (หรือ screen) จึงเป็นเครื่องมือที่เหมาะสมกว่า
การกำหนดขนาดเครื่อง
CLI เป็นกระบวนการของ Node ซึ่งไม่ใช่สิ่งที่ทำให้ทรัพยากรเครื่องเต็ม สิ่งที่ทำให้เครื่องเต็มคือสิ่งที่ agent ทำงานแทนคุณ เช่น การ build, ชุดทดสอบเต็มรูปแบบ, tsc, language server หรือฐานข้อมูลใน Docker ให้กำหนดขนาดตาม toolchain ไม่ใช่ตาม CLI ควรเพิ่ม swap แม้ว่าคุณจะไม่ได้วางแผนที่จะใช้งานก็ตาม เพราะมันจะเปลี่ยนเหตุการณ์ OOM kill ที่รุนแรงให้กลายเป็นการ build ที่ช้าลงแทน:
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คอยตรวจสอบพื้นที่ดิสก์ด้วย: เพราะ repos, node_modules และ Docker images จะเพิ่มขึ้นอย่างรวดเร็ว และหาก toolchain ของคุณขยายขอบเขตไปไกลกว่า container จนถึง virtual machine เต็มรูปแบบ เช่น KVM guest หรือ local Kubernetes node ให้ตรวจสอบว่าแผนการใช้งานรองรับ CPU virtualisation extensions ก่อนที่คุณจะตัดสินใจเลือก เนื่องจาก การรัน nested virtualization บน VPS เป็นสิ่งที่ผู้ให้บริการต้องเปิดใช้งานให้คุณ ไม่ใช่สิ่งที่คุณสามารถเปิดเองได้จากภายใน guest
การใช้งานผู้ใช้ที่ไม่ใช่ root เป็นลำดับแรก
สร้างผู้ใช้เฉพาะสำหรับงานนี้พร้อมไดเรกทอรี home ของตนเอง และนำ public key ของคุณไปวางไว้ในตำแหน่งที่ถูกต้อง:
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ทดสอบการล็อกอินจากเทอร์มินัลที่สองก่อนดำเนินการต่อ ในขณะที่การยืนยันตัวตนด้วยรหัสผ่านยังคงใช้งานได้เพื่อเป็นทางเลือกสำรอง หากระบบตอบกลับด้วย Permission denied (publickey) สาเหตุส่วนใหญ่มักเกิดจากสิทธิ์การเข้าถึง (ownership) หรือโหมดของไดเรกทอรี .ssh ไม่ถูกต้อง มากกว่าที่จะเป็นที่ตัวกุญแจเอง
โดยเจตนาแล้ว agent จะไม่อยู่ในกลุ่ม sudo หากจำเป็นต้องใช้แพ็กเกจของระบบ คุณสามารถติดตั้งได้ด้วยตนเอง การตัดสินใจเพียงข้อเดียวนี้ช่วยลดความเสี่ยงส่วนใหญ่ที่คำสั่ง shell ที่ผิดพลาดอาจสร้างความเสียหายให้กับโฮสต์ได้
สุขอนามัยของ SSH สำหรับเซิร์ฟเวอร์ที่เปิดทิ้งไว้
การใช้การยืนยันตัวตนด้วยรหัสผ่านบนเครื่องที่เชื่อมต่ออินเทอร์เน็ตสาธารณะตลอดเวลา ซึ่งมีการรัน 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 sshข้อควรระวังสำหรับ Ubuntu 24.04: sshd ทำงานแบบ socket-activated การตั้งค่าการยืนยันตัวตนจะมีผลที่ 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 โดยต้องเข้าใจชัดเจนว่ามันช่วยอะไรคุณได้บ้าง: เมื่อปิดการยืนยันตัวตนด้วยรหัสผ่านแล้ว การโจมตีแบบ brute force จะไม่สามารถทำได้สำเร็จอยู่แล้ว แต่เครื่องมือนี้จะช่วยกันไม่ให้ความพยายามล็อกอินที่ล้มเหลวไปปรากฏอยู่ใน 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 ใดๆ ที่เก็บงานสำคัญของคุณไว้
การติดตั้ง Node.js และ Claude Code บน Ubuntu
Claude Code เป็น Node CLI ดังนั้นคุณจำเป็นต้องใช้ Node เวอร์ชันปัจจุบัน แพ็กเกจใน distro มักจะล้าสมัย การใช้ NodeSource จึงเป็นวิธีมาตรฐานบน Ubuntu และ Debian ซึ่งมาพร้อมกับ repo ที่มีการเซ็นรับรอง (ไม่มี apt-key แล้ว เครื่องมือดังกล่าวถูกยกเลิกไป)
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --versionส่วนที่ผู้คนมักทำผิดพลาด: ติดตั้ง CLI ในฐานะผู้ใช้ agent ของคุณ ห้ามใช้ sudo npm -g โดยเด็ดขาด การใช้ global prefix ที่เป็นของ root จะทำให้เกิดข้อผิดพลาดด้านสิทธิ์ในภายหลัง และทิ้งไฟล์ที่เป็นของ root ไว้ใน npm cache ให้กำหนด prefix ของ npm ไปที่โฮมไดเรกทอรีของผู้ใช้ก่อน:
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 อาจเริ่ม non-login shell ซึ่งจะอ่าน ~/.bashrc และข้าม ~/.profile ไป ส่วน ~/.profile จะทำงานเฉพาะกับ login shell เท่านั้น การใช้ Node ต่อผู้ใช้ผ่านตัวจัดการเวอร์ชัน เช่น nvm ก็ให้ผลลัพธ์เช่นเดียวกัน เป้าหมายในทั้งสองกรณีคือการทำให้ npm install -g ไม่จำเป็นต้องใช้ sudo npm ยังคงทำงานได้ตามปกติ หรือจะใช้สคริปต์ติดตั้งแบบ native ของ Anthropic ซึ่งเป็นวิธีมาตรฐานที่ระบุไว้ในเอกสารปัจจุบันก็ได้ โปรดตรวจสอบเอกสารการติดตั้งของ Anthropic ก่อนคัดลอกคำสั่งไปใช้ เนื่องจากวิธีการติดตั้งอาจมีการเปลี่ยนแปลง
รัน claude ภายใน repo เพื่อเริ่มใช้งาน การรันครั้งแรกจะนำคุณเข้าสู่ขั้นตอนการยืนยันตัวตน หากเป็นเครื่อง headless ที่ไม่มีเบราว์เซอร์ ระบบจะแสดง URL ให้คุณเปิดบนเครื่องของคุณเองและนำรหัสกลับมาใส่ใน terminal (อีกวิธีหนึ่งคือการใช้ API key ใน environment) ไม่ว่าจะใช้วิธีใด ข้อมูลประจำตัวนั้นจะถูกเก็บไว้บนเซิร์ฟเวอร์ ซึ่งนำไปสู่ส่วนที่ผู้คนมักมองข้าม
การสนทนาเรื่องขอบเขตความเสียหาย (Blast Radius)
เอเจนต์ที่มีสิทธิ์เข้าถึง shell ก็คือ shell ตัวหนึ่ง มันสามารถอ่านทุกอย่างที่ผู้ใช้ที่มันรันอยู่สามารถอ่านได้ และเขียนข้อมูลลงในทุกที่ที่ผู้ใช้นั้นมีสิทธิ์เขียน นี่ไม่ใช่การวิจารณ์ตัวเครื่องมือ แต่เป็นนิยามการทำงานของมัน และเป็นเหตุผลว่าทำไมบัญชีผู้ใช้ที่มันรันอยู่จึงมีความสำคัญมากกว่าการตั้งค่ารายบุคคลใดๆ
- ใช้ผู้ใช้เฉพาะที่ไม่มีสิทธิ์พิเศษ (Dedicated, unprivileged user) ห้ามใช้กลุ่ม
sudoและห้ามใช้ home directory ร่วมกับบัญชีผู้ใช้ของคุณเอง - ไม่มีข้อมูลรับรอง (credentials) ของระบบ production บนเครื่อง ห้ามมีไฟล์
~/.aws/credentialsที่เก็บคีย์ของระบบ production, ห้ามมีไฟล์.envที่คัดลอกมาจากระบบ production และห้ามมีรหัสผ่านฐานข้อมูลที่มีสิทธิ์เขียนในส่วนที่สำคัญ ให้ใช้ข้อมูลรับรองสำหรับ staging หรือสิทธิ์แบบอ่านอย่างเดียว (read-only) แทน - ใช้โทเค็นที่มีขอบเขตจำกัด (Scoped tokens) ใช้ GitHub token ที่กำหนดสิทธิ์อย่างละเอียดและจำกัดไว้เพียง repository เดียว หรือใช้ deploy key ในกรณีที่สิทธิ์การอ่านเพียงพอต่อการใช้งาน
Claude Code มีแฟล็กที่ใช้ข้ามการแจ้งเตือนขอสิทธิ์ทั้งหมด หากใช้งานบนแล็ปท็อปในโปรเจกต์ทดลอง นั่นเป็นดุลยพินิจของคุณ แต่หากใช้งานบนเซิร์ฟเวอร์ที่เก็บโทเค็น การทำเช่นนี้จะทำลายปราการด่านสุดท้ายที่ป้องกันไม่ให้คำสั่งที่ถูกตีความผิดพลาดกลายเป็นการทำ git push --force การแจ้งเตือนที่คุณกำลังจะข้ามไปนั้นไม่ใช่ระบบแบบทั้งหมดหรือไม่มีเลย และด้วย โหมดอัตโนมัติที่กำลังกลายเป็นค่าเริ่มต้นใหม่ คุณควรทราบว่าเซิร์ฟเวอร์ที่คุณไม่ได้เฝ้าดูตลอดเวลาควรถูกกำหนดให้อยู่ในโหมดสิทธิ์แบบใด สิ่งที่แฟล็กนี้เปลี่ยนแปลงจริงและวิธีการควบคุมเอเจนต์ที่รันด้วยแฟล็กดังกล่าว ตั้งแต่การใช้ sandbox ในตัวไปจนถึงการใช้ VPS แบบใช้แล้วทิ้ง ได้อธิบายไว้ใน การรัน Claude Code บนเซิร์ฟเวอร์อย่างปลอดภัย
Deploy key เทียบกับ SSH agent forwarding
การใช้ ssh -A เพื่อให้ git สามารถใช้คีย์บนแล็ปท็อปของคุณได้นั้นเป็นสิ่งที่น่าดึงดูดใจ แต่ต้องเข้าใจสิ่งที่ได้รับอนุญาต: การทำ agent forwarding จะเปิดเผย socket ของ SSH agent ในเครื่องของคุณให้กับกระบวนการที่รันในฐานะผู้ใช้นั้นบนเซิร์ฟเวอร์ สิ่งใดก็ตามที่รันในฐานะ agent รวมถึงตัวเอเจนต์ สามารถร้องขอให้คีย์ของคุณลงนาม (sign) สำหรับโฮสต์ใดๆ ที่มันเข้าถึงได้ ตราบเท่าที่คุณยังคงเชื่อมต่ออยู่ ซึ่งนั่นเป็นสิทธิ์ที่มากกว่าการ "ให้ git ดึงข้อมูล repository นี้เพียงแห่งเดียว" มาก
ให้สร้างคีย์ บนเซิร์ฟเวอร์ แทน ลงทะเบียนเป็น deploy key สำหรับแต่ละ repository (ให้สิทธิ์เขียนเฉพาะเมื่อเอเจนต์จำเป็นต้อง push เท่านั้น) และตั้งค่า 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
ติดตั้งโปรแกรม (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 คือคำสั่งที่ควรจำให้ขึ้นใจ โดยจะทำการแนบ (attach) เข้าสู่ session หากมีอยู่แล้ว หรือสร้างใหม่หากยังไม่มี ดังนั้นคำสั่งเดียวจึงครอบคลุมทั้งการเริ่มงานในแต่ละวันและการกลับมาทำงานต่อหลังจากหลุดการเชื่อมต่อ แนะนำให้ทำ alias ไว้ ภายใน session คำสั่ง Ctrl-b c ใช้เปิดหน้าต่างใหม่, Ctrl-b n และ Ctrl-b p ใช้สลับหน้าต่างไปมา และ Ctrl-b [ ใช้เข้าสู่โหมดคัดลอกเพื่อเลื่อนดูประวัติ (ใช้ q เพื่อออก)
สิ่งหนึ่งที่ควรทราบเกี่ยวกับ session ที่คุณไม่เคยสั่ง kill คือ agent จะส่งบทสนทนาทั้งหมดซ้ำในทุกเทิร์น ดังนั้นโปรดอ่าน สิ่งที่ Claude Code session แบบเปิดค้างไว้นานๆ ใช้โทเค็นไปกับอะไรบ้าง ก่อนที่คุณจะปล่อยให้มันทำงานค้างไว้เป็นสัปดาห์
รูปแบบความล้มเหลว
"เซสชันของฉันหายไป" tmux ls แสดงผล no server running on /tmp/tmux-1000/default กรณีนี้มักหมายความว่ากระบวนการทำงานไม่ได้อยู่ภายใน tmux คุณอาจ SSH เข้าไปแล้วรัน claude โดยตรง ทำให้เมื่อตัดการเชื่อมต่อ กระบวนการจึงถูกยุติลง ไม่สามารถกู้คืนได้ นิสัยที่ช่วยป้องกันปัญหานี้คือการรัน tmux new -A -s <project> เป็นคำสั่งแรกหลังจากล็อกอินทุกครั้ง
พาเนลหดเหลือกล่องขนาดเล็ก tmux จะปรับขนาดเซสชันตามไคลเอนต์ที่เชื่อมต่ออยู่ที่มีขนาดเล็กที่สุด ดังนั้นหากมีไคลเอนต์ค้างจากการเชื่อมต่อจากเครื่องอื่น จะทำให้หน้าจอถูกบีบอัด ให้บังคับตัดการเชื่อมต่อไคลเอนต์อื่นขณะที่คุณเชื่อมต่อด้วยคำสั่ง: tmux attach -d -t claude
การ build แสดงผล Killed เป็นคำเดียวโดยไม่มี stack trace ให้ยืนยันด้วย sudo dmesg -T | grep -i -E 'out of memory|killed process' ว่า kernel OOM killer ได้เลือกกระบวนการที่ใช้หน่วยความจำมากที่สุดไป สำหรับ Node คุณอาจพบ FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory แทน วิธีแก้ไขตามลำดับคือ: เพิ่ม swap (ตามที่ระบุข้างต้น), จำกัดการทำงานแบบขนานของ test และ compiler, เพิ่ม heap ของ Node ด้วย NODE_OPTIONS=--max-old-space-size=... หรืออัปเกรดขนาด VPS ทั้งนี้ OOM killer อาจเลือกยุติ tmux server แทนที่จะเป็นกระบวนการ build ซึ่งจะทำให้เซสชันของคุณหลุดไปด้วย หาก systemd-oomd กำลังทำงานอยู่ มันอาจยุติทั้ง user slice ซึ่งส่งผลในลักษณะเดียวกัน
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...' เกิดจากการติดตั้งแบบ global ลงใน prefix ที่เป็นของ root ให้ใช้ prefix ~/.npm-global ตามที่ระบุข้างต้น หากคุณเคยรัน sudo npm ไปแล้ว คุณอาจพบ Your cache folder contains root-owned files ให้ซ่อมแซมด้วย sudo chown -R $(id -u):$(id -g) ~/.npm
claude: command not found แต่เป็นแค่บางครั้ง การ export PATH ของคุณอยู่ใน ~/.bashrc ซึ่งอยู่ใต้เงื่อนไข "If not running interactively, don't do anything" ทำให้ non-interactive shell ข้ามการตั้งค่านี้ไป ให้ย้ายการ export ไปไว้เหนือเงื่อนไขดังกล่าวและเก็บไว้ใน ~/.bashrc ไม่ใช่ ~/.profile เนื่องจาก tmux อาจเริ่ม non-login shell ซึ่งจะอ่านเพียง ~/.bashrc และไม่แตะต้อง ~/.profile เลย
สีเพี้ยนหลังจากเชื่อมต่อ เกิดจาก TERM ไม่ตรงกัน ให้ใช้บรรทัด default-terminal ด้านบนเพื่อแก้ไข
เซสชันหายไปหลังจากรีบูต ไม่ใช่บั๊ก: tmux server เป็นเพียงกระบวนการหนึ่ง และการรีบูตจะทำให้มันสิ้นสุดลง ให้ตรวจสอบ uptime
สิ่งที่อาจเกิดปัญหาเมื่อระบบขยายตัว
โปรเจกต์จำนวนมากขึ้น การใช้ tmux หนึ่ง session ต่อหนึ่ง repository โดยตั้งชื่อตามชื่อโปรเจกต์เป็นวิธีที่ดี และใช้ tmux ls เป็นแดชบอร์ดของคุณ หากละเลยการตั้งชื่อ คุณจะได้ session ที่ชื่อ 0, 1, 2 เมื่อรันหลายโปรเจกต์พร้อมกัน ไม่จำเป็นต้องทำงานแยกส่วนกันเสมอไป เนื่องจาก session หนึ่งสามารถส่งข้อความไปยังอีก session บนเครื่องเดียวกันได้ ซึ่งมีประโยชน์เมื่อ agent ที่กำลังทำ refactor งานชิ้นใหญ่ต้องการให้ agent อีกตัวไปรันชุดทดสอบ การจัดการพอร์ตก็จะเจอปัญหาเดียวกัน เมื่อมี 6 repository ที่ต้องการใช้พอร์ต :3000 ถึงเวลาที่ต้องหยุดกำหนดพอร์ตด้วยตนเอง แล้วปล่อยให้ Traefik reverse proxy จัดการ route หลายแอปผ่าน Docker Compose โดยใช้ hostname เป็นตัวแยกแอป
จำนวนผู้ใช้มากขึ้น tmux socket จะแยกตามผู้ใช้ ดังนั้นนักพัฒนาสองคนที่อยู่บนเครื่องเดียวกันจะมี tmux server ของตนเองและไม่สามารถมองเห็น session ของกันและกันได้ การแชร์ session เดียวผ่าน socket ร่วมกันหมายความว่าทุกคนจะพิมพ์คำสั่งลงใน shell เดียวกันในฐานะผู้ใช้ Unix คนเดียวกัน ซึ่งส่งผลต่อการตรวจสอบ (audit) และสิทธิ์การเข้าถึง การแยกผู้ใช้เป็นวิธีที่น่าเบื่อแต่ถูกต้องที่สุด
งานที่รันโดยไม่มีผู้ดูแล tmux ออกแบบมาสำหรับ session แบบโต้ตอบที่คุณต้องคอยเฝ้าดู งานที่รันตามตารางเวลาโดยไม่มีคนดูควรอยู่ใน systemd unit และ timer ซึ่งจะได้รับฟีเจอร์การบันทึก log, นโยบายการ restart และการรันอัตโนมัติหลังบูตเครื่องโดยอัตโนมัติ การใช้ tmux เพื่อรันงานในลักษณะ cron เป็นสัญญาณว่างานนั้นควรเปลี่ยนไปเป็น service
ข้อควรทราบสุดท้าย ให้ bind dev server ที่ agent สตาร์ทไว้กับ 127.0.0.1 ไม่ใช่ 0.0.0.0 และเข้าถึงผ่าน SSH tunnel (ssh -L 3000:127.0.0.1:3000 agent@your-server) แทนการเปิดพอร์ตใน ufw เมื่อคุณเริ่ม forward พอร์ตจำนวนมาก หรือต้องการเข้าถึงหน้าเว็บ preview เดียวกันจากทั้งโทรศัพท์และแล็ปท็อป ให้ติดตั้ง WireGuard VPN บน VPS ไว้หน้าบริการเหล่านั้นแทน โดยให้ dev server bind กับ interface ภายใน และใช้ ufw ปฏิเสธการเชื่อมต่อทั้งหมดจาก public interface ไฟร์วอลล์จะช่วยป้องกันได้ก็ต่อเมื่อคุณหยุดเจาะรูเปิดพอร์ตบนนั้นเท่านั้น
Claude Code ไม่ใช่ทางเลือกเดียว: การรัน coding AI agent บน VPS ยังมีตัวเลือกอย่าง Aider และ Goose ให้พิจารณาด้วย
FAQ
Claude Code จะยังคงทำงานอยู่หรือไม่หากการเชื่อมต่อ SSH ของฉันหลุด?
จะทำงานต่อก็ต่อเมื่อคุณเริ่มการทำงานภายใน tmux เท่านั้น กระบวนการที่เรียกใช้โดยตรงจากเชลล์ SSH จะเป็นกระบวนการลูกของเชลล์นั้น และจะถูกยุติลงพร้อมกับ pty เมื่อการเชื่อมต่อขาดหายไป หากอยู่ภายใน tmux เชลล์จะเป็นส่วนหนึ่งของเซิร์ฟเวอร์ tmux ที่แยกออกมา (detached) ดังนั้นเอเจนต์จะยังคงทำงานต่อไปในระหว่างที่ทำภารกิจ และ tmux attach จะนำคุณกลับไปยังหน้าจอเดิมที่มีประวัติการเลื่อนดูเหมือนเดิม ทำให้ tmux new -A -s <project> เป็นคำสั่งแรกที่เรียกใช้หลังการล็อกอินทุกครั้ง แล้วปัญหานี้จะหมดไป
ฉันควรติดตั้ง CLI ด้วย sudo npm install -g หรือไม่?
ไม่ควร การใช้ global prefix ที่เป็นของ root จะทำให้คุณพบกับข้อผิดพลาด EACCES ในการติดตั้งครั้งถัดไป และทำให้ไฟล์ใน npm cache เป็นของ root ให้ตั้งค่า prefix ของ npm ไปที่ ~/.npm-global (หรือใช้ตัวจัดการเวอร์ชันอย่าง nvm) แทน จากนั้นทำการติดตั้งในฐานะผู้ใช้ agent ที่ไม่มีสิทธิ์ root และส่งออก ~/.npm-global/bin ไปยัง PATH จากไฟล์ ~/.bashrc โดยวางไว้เหนือส่วน interactive guard หากคุณเคยรัน sudo npm ไปแล้ว ให้ซ่อมแซมแคชด้วย sudo chown -R $(id -u):$(id -g) ~/.npm
การทำ agent forwarding ด้วย ssh -A ปลอดภัยหรือไม่บนเครื่องที่มีเอเจนต์ทำงานอยู่?
การทำเช่นนั้นให้สิทธิ์เกินความจำเป็นของงาน การทำ forwarding จะเปิดเผยซ็อกเก็ตของ SSH agent ในเครื่องของคุณให้กับทุกกระบวนการที่รันในฐานะผู้ใช้นั้น ดังนั้นทุกอย่างบนเครื่องสามารถร้องขอให้คีย์ของคุณลงนามสำหรับโฮสต์ใดก็ตามที่มันเข้าถึงได้ ตราบเท่าที่คุณยังคงเชื่อมต่ออยู่ ให้สร้างคีย์ ed25519 บนเซิร์ฟเวอร์และลงทะเบียนเป็น deploy key สำหรับแต่ละ repository โดยให้สิทธิ์การเขียนเฉพาะในกรณีที่เอเจนต์จำเป็นต้อง 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, จำกัดการทำงานแบบขนานของ test และ compiler, เพิ่มค่า NODE_OPTIONS=--max-old-space-size=... จากนั้นจึงขยายขนาด VPS ให้ใหญ่ขึ้น โปรดระวังว่า OOM killer อาจเลือกยุติเซิร์ฟเวอร์ tmux แทนที่จะเป็นตัวบิลด์ ซึ่งจะทำให้เซสชันทั้งหมดของคุณหลุดไปด้วย
ควรใช้ tmux หรือ systemd service?
tmux เหมาะสำหรับเซสชันแบบโต้ตอบที่คุณต้องเชื่อมต่อเข้าไป ดูการทำงาน และพิมพ์คำสั่ง ซึ่งเป็นลักษณะของเซสชันเอเจนต์ งานที่รันตามกำหนดเวลาโดยไม่มีผู้ดูแลควรอยู่ใน systemd unit และ timer ซึ่งจะมาพร้อมกับระบบบันทึก log, นโยบายการรีสตาร์ท และความสามารถในการทำงานต่อหลังการบูตเครื่อง หากคุณกำลังใช้ tmux เพื่อรันงานที่มีลักษณะเป็น cron งานนั้นควรเปลี่ยนไปเป็น service จะเหมาะสมกว่า