SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Claude Code na zdalnym VPS przez tmux

Uruchom Claude Code na serwerze Linux w sesji tmux, aby uniknąć przerywania pracy agenta po rozłączeniu SSH. Poradnik instalacji, konfiguracji oraz obsługi błędów SIGHUP.

Problem stanowi pokrywa laptopa, a nie CLI

Claude Code działa poprawnie na laptopie do momentu jego zamknięcia: sesja SSH zostaje przerwana, powłoka otrzymuje sygnał SIGHUP, a agent działający od trzech minut w trakcie testu kończy pracę wraz z nią. Uruchom CLI na maszynie, która nigdy nie przechodzi w stan uśpienia, wewnątrz multipleksera terminala, którego procesy nie są procesami potomnymi sesji SSH. Na tym polega cały trik, a to tmux, a nie sama instalacja, stanowi kluczowy element utrzymujący działanie.

Niniejsza strona dotyczy obsługi maszyny, na której pozostawiasz uruchomionych agentów. Jeśli nie posiadasz serwera Linux, który może pozostać włączony, powyższe informacje nie mają zastosowania. Jest to jedyny uczciwy warunek wstępny.

Jak w rzeczywistości działa tmux

Podczas logowania przez SSH, sshd tworzy proces potomny powłoki i przydziela mu pseudo-terminal; wszystko, co uruchamiasz z poziomu tej powłoki, jest jej procesem potomnym. Zerwanie połączenia powoduje, że jądro systemu zamyka pty, powłoka otrzymuje sygnał SIGHUP i kończy działanie swoich procesów potomnych. Długotrwałe procesy działające na pierwszym planie zostają przerwane.

tmux odwraca tę zależność. Polecenie tmux, które wpisujesz, jest lekkim klientem komunikującym się przez gniazdo unixowe z serwerem tmux, który działa w oderwaniu od Twojego terminala. Powłoki wewnątrz sesji są procesami potomnymi tego serwera, a nie sshd. Przerwanie połączenia SSH powoduje zamknięcie klienta, podczas gdy serwer, sesja i trwające zadanie działają dalej. Po ponownym połączeniu, tmux attach, wracasz do tej samej powłoki z zachowaną historią przewijania. nohup również przetrwa rozłączenie, ale nie oferuje możliwości powrotu; nie można ponownie podłączyć się do procesu TUI działającego w tle. Claude Code jest interaktywny; tmux (lub screen) jest właściwym narzędziem.

Dobieranie zasobów serwera

Interfejs CLI jest procesem Node; to nie on obciąża maszynę. Obciążenie generuje to, co agent uruchamia w Twoim imieniu: proces budowania, pełny zestaw testów, tsc, serwer języka czy bazę danych w Docker. Dobierz zasoby pod kątem łańcucha narzędzi, a nie samego CLI. Dodaj swap, nawet jeśli nie planujesz z niego korzystać; zamienia on nagłe przerwanie procesu przez OOM killer na powolne budowanie:

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

Monitoruj również miejsce na dysku: repozytoria, node_modules oraz obrazy Docker szybko zajmują przestrzeń. Jeśli łańcuch narzędzi wykracza poza kontenery i obejmuje pełne maszyny wirtualne, gościa KVM lub lokalny węzeł Kubernetes, sprawdź przed podjęciem decyzji, czy plan udostępnia rozszerzenia wirtualizacji CPU. Uruchamianie zagnieżdżonej wirtualizacji na VPS jest funkcją, którą musi włączyć dostawca, a nie opcją, którą można aktywować z poziomu systemu gościa.

Użytkownik bez uprawnień root

Utwórz dedykowanego użytkownika z własnym katalogiem domowym i umieść w nim swój 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

Przetestuj logowanie z poziomu drugiego terminala przed wykonaniem kolejnych kroków, dopóki uwierzytelnianie hasłem pozostaje dostępne jako rozwiązanie awaryjne. Jeśli pojawi się błąd Permission denied (publickey), przyczyną jest zazwyczaj niewłaściwy właściciel lub uprawnienia katalogu .ssh, a nie sam klucz.

