SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

วิธีรัน 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 เองเป็น binary แบบ native เพียงไฟล์เดียว จึงไม่ใช่สิ่งที่ทำให้พื้นที่จัดเก็บข้อมูลเต็ม สิ่งที่ทำให้เครื่องเต็มคือสิ่งที่ 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

อย่าใส่ key exchange ไว้ในไฟล์นั้น เว้นแต่มีเหตุผลเฉพาะเจาะจง: บรรทัด KexAlgorithms ที่คัดลอกจาก hardening guide รุ่นเก่ามักกำหนดให้ใช้เฉพาะ classical algorithms ซึ่งทำให้ ssh client รุ่นใหม่แสดง คำเตือนว่าการเชื่อมต่อไม่ได้ใช้ post-quantum key exchange

ตรวจสอบความถูกต้องและโหลดการตั้งค่าใหม่ โดยให้คงเซสชันปัจจุบันไว้ในขณะที่คุณทดสอบเซสชัน ใหม่ จากเทอร์มินัลที่สอง:

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

เส้นทางผ่าน npm จำเป็นต้องใช้ Node.js เวอร์ชัน 22 ขึ้นไป (ปัจจุบันแพ็กเกจจะดึง native binary มาใช้งานซึ่งไม่ได้รันบน Node โดยตรง แต่ npm ยังคงตรวจสอบเวอร์ชันของ engine อยู่ และ toolchain ของตัว agent เองมักต้องการ Node เวอร์ชันปัจจุบันเช่นกัน) แพ็กเกจใน distro มักจะล้าสมัย NodeSource จึงเป็นเส้นทางปกติบน Ubuntu และ Debian ซึ่งมีการจัดส่ง repo ที่ลงนามไว้ (ไม่มี apt-key ซึ่งถูกเลิกใช้ใน Ubuntu 24.04 และถูกถอดออกจาก Debian 13 แล้ว):

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 และต้องวางไว้ เหนือ เงื่อนไข "If not running interactively, don't do anything" ที่อยู่ใกล้ส่วนบนของไฟล์ tmux pane โดยค่าเริ่มต้นจะรัน login shell: tmux จะปล่อยให้ default-command ว่างเปล่า ซึ่งบอกให้มันเริ่ม default-shell ในฐานะ login shell และ bash ที่เป็น login shell จะอ่าน ~/.profile ซึ่งบน Ubuntu จะทำการ source ~/.bashrc ส่วน non-login shell เช่น หากคุณตั้งค่า default-command ด้วยตนเอง จะอ่าน ~/.bashrc โดยตรง ดังนั้น ~/.bashrc จึงครอบคลุมทั้งสองกรณี ในขณะที่ ~/.profile เพียงอย่างเดียวจะพลาดกรณีที่สอง ข้อควรระวัง: bash จะข้าม ~/.profile หากมี ~/.bash_profile หรือ ~/.bash_login อยู่ ดังนั้นหากคุณสร้างไฟล์เหล่านี้ไว้ ให้ตั้งค่าให้มัน source ~/.bashrc ด้วย การใช้ Node ต่อผู้ใช้ผ่านตัวจัดการเวอร์ชัน เช่น nvm ก็ให้ผลลัพธ์เดียวกัน เป้าหมายไม่ว่าจะใช้วิธีใดคือการที่ npm install -g ไม่จำเป็นต้องใช้ sudo เลย npm ยังคงทำงานได้ตามปกติ แต่เอกสารของ Anthropic ระบุว่าสคริปต์ติดตั้งแบบ native เป็นวิธีที่แนะนำ และการใช้ repository แบบ apt, dnf และ apk ที่ลงนามไว้ก็เป็นอีกทางเลือกหนึ่ง โปรดตรวจสอบเอกสารการติดตั้งของ Anthropic ก่อนคัดลอกคำสั่งไปใช้ เนื่องจากวิธีการติดตั้งอาจมีการเปลี่ยนแปลง

