SSD Nodes Learn
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-07-24

Claude Code na VPS z tmux

Dowiedz się, jak uniknąć błędów SIGHUP po zamknięciu laptopa. Dowiedz się, jak uruchomić Claude Code w tmux na Linux VPS, aby zachować sesje agenta.

Przyczyną jest pokrywa laptopa, a nie CLI

Claude Code działa poprawnie na laptopie do momentu zamknięcia pokrywy: sesja SSH zostaje przerwana, powłoka otrzymuje sygnał SIGHUP, a agent przerywa test po trzech minutach. Należy uruchamiać CLI na urządzeniu, które nie przechodzi w tryb uśpienia, wewnątrz multipleksera terminali, którego procesy nie są procesami potomnymi sesji SSH. To jest kluczowe rozwiązanie — to tmux, a nie instalacja, stanowi fundament rozwiązania.

Strona ta dotyczy obsługi serwera, na którym działają agenci. Jeśli nie posiadasz serwera Linux, który może pracować w trybie ciągłym, powyższe informacje nie mają zastosowania. Jest to jedyny wymagany warunek wstępny.

Funkcja tmux

Podczas połączenia SSH, sshd tworzy proces fork shell i przydziela mu pseudo-terminal; wszystkie procesy uruchomione z tego shella są jego procesami potomnymi. Po zerwaniu połączenia jądro systemu zamyka pty, shell otrzymuje sygnał SIGHUP, co powoduje wysłanie sygnału SIGHUP do wszystkich procesów potomnych. Procesy działające w trybie foreground zostają przerwane.

tmux odwraca strukturę własności procesów. Wpisana komenda tmux działa jako lekki klient komunikujący się przez unix socket z tmux server, który działa niezależnie od terminala. Sesje shell wewnątrz sesji są procesami potomnymi serwera, a nie sshd. Po przerwaniu połączenia SSH klient zostaje zamknięty, natomiast serwer, sesja oraz agent kontynuują pracę. Po ponownym połączeniu przez tmux attach użytkownik powraca do tego samego shella z zachowanym buforem przewijania. nohup również działa po zerwaniu połączenia, ale nie umożliwia ponownego połączenia — nie można dołączyć ponownie do działającego w tle interfejsu TUI. Claude Code jest interaktywny; tmux (lub screen) jest właściwym narzędziem.

Dobór zasobów maszyny

CLI to proces Node; nie stanowi on głównego obciążenia maszyny. Zasoby zużywa procesy uruchamiane w Twoim imieniu: procesy budowania, pełne zestawy testów, tsc, language server lub baza danych w Docker. Należy dobrać zasoby pod cały toolchain, a nie pod samo CLI. Należy dodać swap, nawet jeśli nie będzie on używany — zapobiega to gwałtownym błędom OOM (Out Of Memory) i zamienia je na wolniejsze działanie procesu.

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

Należy monitorować również zajętość dysku: repozytoria, node_modules oraz obrazy Docker szybko zajmują miejsce. Jeśli toolchain wykracza poza kontenery i obejmuje pełne maszyny wirtualne — np. gościa KVM lub lokalny węzeł Kubernetes — należy sprawdzić, czy wybrany plan zapewnia wsparcie dla rozszerzeń wirtualizacji CPU przed dokonaniem zakupu. Uruchamianie wirtualizacji zagnieżdżonej na VPS jest funkcją aktywowaną przez dostawcę, a nie opcją, którą można włączyć z poziomu gościa.

Użytkownik bez uprawnień root

Należy utworzyć dedykowanego użytkownika z własnym katalogiem home oraz umieścić w nim klucz publiczny:

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

Świadomie nie dodaje się agent do grupy sudo. W przypadku konieczności użycia pakietu systemowego, należy go zainstalować. Ta decyzja eliminuje większość metod, poprzez które błędna komenda powłoki może uszkodzić hosta.

Higiena SSH dla pracujących serwerów