Celowo użytkownik agent nie jest dodawany do grupy sudo. W razie potrzeby instalacji pakietu systemowego, wykonuje się to ręcznie. Ta decyzja eliminuje większość scenariuszy, w których przypadkowe polecenie powłoki mogłoby uszkodzić system hosta.

Higiena SSH dla serwera działającego w trybie ciągłym

Uwierzytelnianie hasłem na maszynie wystawionej na publiczny dostęp do Internetu, która przechowuje agenta oraz kod źródłowy, stanowi nieakceptowalne 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 zaleca się utworzenie osobnego pliku konfiguracyjnego zamiast edycji głównego pliku:

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

Zweryfikuj poprawność konfiguracji i przeładuj usługę, zachowując aktywną bieżącą sesję podczas testowania nowego połączenia z drugiego terminala:

sudo sshd -t && sudo systemctl restart ssh

Ważny szczegół w Ubuntu 24.04: sshd jest aktywowany przez gniazdo (socket-activated). Ustawienia uwierzytelniania są stosowane w systemctl restart ssh, jednak zmiana portu nasłuchiwania Port wymaga również edycji systemctl daemon-reload oraz restartu ssh.socket.

Następnie skonfiguruj zaporę sieciową. Zezwól na ruch SSH przed włączeniem firewalla, aby uniknąć zablokowania dostępu do serwera:

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

Zainstaluj fail2ban, mając świadomość jego przeznaczenia: po wyłączeniu uwierzytelniania hasłem ataki typu brute force i tak nie zakończą się powodzeniem, a narzędzie to jedynie odciąży dziennik systemowy z nieudanych prób logowania.

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

Na koniec skonfiguruj automatyczne aktualizacje za pomocą sudo apt install unattended-upgrades oraz sudo dpkg-reconfigure -plow unattended-upgrades. Zwróć uwagę na interakcję z tmux: włączenie Unattended-Upgrade::Automatic-Reboot spowoduje restart serwera po aktualizacji jądra, co zamknie wszystkie sesje. Zaleca się pozostawienie tej opcji wyłączonej i wykonywanie restartów w zaplanowanym terminie, gdy żadne procesy nie są w toku. Ta sama ostrożność obowiązuje przy aktualizacji dystrybucji: przejście z Ubuntu 24.04 na 26.04 powoduje restart sshd oraz jądra, dlatego należy to wykonać w oknie serwisowym, gdy żadna sesja tmux nie przechowuje istotnych danych.

Instalacja Node.js i Claude Code w systemie Ubuntu

Claude Code jest narzędziem CLI opartym na Node.js, dlatego wymagana jest aktualna wersja środowiska Node. Pakiety dystrybucyjne często są przestarzałe; standardową metodą w systemach Ubuntu i Debian jest użycie repozytoriów NodeSource, które dostarczają podpisane pakiety (narzędzie apt-key nie jest już używane):

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

Kluczowy etap, w którym często popełniane są błędy: zainstaluj CLI jako użytkownik agent, nigdy z uprawnieniami sudo npm -g. Globalny prefiks należący do root powoduje późniejsze błędy uprawnień i pozostawia pliki w pamięci podręcznej npm z niewłaściwym właścicielem. Najpierw skieruj 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

Eksport zmiennej należy umieścić w pliku ~/.bashrc, a nie ~/.profile. Musi on znajdować się powyżej bloku sprawdzającego, czy powłoka jest interaktywna, znajdującego się na początku pliku: tmux może uruchamiać powłoki niebędące powłokami logowania, które odczytują ~/.bashrc i pomijają ~/.profile, podczas gdy ~/.profile jest wykonywane tylko dla powłok logowania. Użycie menedżera wersji Node, takiego jak nvm, pozwala osiągnąć ten sam cel; w obu przypadkach chodzi o to, aby npm install -g nigdy nie wymagało sudo. Polecenie npm działa poprawnie, można również użyć natywnego skryptu instalacyjnego Anthropic, co jest obecnie zalecaną metodą. Przed uruchomieniem poleceń sprawdź dokumentację instalacji Anthropic, ponieważ metody instalacji ulegają zmianom.

