הרצת Claude Code על שרת VPS מרוחק בעזרת tmux
למדו כיצד להריץ את Claude Code על שרת Linux מרוחק בתוך tmux כדי למנוע קריסת סוכנים בעת ניתוק SSH. המדריך כולל הגדרות אבטחה, התקנה ופתרון תקלות נפוצות בסביבות עבודה מרוחקות.
הבעיה היא מכסה המחשב הנייד, לא ה-CLI
Claude Code פועל היטב על המחשב הנייד שלכם עד לרגע סגירתו: סשן ה-SSH נקטע, ה-shell מקבל SIGHUP, והסוכן, שנמצא שלוש דקות בתוך הרצת בדיקה, קורס יחד איתו. הריצו את ה-CLI על מכונה שלעולם אינה נכנסת למצב שינה, בתוך terminal multiplexer שתהליכיו אינם צאצאים של סשן ה-SSH שלכם. זהו כל הטריק, ו-tmux, ולא ההתקנה עצמה, הוא החלק שנושא בעומס.
זהו דף העוסק בתפעול מכונה שעליה אתם משאירים סוכנים רצים. אם אין ברשותכם שרת Linux שניתן להשאיר דולק, שום דבר מזה אינו רלוונטי. זוהי דרישת הקדם הכנה היחידה.
מה tmux עושה בפועל
כאשר אתם מתחברים ב-SSH, ה-sshd מבצע fork למעטפת (shell) ומקצה לה pseudo-terminal; כל תהליך שאתם מריצים מהמעטפת הזו הוא תהליך בן שלה. אם החיבור מתנתק, ה-kernel מפרק את ה-pty, המעטפת מקבלת SIGHUP, והיא בתורה שולחת אות ניתוק לכל התהליכים הבנים שלה. תהליכים שרצים בחזית (foreground) וזמן ריצתם ארוך, מסתיימים.
tmux הופך את יחסי הבעלות. פקודת ה-tmux שאתם מקלידים היא לקוח רזה המתקשר דרך unix socket אל שרת tmux שרץ במנותק מהטרמינל שלכם. המעטפות בתוך הסשן הן בנות של השרת הזה, ולא של ה-sshd. אם תנתקו את חיבור ה-SSH, הלקוח ייסגר, אך השרת, הסשן והסוכן שמבצע את המשימה ימשיכו לרוץ. התחברו מחדש, הריצו tmux attach, ותחזרו לאותה מעטפת עם אותו היסטוריית גלילה (scrollback). גם nohup שורד ניתוק, אך הוא לא מציע דרך חזרה פנימה; לא ניתן להתחבר מחדש ל-TUI שרץ ברקע. Claude Code הוא כלי אינטראקטיבי; tmux (או screen) הוא הכלי הנכון למשימה זו.
הערכת גודל השרת
ה־CLI הוא תהליך Node; הוא אינו הגורם שממלא את משאבי המכונה. מה שממלא את המכונה הוא כל מה שהסוכן מריץ עבורך: תהליך build, חבילת בדיקות מלאה, tsc, שרת שפה, או מסד נתונים בתוך Docker. בצעו הערכת גודל לפי ה־toolchain, לא לפי ה־CLI. הוסיפו swap גם אם אתם מתכננים לא להשתמש בו לעולם; הוא הופך אירוע של OOM kill אלים לבנייה איטית בלבד:
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 מצטברים במהירות. ואם ה־toolchain חורג מעבר למכולות אל מכונות וירטואליות מלאות, כגון KVM guest או צומת Kubernetes מקומי, ודאו שהתוכנית חושפת הרחבות וירטואליזציה של המעבד לפני שתתחייבו, כיוון ש־הרצת וירטואליזציה מקוננת ב-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בדקו את ההתחברות מטרמינל שני לפני שתמשיכו הלאה, כל עוד אימות באמצעות סיסמה זמין כגיבוי; אם מתקבלת השגיאה Permission denied (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 sshדגש חשוב ב-Ubuntu 24.04: השירות sshd מופעל באמצעות socket. הגדרות האימות חלות ב-systemctl restart ssh, אך שינוי בפורט ההאזנה Port דורש גם את systemctl daemon-reload ואתחול של ssh.socket.
לאחר מכן, הגדירו את ה-firewall. אפשרו SSH לפני הפעלת ה-firewall, אחרת תינעלו מחוץ לשרת:
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 הוא כלי CLI מבוסס Node, לכן נדרשת גרסה עדכנית של Node. החבילות במאגרים הרשמיים של ההפצה נוטות להיות מיושנות; השימוש ב-NodeSource הוא הדרך המקובלת ב-Ubuntu וב-Debian, והוא מספק מאגר חתום (ללא 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. קידומת (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 עשוי להפעיל shell שאינו login shell, אשר קורא את ~/.bashrc ומדלג על ~/.profile, בעוד ש-~/.profile רץ רק עבור login shells. שימוש ב-Node מנוהל לפי משתמש באמצעות מנהל גרסאות כגון nvm משיג את אותה תוצאה; המטרה בשני המקרים היא ש-npm install -g לעולם לא יזדקק ל-sudo. הפקודה npm תמשיך לעבוד כראוי, או לחלופין השתמשו בסקריפט ההתקנה המקורי של Anthropic, שהיא הדרך המתועדת כברירת מחדל כיום. בדקו את תיעוד ההתקנה של Anthropic לפני ההדבקה, שכן שיטות ההתקנה משתנות.
הריצו את claude בתוך מאגר (repo) כדי להפעיל אותו. ההרצה הראשונה תדריך אתכם בתהליך האימות; בשרת ללא ממשק גרפי (headless) אין דפדפן, לכן התהליך יספק לכם כתובת URL לפתיחה במחשב האישי שלכם וקוד להזנה חזרה בטרמינל. (שימוש ב-API key בסביבה הוא הדרך השנייה). כך או כך, פרטי הזיהוי הללו נשמרים כעת על השרת, מה שמביא אותנו לחלק שאנשים נוטים לדלג עליו.
שיחת ה-blast radius
סוכן בעל גישת shell הוא shell לכל דבר. הוא יכול לקרוא כל מה שהמשתמש שמריץ אותו יכול לקרוא, ולכתוב לכל מקום שאותו משתמש יכול לכתוב אליו. זו אינה ביקורת על הכלי, אלא הגדרתו הבסיסית, וזו הסיבה שהחשבון שתחתיו הוא רץ חשוב יותר מכל הגדרה פרטנית.
- משתמש ייעודי ונטול הרשאות. ללא קבוצת
sudo, וללא ספריית בית משותפת עם החשבון האישי שלכם. - ללא פרטי הזדהות של סביבת הייצור על השרת. ללא
~/.aws/credentialsהמכיל מפתחות production, ללא.envשהועתקו מסביבת הייצור, וללא סיסמת מסד נתונים בעלת הרשאות כתיבה לכל דבר קריטי. העניקו לסוכן פרטי הזדהות של סביבת staging או גישת קריאה בלבד. - אסימונים (tokens) מוגבלים. אסימון GitHub בעל הרשאות ממוקדות המוגבל למאגר אחד בלבד; או מפתח deploy כאשר גישת קריאה מספיקה.
Claude Code מגיע עם דגל המדלג לחלוטין על בקשות האישור שלו. על מחשב נייד, בפרויקט זמני, זו החלטה שלכם. על שרת המכיל אסימונים, הדבר מסיר את המחסום האחרון שבין הוראה שפורשה לא נכון לבין git push --force. הבקשות שאתם מדלגים עליהן אינן בהכרח "הכל או כלום", ועם הגעת מצב auto כברירת מחדל חדשה, כדאי לדעת על איזה מצב הרשאות יש לקבע שרת שאינכם מנטרים באופן פעיל. מה הדגל משנה בפועל, וכיצד להכיל סוכן שרץ איתו – החל מה-sandbox המובנה ועד ל-VPS חד-פעמי – מפורט ב-הרצת Claude Code בצורה בטוחה על שרת.
מפתח Deploy לעומת SSH agent forwarding
מפתה להשתמש ב-ssh -A כדי ש-git יוכל להשתמש במפתח שעל המחשב הנייד שלכם. הבינו מה זה מעניק: agent forwarding חושף את ה-socket של ה-SSH agent המקומי שלכם לתהליכים שרצים כמשתמש ההוא על השרת. כל דבר שרץ כ-agent, כולל הסוכן, יכול לבקש מהמפתח שלכם לחתום עבור כל מארח שהוא יכול להגיע אליו, כל עוד אתם מחוברים. זה הרבה יותר מאשר "לאפשר ל-git למשוך מאגר אחד".
צרו מפתח על השרת במקום זאת, רשמו אותו כמפתח deploy לכל מאגר (עם הרשאות כתיבה רק אם הסוכן צריך לבצע push), והגדירו זהות git כך שניתן יהיה לזהות commits שמגיעים מהשרת:
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"ארבע פקודות מכסות את השימוש היומיומי:
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 היא החשובה ביותר לשינון; היא מחברת אתכם לסשן קיים אם הוא רץ, או יוצרת אותו אם לא, כך שפקודה אחת מספיקה גם לתחילת יום עבודה וגם להתחברות מחדש לאחר ניתוק. צרו לה Alias. בתוך סשן, Ctrl-b c פותח חלון חדש, Ctrl-b n ו-Ctrl-b p מדלגים ביניהם, ו-Ctrl-b [ מפעיל את מצב העתקה (copy mode) כדי לגלול אחורה (q יוצא ממנו).
דבר אחד שחשוב לדעת על סשנים שלעולם אינכם סוגרים: הסוכן שולח מחדש את כל היסטוריית השיחה בכל פנייה, לכן קראו על מה צורך את הטוקנים בסשן ארוך של Claude Code לפני שתשאירו סשן פעיל במשך שבוע.
מצבי כשל
"הסשן שלי נעלם." 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. מילה אחת, ללא stack trace. אשרו זאת עם sudo dmesg -T | grep -i -E 'out of memory|killed process'; ה-OOM killer של ה-kernel בחר בתהליך הגדול ביותר. מ-Node ייתכן שתראו במקום זאת FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. פתרונות, לפי סדר: הוסיפו swap (לעיל), הגבילו את המקביליות של הבדיקות והקומפיילר, הגדילו את ה-heap של Node עם NODE_OPTIONS=--max-old-space-size=..., או שדרגו את ה-VPS. ה-OOM killer עשוי לבחור גם ב-tmux server במקום בתהליך הבנייה, מה שיגרום לאובדן הסשן; אם systemd-oomd רץ, הוא עלול להרוג את כל ה-user slice עם תוצאה דומה.
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. התקנה גלובלית לתוך 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", לכן shells לא אינטראקטיביים מדלגים עליו. העבירו את ה-export מעל להגנה זו ושמרו אותו ב-~/.bashrc, לא ב-~/.profile: tmux עשוי להפעיל shells שאינם login shells, אשר קוראים את ~/.bashrc ולעולם לא ניגשים ל-~/.profile.
צבעים משובשים לאחר התחברות. אי-התאמה ב-TERM; השורה default-terminal לעיל היא הפתרון.
סשנים נעלמים לאחר אתחול. לא מדובר בבאג: ה-tmux server הוא תהליך, ואתחול מסיים אותו. בדקו את uptime.
מה משתבש ככל שהסביבה גדלה
פרויקטים נוספים. מומלץ להשתמש בסשן tmux אחד לכל מאגר (repo), עם שם תואם; tmux ls ישמש אז כלוח הבקרה שלכם. אם תוותרו על משמעת מתן שמות, תמצאו את עצמכם עם סשנים בשמות 0, 1, 2. ברגע שרצים כמה סשנים במקביל, הם לא חייבים לעבוד בבידוד, שכן סשן אחד יכול להעביר הודעה לאחר על אותו שרת, דבר שימושי כאשר הסוכן שמבצע refactor ארוך זקוק לסוכן נוסף שיריץ את הבדיקות. פורטים מתרבים באותו אופן; כאשר שישה מאגרים דורשים כולם את :3000, זה הזמן להפסיק להקצות אותם ידנית ולתת ל-reverse proxy מסוג Traefik עם Docker Compose לבצע את הניתוב לפי שם המתחם (hostname).
משתמשים נוספים. ה-sockets של tmux הם ברמת המשתמש, לכן שני מפתחים על אותו שרת יקבלו כל אחד שרת tmux משלו ולא יוכלו לראות את הסשנים של האחר. שיתוף סשן יחיד דרך socket משותף אומר שכולם מקלידים לאותו shell בתור אותו משתמש Unix, עם השלכות על בקרת הגישה וההרשאות. שימוש במשתמשים נפרדים הוא הפתרון המשעמם אך הנכון.
עבודה ללא השגחה. tmux מיועד לסשנים אינטראקטיביים שמתחברים אליהם. משימות שרצות לפי לוח זמנים ללא השגחה צריכות להיות מוגדרות כ-systemd unit ו-timer, שם הן מקבלות רישום לוגים (logging), מדיניות הפעלה מחדש ושרידות לאחר אתחול כברירת מחדל. שימוש ב-tmux להרצת משימה בסגנון cron הוא סימן לכך שהמשימה צריכה להפוך לשירות (service).
הערה אחרונה: הגדירו שרתי פיתוח שהסוכן מפעיל להאזין ל-127.0.0.1, לא ל-0.0.0.0, וגשו אליהם דרך מנהרת SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server) במקום לפתוח פורטים ב-ufw. ברגע שאתם מעבירים חצי תריסר פורטים, או כאשר טלפון ומחשב נייד זקוקים שניהם לאותה תצוגה מקדימה, הקימו שרת WireGuard VPN עצמאי על ה-VPS לפני השרתים: שרתי הפיתוח יאזינו לממשק פרטי, ו-ufw ימשיך לחסום הכל מהממשק הציבורי. ה-firewall עוזר רק אם מפסיקים ליצור בו חורים.
Claude Code אינו הבחירה היחידה: הרצת סוכן AI לכתיבת קוד על VPS מאפשרת לשקול גם את Aider ו-Goose.
FAQ
האם Claude Code ממשיך לרוץ לאחר ניתוק חיבור ה-SSH שלי?
רק אם הפעלת אותו בתוך tmux. תהליך שמופעל ישירות ממעטפת ה-SSH הוא תהליך בן של אותה מעטפת, והוא נסגר יחד עם ה-pty כאשר החיבור מתנתק. בתוך tmux, המעטפת שייכת לשרת ה-tmux המנותק, לכן הסוכן ממשיך לעבוד באמצע המשימה ו-tmux attach מחזיר אותך לאותו היסטוריית גלילה. הפוך את tmux new -A -s <project> לפקודה הראשונה לאחר כל התחברות והבעיה תיפתר.
האם עליי להתקין את ה-CLI בעזרת sudo npm install -g?
לא. קידומת גלובלית בבעלות root גורמת לשגיאות EACCES בהתקנות עתידיות ולקבצים בבעלות root במטמון של npm. הגדר את הקידומת של npm ל-~/.npm-global (או השתמש במנהל גרסאות כמו nvm), בצע את ההתקנה כמשתמש לא מורשה agent, וייצא את ~/.npm-global/bin לתוך PATH מתוך ~/.bashrc, מעל שומר הסף האינטראקטיבי. אם כבר הרצת את sudo npm פעם אחת, תקן את המטמון בעזרת sudo chown -R $(id -u):$(id -g) ~/.npm.
האם העברת סוכן (agent forwarding) של ssh -A בטוחה בשרת שמריץ סוכן?
היא מעניקה הרבה יותר הרשאות ממה שהמשימה דורשת. העברה חושפת את ה-socket של סוכן ה-SSH המקומי שלך לכל תהליך שרץ כמשתמש זה, כך שכל דבר בשרת יכול לבקש מהמפתח שלך לחתום עבור כל מארח אליו הוא יכול להגיע, כל עוד אתה מחובר. צור מפתח ed25519 בשרת ורשום אותו כמפתח פריסה (deploy key) לכל מאגר, עם הרשאות כתיבה רק אם הסוכן באמת נדרש לבצע push.
מדוע ה-build שלי מדפיס רק Killed?
מילה אחת ללא stack trace מעידה על ה-OOM killer של ה-kernel. אשר זאת בעזרת 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 במקום ב-build, ובכך להפיל את כל הסשן שלך יחד איתו.
tmux או שירות systemd?
tmux מתאים לסשנים אינטראקטיביים שאליהם אתה מתחבר, צופה ומקליד, וזה בדיוק מה שסשן סוכן הוא. עבודה שרצה לפי לוח זמנים ללא צופה שייכת ליחידת systemd ו-timer, שם רישום לוגים, מדיניות הפעלה מחדש ושרידות לאחר אתחול מגיעים כברירת מחדל. אם אתה פונה ל-tmux כדי להריץ משימה במבנה של cron, המשימה צריכה להיות שירות.