Claude Code на VPS у tmux: робота після розриву SSH
Запустіть Claude Code на постійному Linux VPS у tmux: сесія переживе розрив SSH. Інструкції зі встановлення, захисту та типових помилок.
Проблема в кришці ноутбука, а не в CLI
Claude Code працює на ноутбуці без проблем, доки ви його не закриєте: SSH-сеанс завершується, shell отримує SIGHUP, а агент, який уже три хвилини виконує тест, завершує роботу разом із ним. Запускайте CLI на машині, яка ніколи не переходить у режим сну, усередині terminal multiplexer, процеси якого не є дочірніми процесами вашого SSH-сеансу. У цьому і полягає весь прийом: ключову роль відіграє tmux, а не встановлення.
Ця сторінка присвячена роботі із сервером, на якому ви залишаєте агентів працювати. Якщо у вас немає Linux-сервера, який можна залишати увімкненим, ці інструкції вам не підходять. Це єдина обов’язкова передумова.
Що насправді робить tmux
Коли ви підключаєтеся через SSH, sshd запускає дочірній shell і передає йому псевдотермінал. Усе, що ви запускаєте з цього shell, є його дочірнім процесом. Якщо з’єднання переривається, kernel закриває pty, shell отримує SIGHUP і своєю чергою надсилає цей сигнал дочірнім процесам. Довготривалі процеси, запущені у foreground, завершуються.
tmux змінює цю схему. Команда tmux, яку ви вводите, є тонким клієнтом. Вона взаємодіє через unix socket із tmux server, який працює окремо від вашого термінала. Shell усередині сесії є дочірніми процесами цього сервера, а не sshd. Якщо SSH-з’єднання переривається, клієнт завершується, але server, сесія та агент, який виконує завдання, продовжують працювати. Після повторного підключення виконайте tmux attach і поверніться до того самого shell із тією самою історією прокручування. nohup також переживає розрив з’єднання, але не дає змоги повторно підключитися до сесії. Ви не зможете знову під’єднатися до TUI, запущеного у background. Claude Code є інтерактивним, тому tmux (або screen) — правильний інструмент.
Розмір сервера
CLI працює як процес Node; саме він не заповнює ресурси машини. Ресурси заповнює те, що агент запускає від вашого імені: збірка, повний набір тестів, tsc, language server або база даних у Docker. Розраховуйте ресурси для toolchain, а не для 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 швидко накопичуються. Якщо toolchain виходить за межі контейнерів і використовує повноцінні віртуальні машини, наприклад 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 sshВ Ubuntu 24.04 є важлива особливість: sshd активується через socket. Параметри автентифікації застосовуються в systemctl restart ssh, але зміна параметра прослуховування Port також потребує systemctl daemon-reload і перезапуску ssh.socket.
Далі налаштуйте firewall. Дозвольте SSH до його ввімкнення, інакше ви втратите доступ до сервера:
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enableВстановлюйте fail2ban з чітким розумінням його призначення: після вимкнення автентифікації за паролем brute force однаково не спрацює, але fail2ban не дає невдалим спробам потрапляти до журналу.
# /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. Пакет дистрибутива часто застарілий. В Ubuntu та Debian зазвичай використовують NodeSource, який надає підписаний репозиторій (без 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. Спочатку налаштуйте 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Експорт потрібно додати до ~/.bashrc, а не до ~/.profile. Розмістіть його вище за перевірку "If not running interactively, don't do anything" на початку файла: tmux може запускати не login-shell, які читають ~/.bashrc і пропускають ~/.profile; ~/.profile виконується лише для login-shell. Node для окремого користувача через менеджер версій, наприклад nvm, дає такий самий результат. У будь-якому разі мета полягає в тому, щоб для npm install -g ніколи не був потрібен sudo. npm і надалі працює коректно. Також можна скористатися штатним скриптом встановлення Anthropic — це поточний задокументований спосіб за замовчуванням. Перед вставленням перевірте документацію Anthropic щодо встановлення, оскільки способи встановлення змінюються.
Виконайте claude у репозиторії, щоб запустити його. Під час першого запуску програма проведе вас через автентифікацію. На headless-сервері немає браузера, тому процес надасть URL, який потрібно відкрити на власному комп’ютері, і код, який потрібно повернути в термінал. Інший варіант — передати API key через змінну середовища. У будь-якому разі ці облікові дані тепер зберігаються на сервері, що приводить нас до частини, яку часто пропускають.
Розмова про радіус ураження
Агент із доступом до shell має всі можливості shell. Він може читати все, до чого має доступ користувач, від імені якого запущений, і виконувати push усюди, куди цей користувач має право. Це не недолік інструмента, а його визначальна властивість. Саме тому обліковий запис, від імені якого працює агент, важливіший за будь-який окремий параметр.
- Окремий непривілейований користувач. Без групи
sudoі без домашнього каталогу, спільного з вашим обліковим записом. - Жодних production-облікових даних на сервері. Не зберігайте
~/.aws/credentialsіз production-ключами, не копіюйте.envіз production і не використовуйте пароль бази даних із правом запису до важливих даних. Надайте агенту облікові дані для staging або доступ лише для читання. - Токени з обмеженою областю дії. Використовуйте fine-grained GitHub token, обмежений одним репозиторієм, або deploy key, якщо достатньо доступу для читання.
Claude Code має flag, який повністю пропускає запити дозволів. На ноутбуці або в одноразовому проєкті це ваше рішення. На сервері, де зберігаються токени, такий режим усуває останній бар’єр між неправильно витлумаченою інструкцією та git push --force. Запити, які ви пропускаєте, також не є суто двійковим вибором. Оскільки автоматичний режим стає новим режимом за замовчуванням, варто знати, який режим дозволів слід зафіксувати для сервера, за яким ви не стежите. Що саме змінює цей flag і як обмежити агента, який працює з ним, — від вбудованого sandbox до одноразового VPS — описано в матеріалі безпечний запуск Claude Code на сервері.
Deploy key порівняно з пересиланням SSH agent
Може виникнути спокуса ssh -A, щоб git міг використовувати ключ на вашому ноутбуці. Важливо розуміти, які права це надає: пересилання agent відкриває socket локального SSH agent для процесів, що працюють на сервері від імені цього користувача. Будь-який процес, що працює як agent, зокрема агент, може попросити ваш ключ підписати запит до будь-якого доступного йому хоста, доки ви залишаєтеся підключеними. Це значно більше, ніж «дозволити git виконати pull з цього одного репозиторію».
Натомість створіть ключ на сервері, зареєструйте його як deploy key для конкретного репозиторію та надайте доступ на запис лише якщо агенту потрібно виконувати push. Також задайте git identity, щоб коміти із сервера можна було ідентифікувати:
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 claudetmux new -A -s claude — це команда, яку потрібно запам’ятати. Вона під’єднується до наявної сесії або створює її, якщо сесії немає. Тому одна команда підходить і для початку роботи, і для відновлення після розриву з’єднання. Додайте для неї alias. Усередині сесії Ctrl-b c відкриває вікно, Ctrl-b n і Ctrl-b p перемикають вікна, а Ctrl-b [ вмикає режим копіювання для прокручування виводу назад (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': kernel OOM killer завершив процес, який споживав найбільше пам’яті. У Node натомість можна побачити FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Виконуйте виправлення в такому порядку: додайте swap (як описано вище), обмежте паралелізм тестів і compiler, збільште 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, але лише іноді. Експорт PATH розташований у ~/.bashrc нижче перевірки «If not running interactively, don't do anything», тому неінтерактивні shell пропускають його. Перемістіть export вище цієї перевірки та залиште його в ~/.bashrc, а не в ~/.profile: tmux може запускати нелогінні shell, які читають ~/.bashrc і ніколи не обробляють ~/.profile.
Після підключення кольори відображаються неправильно. Це невідповідність TERM. Рядок default-terminal вище виправляє проблему.
Сесії зникають після перезавантаження. Це не помилка: tmux server є процесом, а перезавантаження його завершує. Перевірте uptime.
Що ламається зі зростанням масштабу
Більше проєктів. Створюйте окрему сесію tmux для кожного репозиторію та називайте її відповідно до репозиторію; tmux ls тоді буде вашою панеллю керування. Якщо не дотримуватися правил іменування, з’являться сесії 0, 1, 2. Коли одночасно працює кілька сесій, вони не обов’язково мають бути ізольованими, оскільки одна сесія може передавати повідомлення іншій на тому самому сервері. Це корисно, коли агент виконує тривалий рефакторинг, а другому агенту потрібно запустити тести. З портами відбувається те саме: коли шість репозиторіїв одночасно потребують :3000, час припинити призначати порти вручну та доручити reverse proxy Traefik маршрутизацію кількох застосунків у Docker Compose за іменами хостів.
Більше користувачів. Сокети tmux належать окремим користувачам, тому два розробники на одному сервері отримують власні сервери tmux і не бачать сесій один одного. Спільний доступ до однієї сесії через спільний сокет означає, що всі вводять команди в одну оболонку від імені одного Unix-користувача. Це має відповідні наслідки для аудиту та дозволів. Окремі користувачі — просте й правильне рішення.
Фонові завдання. tmux призначений для інтерактивних сесій, до яких ви підключаєтеся. Завдання, що запускаються за розкладом без нагляду, слід оформлювати як unit і timer systemd. Вони отримують журналювання, політику перезапуску та автоматичне відновлення після завантаження системи. Використання tmux для запуску завдання, призначеного для cron, свідчить, що це завдання слід оформити як сервіс.
І ще одне: прив’язуйте dev-сервери, які запускає агент, до 127.0.0.1, а не до 0.0.0.0, і підключайтеся до них через SSH-тунель (ssh -L 3000:127.0.0.1:3000 agent@your-server), замість того щоб відкривати порти в ufw. Якщо ви вже перенаправляєте пів дюжини портів або один і той самий preview потрібен одночасно телефону й ноутбуку, розмістіть перед ними self-hosted VPN WireGuard на VPS. У такому разі dev-сервери прив’язуються до приватного інтерфейсу, а ufw продовжує блокувати всі підключення через публічний інтерфейс. Firewall допомагає лише тоді, коли ви не відкриваєте в ньому нові отвори.
Claude Code — не єдиний варіант: запуск coding AI-агента на VPS також дає змогу порівняти Aider і Goose.
FAQ
Чи продовжує Claude Code працювати після розриву SSH-з’єднання?
Лише якщо ви запустили його всередині tmux. Процес, запущений безпосередньо з SSH shell, є дочірнім процесом цього shell і завершується разом із pty після розриву з’єднання. Усередині tmux shell належить від’єднаному серверу tmux, тому агент продовжує виконувати завдання, а tmux attach повертає вас до того самого scrollback. Зробіть tmux new -A -s <project> першою командою після кожного входу, і проблема зникне.
Чи потрібно встановлювати CLI за допомогою sudo npm install -g?
Ні. Глобальний prefix, власником якого є root, спричиняє помилки EACCES під час наступних встановлень і створює файли, власником яких є root, у npm cache. Встановіть prefix npm у ~/.npm-global або скористайтеся version manager, наприклад nvm, встановлюйте пакети від імені непривілейованого користувача agent і додайте ~/.npm-global/bin до PATH з ~/.bashrc вище за interactive guard. Якщо ви вже один раз виконали sudo npm, виправте cache за допомогою sudo chown -R $(id -u):$(id -g) ~/.npm.
Чи безпечне ssh -A agent forwarding на сервері, де працює агент?
Воно надає значно більше доступу, ніж потрібно для завдання. Forwarding відкриває socket вашого локального 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, обмежте паралельність тестів і компілятора, збільште NODE_OPTIONS=--max-old-space-size=..., а потім виберіть VPS із більшими ресурсами. Врахуйте, що OOM killer може завершити сервер tmux, а не процес збірки. У такому разі разом із ним завершиться весь сеанс.
tmux чи systemd service?
tmux підходить для interactive sessions, до яких ви підключаєтеся, які переглядаєте та в яких вводите команди. Саме так працює сеанс агента. Завдання, що запускається за розкладом і не потребує нагляду, слід розміщувати в systemd unit і timer. Вони забезпечують logging, restart policy та збереження роботи після завантаження системи. Якщо ви використовуєте tmux для запуску завдання, подібного до cron, це завдання має бути service.