Uruchom claude wewnątrz repozytorium, aby rozpocząć pracę. Pierwsze uruchomienie przeprowadzi użytkownika przez proces uwierzytelniania; serwer bez interfejsu graficznego nie posiada przeglądarki, więc proces wyświetli adres URL, który należy otworzyć na własnym komputerze, oraz kod, który trzeba wprowadzić w terminalu. (Alternatywną metodą jest użycie klucza API w zmiennych środowiskowych). W obu przypadkach poświadczenia zostaną zapisane na serwerze, co prowadzi do etapu, który jest często pomijany.

Rozmowa o promieniu rażenia

Agent z dostępem do powłoki jest powłoką. Może odczytać wszystko, co może odczytać użytkownik, na którego koncie działa, i zapisać 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, jest ważniejsze niż jakiekolwiek pojedyncze ustawienie.

  • Dedykowany, nieuprzywilejowany użytkownik. Brak grupy sudo, brak katalogu domowego współdzielonego z własnym kontem użytkownika.
  • Brak danych uwierzytelniających środowiska produkcyjnego na serwerze. Brak pliku ~/.aws/credentials przechowującego klucze produkcyjne, brak pliku .env skopiowanego z produkcji, brak hasła do bazy danych z uprawnieniami do zapisu w istotnych zasobach. Należy nadać agentowi uprawnienia środowiska testowego lub dostęp tylko do odczytu.
  • Tokeny o ograniczonym zakresie. Precyzyjnie określony token GitHub ograniczony do jednego repozytorium; klucz deploy key, gdy wystarczy dostęp do odczytu.

Claude Code udostępnia flagę, która całkowicie pomija monity o uprawnienia. Na laptopie, w przypadku projektu tymczasowego, jest to decyzja użytkownika. Na serwerze przechowującym tokeny usuwa ona ostatnią barierę między błędnie zinterpretowaną instrukcją a poleceniem git push --force. Monity, które są pomijane, nie działają na zasadzie „wszystko albo nic”, a w związku z wprowadzeniem trybu automatycznego jako domyślnego warto wiedzieć, w jakim trybie uprawnień powinien pracować serwer, który nie jest stale monitorowany. Informacje o tym, co faktycznie zmienia ta flaga oraz jak ograniczyć działanie agenta, który jej używa – od wbudowanego środowiska typu sandbox po jednorazowy serwer VPS – znajdują się w sekcji bezpieczne uruchamianie Claude Code na serwerze.

Deploy key a przekazywanie SSH agenta

Kuszące jest użycie ssh -A, aby git mógł korzystać z klucza znajdującego się na laptopie. Należy zrozumieć, co to oznacza: przekazywanie agenta udostępnia gniazdo lokalnego agenta SSH procesom działającym jako dany użytkownik na serwerze. Każdy proces działający jako agent, w tym agent, może zażądać od klucza podpisu dla dowolnego hosta, do którego ma dostęp, tak długo, jak trwa połączenie. Jest to znacznie szerszy zakres uprawnień niż „pozwól gitowi pobrać to jedno repozytorium”.

Zamiast tego należy wygenerować klucz na serwerze, zarejestrować go jako klucz deploy key dla konkretnego repozytorium (dostęp do zapisu tylko wtedy, gdy agent musi wypychać zmiany) oraz ustawić tożsamość git, aby commity z serwera 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"

Przepływ pracy w tmux

Zainstaluj narzędzie (sudo apt install tmux), a następnie utwórz minimalny plik ~/.tmux.conf:

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

Cztery polecenia wystarczają do codziennej pracy:

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