รัน claude ภายใน repository เพื่อเริ่มใช้งาน การรันครั้งแรกจะนำคุณเข้าสู่ขั้นตอนการยืนยันตัวตน หากเป็นเครื่อง headless ที่ไม่มีเบราว์เซอร์ ขั้นตอนดังกล่าวจะให้ URL เพื่อให้คุณเปิดบนเครื่องของคุณเองและนำรหัสกลับมาใส่ใน terminal (การใช้ API key ใน environment เป็นอีกทางเลือกหนึ่ง) ไม่ว่าจะใช้วิธีใด ข้อมูลประจำตัวนั้นจะถูกเก็บไว้บนเซิร์ฟเวอร์ ซึ่งนำเราไปสู่ส่วนที่ผู้คนมักมองข้าม

การสนทนาเรื่องขอบเขตความเสียหาย (Blast Radius)

เอเจนต์ที่มีสิทธิ์เข้าถึงเชลล์ก็คือเชลล์ตัวหนึ่ง มันสามารถอ่านทุกอย่างที่ผู้ใช้ที่รันมันอยู่สามารถอ่านได้ และเขียนข้อมูลลงในทุกที่ที่ผู้ใช้นั้นสามารถเขียนได้ นี่ไม่ใช่การวิจารณ์ตัวเครื่องมือ แต่เป็นนิยามการทำงานของมัน และเป็นเหตุผลว่าทำไมบัญชีผู้ใช้ที่รันเอเจนต์จึงมีความสำคัญมากกว่าการตั้งค่ารายบุคคลใดๆ

  • ใช้ผู้ใช้เฉพาะที่ไม่มีสิทธิ์พิเศษ (Dedicated, unprivileged user): ห้ามใช้กลุ่ม sudo และห้ามใช้โฮมไดเรกทอรีร่วมกับบัญชีผู้ใช้ของคุณเอง
  • ไม่มีข้อมูลรับรอง (Credentials) ของระบบโปรดักชันบนเครื่อง: ห้ามมี ~/.aws/credentials ที่เก็บคีย์ของระบบโปรดักชัน ห้ามมี .env ที่คัดลอกมาจากระบบโปรดักชัน และห้ามมีรหัสผ่านฐานข้อมูลที่มีสิทธิ์เขียนข้อมูลในส่วนที่สำคัญ ให้ใช้ข้อมูลรับรองสำหรับระบบ staging หรือสิทธิ์แบบอ่านอย่างเดียว (read-only) แทน
  • โทเค็นที่มีขอบเขตจำกัด (Scoped tokens): ใช้ GitHub token ที่กำหนดสิทธิ์อย่างละเอียดและจำกัดไว้เพียงหนึ่ง repository หรือใช้ deploy key ในกรณีที่สิทธิ์แบบอ่านอย่างเดียวเพียงพอต่อการใช้งาน

Claude Code มีแฟล็ก --dangerously-skip-permissions ซึ่งจะข้ามการแจ้งเตือนขอสิทธิ์เกือบทั้งหมด หากใช้งานบนแล็ปท็อปในโปรเจกต์ทดลอง คุณสามารถตัดสินใจเองได้ แต่หากใช้งานบนเซิร์ฟเวอร์ที่เก็บโทเค็นสำคัญ แฟล็กนี้จะทำลายปราการด่านสุดท้ายระหว่างคำสั่งที่ตีความผิดพลาดกับ git push --force การแจ้งเตือนที่คุณกำลังจะข้ามไปนั้นไม่ใช่การตัดสินใจแบบ "ทั้งหมดหรือไม่มีเลย" และด้วย โหมดอัตโนมัติที่เป็นค่าเริ่มต้นในปัจจุบัน (ตั้งแต่ Claude Code v2.1.283 เป็นต้นไป) คุณควรทราบว่าเซิร์ฟเวอร์ที่คุณไม่ได้เฝ้าดูอยู่ตลอดเวลานั้นควรถูกกำหนดให้ใช้โหมดสิทธิ์แบบใด สิ่งที่แฟล็กนี้เปลี่ยนแปลงจริงและวิธีการควบคุมเอเจนต์ที่รันด้วยแฟล็กดังกล่าว ตั้งแต่การใช้ sandbox ในตัวไปจนถึงการใช้ VPS แบบใช้แล้วทิ้ง ได้อธิบายไว้ใน การรัน Claude Code บนเซิร์ฟเวอร์อย่างปลอดภัย

