SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor

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 є одним нативним бінарним файлом і не він заповнює ресурси машини. Ресурси споживає те, що агент запускає від вашого імені: збірка, повний набір тестів, 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

Не додавайте обмін ключами до цього файлу без конкретної причини: рядок KexAlgorithms, скопійований зі старого посібника з hardening, часто обмежує алгоритми лише класичними. Через це новий ssh client зрештою виводить попередження, що з’єднання не використовує постквантовий обмін ключами.

Перевірте конфігурацію та перезавантажте службу. Під час перевірки нового підключення з другого термінала не закривайте поточний сеанс:

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

Для встановлення через npm потрібен Node.js 22 або новішої версії. (Тепер пакет містить native binary, який не запускається в Node, але npm усе одно перевіряє версію engine, а власний toolchain агента зазвичай також потребує актуальної версії Node.) Пакет дистрибутива часто застарілий; в Ubuntu і Debian зазвичай використовують NodeSource. Він надає підписаний репозиторій (без 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. Глобальний prefix, власником якого є root, згодом спричиняє помилки доступу й залишає файли, власником яких є root, у npm cache. Спочатку вкажіть 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

Експорт потрібно додати в ~/.bashrc, вище за перевірку "If not running interactively, don't do anything" на початку файлу. Стандартна панель tmux запускає login shell: tmux залишає default-command порожньою, щоб вона запустила default-shell як login shell, а login bash читає ~/.profile, який в Ubuntu підключає ~/.bashrc. Shell без входу, наприклад якщо ви самостійно встановили default-command, читає ~/.bashrc безпосередньо. Тому ~/.bashrc охоплює обидва випадки, тоді як лише ~/.profile не врахує другий. Є один нюанс: bash пропускає ~/.profile, якщо існує ~/.bash_profile або ~/.bash_login. Тому, якщо ви створили один із них, додайте туди також підключення ~/.bashrc. Node для окремого користувача через version manager, наприклад nvm, дає той самий результат. У будь-якому разі мета полягає в тому, щоб для npm install -g ніколи не був потрібен sudo. npm і надалі працює коректно, але в документації Anthropic native install script позначено як рекомендований спосіб. Інший варіант — підписані apt-, dnf- і apk-репозиторії. Перед вставленням команд перевірте документацію Anthropic щодо встановлення, оскільки способи встановлення змінюються.

Виконайте claude у репозиторії, щоб запустити його. Під час першого запуску система проведе вас через автентифікацію. На headless-сервері немає браузера, тому процес надасть URL, який потрібно відкрити на власному комп’ютері, і код, який потрібно повернути в термінал. (Інший варіант — передати API key через environment.) У будь-якому разі ці облікові дані тепер зберігаються на сервері. Це підводить нас до частини, яку часто пропускають.

Обговорення радіуса ураження

Агент із доступом до shell має можливості shell. Він може читати все, що доступно користувачу, від імені якого він запущений, і виконувати push туди, куди цей користувач має доступ на запис. Це не недолік інструмента, а його визначення. Саме тому обліковий запис, від імені якого запускається агент, важливіший за будь-яке окреме налаштування.

  • Окремий непривілейований користувач. Без групи sudo і без домашнього каталогу, спільного з вашим обліковим записом.
  • Жодних production-облікових даних на сервері. Не зберігайте ~/.aws/credentials із production-ключами, не копіюйте .env із production і не використовуйте пароль бази даних із правом запису до важливих даних. Надайте агенту облікові дані staging або доступ лише для читання.
  • Токени з обмеженою областю дії. Використовуйте fine-grained GitHub token, обмежений одним репозиторієм, або deploy key, якщо достатньо доступу для читання.

Claude Code має прапорець --dangerously-skip-permissions, який пропускає майже всі запити на підтвердження дозволів. На ноутбуці або у тимчасовому проєкті це ваш вибір. На сервері, де зберігаються токени, цей прапорець усуває останній бар’єр між неправильно інтерпретованою інструкцією та git push --force. Пропуски запитів не є безумовним режимом, а з автоматичним режимом, який тепер є режимом запуску за замовчуванням (починаючи з Claude Code v2.1.283) важливо знати, до якого режиму дозволів слід прив’язати сервер, за яким ви не стежите. Пояснення того, що саме змінює цей прапорець і як ізолювати агента, запущеного з ним, від вбудованого 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 claude

tmux 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> має бути першою командою після кожного входу.

Панель стискається до маленького вікна. Опція window-size визначає, який підключений клієнт задає розмір. Починаючи з tmux 3.1 (в Ubuntu 24.04 постачається версія 3.4), типовим значенням є latest — клієнт із найновішою активністю. Але конфігурація зі значенням window-size smallest встановлює розмір за найменшим підключеним клієнтом, тому старий клієнт, який досі підключений з іншої машини, стискає відображення. Примусово від’єднайте інші клієнти під час підключення: 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 (як описано вище), обмежте паралелізм тестів і компілятора, збільште 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", тому неінтерактивні оболонки його пропускають. Перемістіть експорт вище цієї перевірки та залиште його в ~/.bashrc: типова панель tmux запускає login shell, яка доходить до ~/.bashrc через ~/.profile в Ubuntu, а будь-яка non-login shell безпосередньо читає ~/.bashrc. Тому одного цього файла достатньо для обох випадків.

Спотворені кольори після підключення. Причина — невідповідність 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.