Polecenie tmux new -A -s claude jest najważniejsze do zapamiętania; dołącza do istniejącej sesji lub tworzy nową, jeśli nie istnieje, więc jedno polecenie obsługuje zarówno rozpoczęcie pracy, jak i powrót po rozłączeniu. Warto utworzyć dla niego alias. Wewnątrz sesji Ctrl-b c otwiera nowe okno, Ctrl-b n oraz Ctrl-b p przełączają się między nimi, a Ctrl-b [ uruchamia tryb kopiowania umożliwiający przewijanie (q zamyka ten tryb).

Warto pamiętać o sesjach, których nigdy nie kończysz: agent przesyła całą historię konwersacji przy każdym zapytaniu, dlatego przeczytaj na co zużywa tokeny długotrwała sesja Claude Code przed pozostawieniem jej uruchomionej przez tydzień.

Tryby awarii

"Moja sesja zniknęła." tmux ls wypisuje no server running on /tmp/tmux-1000/default. Prawie zawsze oznacza to, że proces nigdy nie działał wewnątrz tmux, użytkownik zalogował się przez SSH, uruchomił claude bezpośrednio, a rozłączenie zakończyło proces. Nie ma czego odzyskiwać. Nawyk, który temu zapobiega: tmux new -A -s <project> to pierwsze polecenie po każdym zalogowaniu.

Panel zmniejsza się do małego okna. tmux dostosowuje rozmiar sesji do najmniejszego podłączonego klienta, więc nieaktywny klient wciąż podłączony z innej maszyny ściska wyświetlany obraz. Wymuś rozłączenie innych podczas podłączania: tmux attach -d -t claude.

Kompilacja wypisuje Killed. Jedno słowo, brak śladu stosu. Potwierdź za pomocą sudo dmesg -T | grep -i -E 'out of memory|killed process', mechanizm OOM killer jądra wybrał największy proces. W przypadku Node można zamiast tego zobaczyć FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Rozwiązania w kolejności: dodaj swap (powyżej), ogranicz równoległość testów i kompilacji, zwiększ stertę Node za pomocą NODE_OPTIONS=--max-old-space-size=... lub zwiększ zasoby VPS. OOM killer może również wybrać serwer tmux zamiast procesu budowania, zamykając sesję wraz z nim; jeśli systemd-oomd jest uruchomiony, może zabić cały wycinek użytkownika z tym samym skutkiem.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Instalacja globalna w prefiksie należącym do root. Użyj prefiksu ~/.npm-global opisanego powyżej. Jeśli w pewnym momencie uruchomiono sudo npm, można również zobaczyć Your cache folder contains root-owned files, napraw za pomocą sudo chown -R $(id -u):$(id -g) ~/.npm.

claude: command not found, ale tylko czasami. Eksport PATH znajduje się w ~/.bashrc poniżej warunku "Jeśli nie działa interaktywnie, nie rób nic", więc powłoki nieinteraktywne go pomijają. Przenieś eksport powyżej tego warunku i zachowaj go w ~/.bashrc, a nie ~/.profile: tmux może uruchamiać powłoki niebędące powłokami logowania, które odczytują ~/.bashrc i nigdy nie dotykają ~/.profile.

Zniekształcone kolory po podłączeniu. Niezgodność TERM, linia default-terminal powyżej stanowi rozwiązanie.

Sesje znikają po restarcie. To nie jest błąd: serwer tmux jest procesem, a restart go kończy. Sprawdź uptime.

Co przestaje działać wraz ze wzrostem skali

Więcej projektów. Jedna sesja tmux na repozytorium, nazwana zgodnie z jego nazwą; tmux ls pełni wtedy rolę pulpitu nawigacyjnego. Pominięcie dyscypliny nazewnictwa prowadzi do powstania sesji 0, 1, 2. Gdy uruchomionych jest kilka sesji jednocześnie, nie muszą one pracować w izolacji, ponieważ jedna sesja może przekazać wiadomość do drugiej na tym samym serwerze, co jest przydatne, gdy agent wykonujący długotrwały refaktoring potrzebuje drugiego agenta do uruchomienia testów. Porty rozrastają się w ten sam sposób; sześć repozytoriów wymagających :3000 to moment, w którym należy przestać przypisywać je ręcznie i pozwolić, aby reverse proxy Traefik w Docker Compose zajęło się kierowaniem ruchu na podstawie nazwy hosta.

Więcej osób. Gniazda (sockets) tmux są przypisane do użytkownika, więc dwóch programistów na tym samym serwerze otrzymuje własne serwery tmux i nie widzi sesji innych osób. Współdzielenie jednej sesji przez wspólne gniazdo oznacza, że wszyscy wpisują polecenia w tę samą powłokę jako ten sam użytkownik Unix, co niesie ze sobą konsekwencje w zakresie audytu i uprawnień. Oddzielni użytkownicy to nudne, ale poprawne rozwiązanie.

Praca w tle. tmux służy do sesji interaktywnych, do których użytkownik się podłącza. Zadania uruchamiane według harmonogramu bez nadzoru powinny znajdować się w jednostce (unit) i timerze systemd, gdzie automatycznie otrzymują logowanie, politykę restartu i odporność na restart systemu. Sięganie po tmux w celu uruchomienia zadania typu cron jest sygnałem, że zadanie powinno stać się usługą.

Ostatnia uwaga: serwery deweloperskie uruchamiane przez agenta należy wiązać 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 przekierowujesz już pół tuzina portów lub gdy telefon i laptop wymagają dostępu do tego samego podglądu, umieść przed nimi samodzielnie hostowany VPN WireGuard na VPS: serwery deweloperskie wiążą się z prywatnym interfejsem, a ufw odrzuca wszystko z interfejsu publicznego. Firewall pomaga tylko wtedy, gdy przestaniesz robić w nim dziury.

Claude Code nie jest jedynym wyborem: uruchomienie agenta AI do kodowania na VPS pozwala również rozważyć Aider oraz Goose.

FAQ

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

Tylko jeśli uruchomiono go wewnątrz tmux. Proces uruchomiony bezpośrednio z powłoki SSH jest procesem potomnym tej powłoki i kończy się wraz z pty w momencie zerwania połączenia. Wewnątrz tmux powłoka należy do odłączonego serwera tmux, więc agent kontynuuje pracę w trakcie zadania, a tmux attach przywraca użytkownika do tego samego bufora przewijania. Ustawienie tmux new -A -s <project> jako pierwszego polecenia po każdym logowaniu rozwiązuje ten problem.

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

Nie. Globalny prefiks należący do root powoduje błędy EACCES przy późniejszych instalacjach oraz 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, takiego jak nvm), instalować jako użytkownik bez uprawnień agent oraz wyeksportować ~/.npm-global/bin do PATH z pliku ~/.bashrc, powyżej warunku sprawdzającego interaktywność. Jeśli polecenie sudo npm zostało już raz uruchomione, należy naprawić pamięć podręczną za pomocą sudo chown -R $(id -u):$(id -g) ~/.npm.