Używanie uwierzytelniania hasłem na maszynie podłączonej do publicznego internetu, która przechowuje agenta i kod źródłowy, generuje niepotrzebne ryzyko. Należy je wyłączyć. W systemach Ubuntu 24.04 oraz Debian 13, /etc/ssh/sshd_config zawiera /etc/ssh/sshd_config.d/*.conf, dlatego zamiast edytować główną konfigurację, należy utworzyć nowy plik:

# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no

Należy zweryfikować konfigurację i przeładować usługę — podczas testowania nowej sesji z drugiego terminala należy pozostawić obecną sesję otwartą:

sudo sshd -t && sudo systemctl restart ssh

Szczegół techniczny w Ubuntu 24.04: usługa sshd jest aktywowana przez socket. Ustawienia uwierzytelniania mają zastosowanie w systemctl restart ssh, jednak zmiana nasłuchiwania na Port wymaga również systemctl daemon-reload oraz restartu ssh.socket.

Następnie należy skonfigurować firewall. Należy zezwolić na ruch SSH przed włączeniem usługi, aby uniknąć utraty dostępu do serwera:

sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

Należy zainstalować fail2ban, mając świadomość korzyści: po wyłączeniu uwierzytelniania hasłem ataki brute force i tak nie będą skuteczne — usługa ta zapobiega zaśmiecaniu dziennika zdarzeń nieudanymi próbami logowania.

# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h

Na koniec należy skonfigurować automatyczne aktualizacje za pomocą sudo apt install unattended-upgrades oraz sudo dpkg-reconfigure -plow unattended-upgrades. Należy zwrócić uwagę na interakcję z tmux: po włączeniu Unattended-Upgrade::Automatic-Reboot aktualizacja jądra spowoduje restart systemu, co przerwie wszystkie aktywne sesje. Jeśli wyłączono automatyczne aktualizacje, restart należy przeprowadzać ręcznie w wybranym terminie, gdy żadne procesy nie są w trakcie wykonywania.

Instalacja Node.js i Claude Code na Ubuntu

Claude Code to interfejs CLI dla Node.js, zatem wymagana jest aktualna wersja Node.js. Pakiety z repozytoriów dystrybucji są często nieaktualne; standardową metodą na Ubuntu i Debian jest NodeSource, który udostępnia podpisane repozytorium (brak apt-key — to narzędzie zostało usunięte):

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version

Kluczowy błąd podczas instalacji: CLI należy zainstalować jako użytkownik agent, nigdy jako sudo npm -g. Globalny prefiks należący do użytkownika root powoduje błędy uprawnień oraz pozostawia pliki należące do root w pamięci podręcznej npm. Należy najpierw ustawić prefiks npm na katalog domowy użytkownika:

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

Polecenie export należy umieścić w ~/.bashrc, a nie w ~/.profile. Powinno ono znajdować się powyżej instrukcji warunkowej "If not running interactively, don't do anything" znajdującej się na początku pliku. Narzędzie tmux może uruchamiać powłoki typu non-login, które odczytują ~/.bashrc i pomijają ~/.profile~/.profile działa tylko w powłokach typu login. Użycie Node.js dla konkretnego użytkownika za pomocą menedżera wersji, takiego jak nvm, daje ten sam efekt; celem jest sytuacja, w której npm install -g nie wymaga sudo. npm nadal działa poprawnie, można również użyć natywnych skryptów instalacyjnych Anthropic, które są obecnie domyślną metodą. Przed wklejeniem poleceń należy sprawdzić dokumentację instalacyjną Anthropic — metody instalacji ulegają zmianom.

Aby uruchomić narzędzie, należy wykonać claude wewnątrz repozytorium. Podczas pierwszego uruchomienia nastąpi proces uwierzytelniania. Serwer typu headless nie posiada przeglądarki, dlatego proces wymaga otwarcia podanego adresu URL na własnym komputerze i wprowadzenia otrzymanego kodu w terminalu. (Alternatywną metodą jest ustawienie klucza API w zmiennych środowiskowych). W obu przypadkach dane uwierzytelniające zostaną zapisane na serwerze — co prowadzi do etapu, który jest często pomijany.

Koncepcja blast radius

Agent z dostępem do powłoki (shell) jest powłoką. Może odczytać wszystko, co może odczytać użytkownik, na którym jest uruchomiony, oraz wysłać dane wszędzie tam, gdzie ten użytkownik ma uprawnienia do zapisu. Nie jest to krytyka narzędzia, lecz jego definicja — dlatego konto, na którym działa agent, ma większe znaczenie niż jakakolwiek pojedyncza konfiguracja.

  • Dedykowany, nieuprzywilejowany użytkownik. Brak grupy sudo oraz brak katalogu home współdzielonego z własnym kontem.
  • Brak poświadczeń produkcyjnych na maszynie. Brak ~/.aws/credentials przechowującego klucze produkcyjne, brak .env skopiowanych z produkcji oraz brak haseł do baz danych z uprawnieniami do zapisu w krytycznych zasobach. Agentowi należy nadać poświadczenia do środowiska staging lub z samym dostępem do odczytu.
  • Tokeny o ograniczonym zakresie. Token GitHub o wysokim stopniu szczegółowości, ograniczony do jednego repozytorium; klucz deploy key, gdy wystarczy dostęp do odczytu.

Claude Code posiada flagę, która całkowicie pomija zapytania o uprawnienia. Na laptopie lub w przypadku tymczasowego projektu decyzja należy do użytkownika. Na serwerze przechowującym tokeny flaga ta usuwa ostatnią barierę między błędną instrukcją a git push --force. Informacje o tym, co faktycznie zmienia ta flaga oraz jak ograniczyć agenta uruchamianego z tą flagą — od wbudowanego piaskownicy (sandbox) po tymczasowy VPS — znajdują się w bezpiecznym uruchamianiu Claude Code na serwerze.

Deploy key vs SSH agent forwarding

Można ulec pokusie użycia ssh -A, aby git mógł korzystać z klucza na laptopie. Należy jednak zrozumieć skutki tej operacji: agent forwarding udostępnia gniazdo (socket) lokalnego agenta SSH procesom uruchomionym na maszynie jako dany użytkownik. Wszystko, co działa jako agent — w tym agent — może poprosić o podpisanie żądania za pomocą klucza dla każdego hosta, do którego można dotrzeć, dopóki połączenie jest aktywne. To znacznie więcej niż tylko "pozwolenie git na pobranie tego jednego repozytorium".

Zamiast tego należy wygenerować klucz na serwerze, zarejestrować go jako per-repository deploy key (tylko uprawnienia do zapisu, jeśli agent musi wykonywać operacje push) oraz ustawić tożsamość git, aby commity z maszyny były rozpoznawalne:

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"

Workflow tmux

Należy zainstalować program (sudo apt install tmux), a następnie skonfigurować minimalny ~/.tmux.conf:

set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"

Cztery polecenia obejmują codzienne użytkowanie:

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

Należy zapamiętać tmux new -A -s claude — polecenie to dołącza do istniejącej sesji lub tworzy nową, jeśli nie istnieje. Dzięki temu jedno polecenie służy zarówno do rozpoczynania pracy, jak i wznowienia sesji po rozłączeniu. Zaleca się utworzenie aliasu. Wewnątrz sesji polecenie Ctrl-b c otwiera okno, Ctrl-b n oraz Ctrl-b p służą do przełączania okien, a Ctrl-b [ uruchamia tryb kopiowania w celu przewijania treści (q wyłącza ten tryb).

W przypadku sesji, których nie jest się wyłącza, należy pamiętać o jednej kwestii: agent przesyła całą historię rozmowy przy każdym kroku. Przed pozostawieniem działającej sesji na tydzień należy przeczytać na co wydawane są tokeny w długotrwałych sesjach Claude Code.

Failure modes

"My session is gone." tmux ls wypisuje no server running on /tmp/tmux-1000/default. Oznacza to, że proces nigdy nie został uruchomiony wewnątrz tmux — po zalogowaniu przez SSH uruchomiono claude bezpośrednio, a rozłączenie przerwało działanie procesu. Nie ma możliwości odzyskania sesji. Aby temu zapobiec: tmux new -A -s <project> musi być pierwszą komendą po każdym zalogowaniu.

Panele zmniejszają się do bardzo małego rozmiaru. tmux dopasowuje rozmiar sesji do najmniejszego podłączonego klienta. Nieaktywny klient podłączony z innego urządzenia powoduje zwężenie wyświetlania. Aby wymusić odłączenie pozostałych klientów podczas podłączania się, należy użyć: tmux attach -d -t claude.

Build wypisuje Killed. Pojawia się pojedyncze słowo bez stosu wywołań (stack trace). Należy to potwierdzić za pomocą sudo dmesg -T | grep -i -E 'out of memory|killed process' — kernel OOM killer wybrał proces o największym zużyciu zasobów. W przypadku Node może wystąpić błąd FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Rozwiązania (w kolejności): dodać swap (opisany powyżej), ograniczyć testy i równoległość kompilatora, zwiększyć heap w Node za pomocą NODE_OPTIONS=--max-old-space-size=... lub zwiększyć parametry VPS. OOM killer może również wybrać tmux server zamiast procesu budowania, co spowoduje utratę sesji; jeśli działa systemd-oomd, może on zabić cały user slice z tym samym skutkiem.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Globalna instalacja w prefiksie należącym do użytkownika root. Należy użyć prefiksu ~/.npm-global opisanego powyżej. Jeśli wykonano już sudo npm, może wystąpić błąd Your cache folder contains root-owned files — naprawić za pomocą sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found — występuje tylko sporadycznie. Eksport PATH znajduje się w ~/.bashrc poniżej instrukcji "If not running interactively, don't do anything", przez co powłoki nieinteraktywne go pomijają. Należy przenieść eksport powyżej tej instrukcji i umieścić go w ~/.bashrc zamiast w ~/.profile: tmux może uruchamiać powłoki typu non-login, które odczytują ~/.bashrc, ale nie odczytują ~/.profile.

Błędne kolory po podłączeniu. Występuje niezgodność TERM — rozwiązaniem jest linia default-terminal opisana powyżej.

Sesje znikają po restarcie systemu. To nie jest błąd: tmux server jest procesem, a restart systemu kończy jego działanie. Sprawdzić uptime.

Co ulega awarii wraz ze wzrostem skali

Więcej projektów. Należy tworzyć jedną sesję tmux na każde repozytorium, nadając jej nazwę zgodną z nazwą repozytorium; tmux ls staje się wtedy panelem sterowania. Brak dyscypliny w nazewnictwie prowadzi do powstawania sesji 0, 1, 2. Podobnie rozrastają się porty — gdy sześć repozytoriów wymaga :3000, należy zaprzestać ręcznego przydzielania portów i pozwolić, aby reverse proxy Traefik przekierowywał ruch wielu aplikacji w Docker Compose zarządzał tym za pomocą nazw hostów.

Więcej osób. Gniazda tmux są przypisane do użytkownika, więc dwaj programiści na tej samej maszynie otrzymują własne serwery tmux i nie widzą wzajemnie swoich sesji. Współdzielenie jednej sesji poprzez wspólne gniazdo oznacza, że wszyscy wpisują polecenia w tej samej powłoce jako ten sam użytkownik Unix, co niesie ze sobą skutki w zakresie audytu i uprawnień. Rozwiązaniem jest utworzenie oddzielnych kont użytkowników.

Praca w tle. tmux służy do interaktywnych sesji, do których można się podłączyć. Zadania uruchamiane harmonogramowo, bez nadzoru, powinny być zdefiniowane jako jednostki systemd oraz timery, co zapewnia im logowanie, politykę restartów oraz automatyczne uruchamianie po starcie systemu. Wykorzystywanie tmux do zadań typu cron oznacza, że zadanie to powinno być usługą.

Uwaga końcowa: należy konfigurować serwery deweloperskie tak, aby agent wiązał je z 127.0.0.1, a nie 0.0.0.0, i uzyskiwać do nich dostęp przez tunel SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server), zamiast otwierać porty w ufw. Gdy liczba przekierowywanych portów wzrośnie lub gdy wiele urządzeń wymaga dostępu do tego samego podglądu, należy zamiast tego wdrożyć self-hosted WireGuard VPN na VPS: serwery deweloperskie są wtedy powiązane z interfejsem prywatnym, a ufw nadal blokuje cały ruch z interfejsu publicznego. Firewall jest skuteczny tylko wtedy, gdy nie tworzy się w nim wyjątków.

Claude Code nie jest jedynym wyborem: uruchamianie agenta AI do kodowania na VPS obejmuje również Aider oraz Goose.

FAQ

Czy Claude Code nadal działa po zerwaniu połączenia SSH?

Tylko jeśli proces został uruchomiony wewnątrz tmux. Proces uruchomiony bezpośrednio w powłoce SSH jest procesem potomnym tej powłoki i zostaje zamknięty wraz z pty po utracie połączenia. Wewnątrz tmux powłoka należy do odłączonego serwera tmux, więc agent kontynuuje zadanie, a tmux attach przywraca użytkownika do tego samego bufora przewijania. Należy ustawić tmux new -A -s <project> jako pierwszą komendę po każdym logowaniu, aby wyeliminować ten problem.

Czy należy instalować CLI za pomocą sudo npm install -g?

Nie. Globalny prefiks należący do użytkownika root powoduje błędy EACCES podczas późniejszych instalacji oraz tworzy pliki należące do root w pamięci podręcznej npm. Należy ustawić prefiks npm na ~/.npm-global (lub użyć menedżera wersji, np. nvm), instalować jako nieuprzywilejowany użytkownik agent, a następnie wyeksportować ~/.npm-global/bin do PATH z ~/.bashrc, powyżej mechanizmu zabezpieczającego interakcję. Jeśli komenda sudo npm została już raz wykonana, należy naprawić pamięć podręczną za pomocą sudo chown -R $(id -u):$(id -g) ~/.npm.

Czy agent forwarding dla ssh -A jest bezpieczny na maszynie z uruchomionym agentem?

To rozwiązanie nadaje uprawnienia większe niż wymagane do wykonania zadania. Przekazywanie (forwarding) udostępnia gniazdo (socket) lokalnego agenta SSH wszystkim procesom uruchomionym jako dany użytkownik. Oznacza to, że każdy proces na maszynie może użyć klucza do podpisania żądania dla dowolnego hosta, do którego ma dostęp, dopóki połączenie jest aktywne. Należy wygenerować klucz ed25519 na serwerze i zarejestrować go jako klucz wdrożeniowy (deploy key) dla konkretnego repozytorium, z dostępem do zapisu tylko wtedy, gdy agent musi wykonać operację push.

Dlaczego proces budowania wyświetla tylko Killed?

Jedno słowo bez śladu stosu (stack trace) oznacza działanie mechanizmu kernel OOM killer. Należy to potwierdzić za pomocą sudo dmesg -T | grep -i -E 'out of memory|killed process'; w środowisku Node może pojawić się komunikat JavaScript heap out of memory. Należy stosować rozwiązania w podanej kolejności: dodać plik swap, ograniczyć równoległość testów i kompilatora, zwiększyć NODE_OPTIONS=--max-old-space-size=..., a następnie zwiększyć parametry VPS. Należy pamiętać, że OOM killer może zakończyć proces serwera tmux zamiast procesu budowania, co spowoduje utratę całej sesji.

tmux czy usługa systemd?

tmux jest odpowiedni dla sesji interaktywnych, do których można się podłączyć, monitorować je i wprowadzać dane, co jest charakterystyczne dla sesji agenta. Zadania wykonywane harmonogramowo, bez nadzoru użytkownika, powinny być uruchamiane jako jednostki (units) i timery systemd, co zapewnia automatyczne logowanie, politykę restartów oraz uruchamianie po starcie systemu. Jeśli tmux jest używany do uruchamiania zadań typu cron, zadanie to powinno zostać skonfigurowane jako usługa.