Deploy key เทียบกับ SSH agent forwarding

การใช้ ssh -A เพื่อให้ git สามารถใช้คีย์บนแล็ปท็อปของคุณได้นั้นเป็นสิ่งที่น่าดึงดูดใจ แต่ต้องเข้าใจว่าสิ่งนั้นมอบสิทธิ์อะไรให้บ้าง: การทำ agent forwarding จะเปิดเผยซ็อกเก็ตของ SSH agent ในเครื่องของคุณให้กับกระบวนการที่รันด้วยผู้ใช้นั้นบนเซิร์ฟเวอร์ ทุกสิ่งที่รันในฐานะ agent รวมถึงตัวเอเจนต์ สามารถร้องขอให้คีย์ของคุณลงนาม (sign) สำหรับโฮสต์ใดก็ตามที่มันเข้าถึงได้ ตราบเท่าที่คุณยังเชื่อมต่ออยู่ ซึ่งนั่นเป็นสิทธิ์ที่มากกว่าแค่ "ให้ git pull เฉพาะ 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 claude

tmux 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> เป็นคำสั่งแรกหลังจากล็อกอินทุกครั้ง

บานหน้าต่าง (pane) หดเหลือขนาดเล็กมาก ตัวเลือก window-size เป็นตัวกำหนดว่าไคลเอนต์ที่เชื่อมต่ออยู่ตัวใดจะเป็นตัวกำหนดขนาด ตั้งแต่ tmux 3.1 เป็นต้นมา (Ubuntu 24.04 ใช้เวอร์ชัน 3.4) ค่าเริ่มต้นคือ latest ซึ่งหมายถึงไคลเอนต์ที่มีการใช้งานล่าสุด แต่หากไฟล์คอนฟิกมีการตั้งค่า window-size smallest ขนาดจะถูกปรับตามไคลเอนต์ที่เชื่อมต่ออยู่ที่มีขนาดเล็กที่สุด ทำให้ไคลเอนต์เก่าที่ยังค้างอยู่จากเครื่องอื่นบีบขนาดหน้าจอของคุณ ให้บังคับตัดการเชื่อมต่อไคลเอนต์อื่นขณะที่คุณเชื่อมต่อด้วยคำสั่ง: 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 ให้ใช้ ~/.npm-global prefix ตามที่ระบุไว้ข้างต้น หากคุณเคยรัน sudo npm ไปแล้ว คุณอาจพบ Your cache folder contains root-owned files ให้ซ่อมแซมด้วย sudo chown -R $(id -u):$(id -g) ~/.npm

claude: command not found แต่เป็นแค่บางครั้ง การ export PATH ของคุณอยู่ใน ~/.bashrc ซึ่งอยู่ใต้เงื่อนไข "หากไม่ได้รันแบบโต้ตอบ (interactive) ไม่ต้องทำอะไร" ทำให้ shell ที่ไม่ใช่แบบโต้ตอบข้ามการตั้งค่านี้ไป ให้ย้ายการ export ไปไว้เหนือเงื่อนไขดังกล่าวและเก็บไว้ใน ~/.bashrc: โดยปกติ tmux pane จะเป็น login shell ที่เข้าถึง ~/.bashrc ผ่าน ~/.profile ของ Ubuntu และ shell ที่ไม่ใช่ login shell จะอ่าน ~/.bashrc โดยตรง ดังนั้นไฟล์เดียวจึงครอบคลุมทั้งสองกรณี

สีเพี้ยนหลังจากเชื่อมต่อ เกิดจาก 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 จะเหมาะสมกว่า