Czy przekazywanie agenta ssh -A jest bezpieczne na serwerze z uruchomionym agentem?

Przyznaje ono znacznie więcej uprawnień, niż wymaga tego zadanie. Przekazywanie udostępnia gniazdo lokalnego agenta SSH każdemu procesowi działającemu jako dany użytkownik, więc każdy element na serwerze może zażądać od klucza podpisu dla dowolnego hosta, do którego ma dostęp, tak długo, jak trwa połączenie. 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 faktycznie musi wykonać operację push.

Dlaczego kompilacja wyświetla tylko Killed?

Jedno słowo bez śladu stosu (stack trace) oznacza działanie mechanizmu OOM killer jądra systemu. Należy to potwierdzić za pomocą sudo dmesg -T | grep -i -E 'out of memory|killed process'; w przypadku Node można zobaczyć JavaScript heap out of memory. Należy zastosować poprawki w następującej kolejności: dodać plik wymiany (swapfile), ograniczyć równoległość testów i kompilatora, zwiększyć NODE_OPTIONS=--max-old-space-size=..., a następnie zwiększyć zasoby VPS. Należy pamiętać, że OOM killer może wybrać serwer tmux zamiast procesu kompilacji, co spowoduje zamknięcie całej sesji.

tmux czy usługa systemd?

tmux jest odpowiedni dla sesji interaktywnych, do których użytkownik się podłącza, obserwuje je i wpisuje polecenia – dokładnie tak działa sesja agenta. Zadania uruchamiane według harmonogramu bez nadzoru powinny być obsługiwane przez jednostkę (unit) i timer systemd, gdzie logowanie, polityka restartu i przetrwanie po restarcie systemu są dostępne w standardzie. Jeśli użytkownik sięga po tmux w celu uruchomienia zadania typu cron, zadanie to powinno być usługą.