Claude Code draaien op een remote VPS met tmux
Voorkom dat uw Claude Code sessies afbreken bij een verbroken SSH verbinding. Leer hoe u de CLI in een tmux sessie op een Linux VPS installeert voor continue agent processen.
Het probleem is de laptopklep, niet de CLI
Claude Code werkt prima op uw laptop totdat u deze sluit: de SSH-sessie wordt beëindigd, de shell ontvangt een SIGHUP en de agent die drie minuten bezig is met een testrun, sterft daarmee af. Draai de CLI op een machine die nooit in de slaapstand gaat, binnen een terminal multiplexer waarvan de processen geen kindprocessen zijn van uw SSH-sessie. Dat is de hele truc; tmux, en niet de installatie, is het dragende onderdeel.
Dit is een pagina over het beheren van een machine waarop u agents laat draaien. Als u geen Linux-server heeft die u aan kunt laten staan, is niets hiervan van toepassing. Dat is de enige eerlijke voorwaarde.
Wat tmux daadwerkelijk doet
Wanneer u inlogt via SSH, fork-t sshd een shell en wijst deze een pseudo-terminal toe; alles wat u vanuit die shell start, is een kindproces daarvan. Verbreekt u de verbinding, dan breekt de kernel de pty af, ontvangt de shell een SIGHUP en beëindigt deze op zijn beurt de onderliggende processen. Langlopende voorgrondprocessen worden hierdoor beëindigd.
tmux draait dit eigenaarschap om. Het tmux-commando dat u invoert, is een dunne client die via een unix socket communiceert met een tmux server die losgekoppeld van uw terminal draait. Shells binnen een sessie zijn kindprocessen van die server, niet van sshd. Beëindigt u de SSH-verbinding, dan verdwijnt de client, terwijl de server, de sessie en de actieve agent blijven draaien. Maak opnieuw verbinding, voer tmux attach uit, en u bent terug in dezelfde shell met dezelfde scrollback-geschiedenis. nohup overleeft een verbindingverlies eveneens, maar biedt geen mogelijkheid om terug te keren; u kunt niet opnieuw koppelen aan een TUI die naar de achtergrond is verplaatst. Claude Code is interactief; tmux (of screen) is hiervoor het juiste hulpmiddel.
De server dimensioneren
De CLI is een Node-proces; dit is niet wat de machine vult. Wat de machine vult, is alles wat de agent namens u uitvoert: een build, een volledige testsuite, tsc, een language server of een database in Docker. Dimensioneer op basis van de toolchain, niet op basis van de CLI. Voeg swap toe, zelfs als u van plan bent deze nooit te gebruiken; het verandert een harde OOM-kill in een trage build:
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/fstabHoud ook de schijfruimte in de gaten: repositories, node_modules en Docker-images hopen zich snel op. En als de toolchain verder gaat dan containers en volledige virtuele machines gebruikt, zoals een KVM-guest of een lokale Kubernetes-node, controleer dan of het abonnement CPU-virtualisatie-extensies aanbiedt voordat u zich vastlegt. Geneste virtualisatie draaien op een VPS is namelijk iets wat de provider voor u moet inschakelen; u kunt dit niet zelf activeren vanuit de guest.
Een non-root gebruiker als eerste stap
Maak een toegewezen gebruiker aan met een eigen home-directory en plaats uw publieke sleutel:
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_keysTest de login vanuit een tweede terminal voordat u verdergaat, terwijl wachtwoordauthenticatie nog als fallback beschikbaar is. Als u de melding Permission denied (publickey) krijgt, ligt de oorzaak meestal bij de eigendomsrechten of de modus van de directory .ssh, en niet bij de sleutel zelf.
De gebruiker agent is bewust niet toegevoegd aan de groep sudo. Als een systeempakket nodig is, installeert u dit handmatig. Deze beslissing elimineert de meeste manieren waarop een onbedoeld shell-commando de host kan beschadigen.
SSH-hygiëne voor een server die continu draait
Wachtwoordauthenticatie op een machine die constant verbonden is met het openbare internet, en die een agent en uw broncode bevat, vormt een onacceptabel risico. Schakel dit uit. Op Ubuntu 24.04 en Debian 13 bevat /etc/ssh/sshd_config de map /etc/ssh/sshd_config.d/*.conf. Plaats daarom een bestand in deze map in plaats van het hoofdconfiguratiebestand te bewerken:
# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin noValideer de configuratie en herlaad de service. Houd uw huidige sessie open terwijl u een nieuwe sessie test vanuit een tweede terminal:
sudo sshd -t && sudo systemctl restart sshEen nuance op Ubuntu 24.04: sshd wordt geactiveerd via een socket. Authenticatie-instellingen worden toegepast op systemctl restart ssh, maar een wijziging aan de luisterende Port vereist ook systemctl daemon-reload en een herstart van ssh.socket.
Configureer vervolgens de firewall. Sta SSH toe voordat u de firewall inschakelt, anders sluit u uzelf buiten:
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enableInstalleer fail2ban met een helder beeld van het nut ervan: zodra wachtwoordauthenticatie is uitgeschakeld, kan een brute-force-aanval toch niet slagen. Het houdt mislukte inlogpogingen echter wel uit uw logbestanden.
# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1hPatch het systeem ten slotte automatisch met sudo apt install unattended-upgrades en sudo dpkg-reconfigure -plow unattended-upgrades. Let op de interactie met tmux: schakel Unattended-Upgrade::Automatic-Reboot in en een kernel-update zal de server herstarten, waarbij alle sessies worden beëindigd. Laat dit uitgeschakeld en herstart op uw eigen tijdstip, wanneer er geen processen actief zijn. Dezelfde voorzichtigheid geldt voor een release-upgrade: het migreren van de server van Ubuntu 24.04 naar 26.04 herstart sshd en de kernel. Voer dit daarom uit in een venster waarin geen tmux-sessie werk bevat dat u wilt behouden.
Node.js en Claude Code installeren op Ubuntu
Claude Code is een Node CLI, dus u heeft een actuele versie van Node nodig. Het pakket in de distributie loopt vaak achter; NodeSource is de gebruikelijke methode op Ubuntu en Debian, en het levert een ondertekende repository (geen apt-key, dat hulpmiddel is verwijderd):
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --versionNu het onderdeel waar mensen vaak de fout in gaan: installeer de CLI als uw agent-gebruiker, nooit met sudo npm -g. Een globale prefix die eigendom is van root leidt later tot rechtenfouten en laat bestanden achter in de npm-cache die eigendom zijn van root. Wijs de prefix van npm eerst naar de home-directory van de gebruiker:
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 --versionDe export moet in ~/.bashrc, niet in ~/.profile, en deze hoort boven de "If not running interactively, don't do anything"-beveiliging bovenaan het bestand: tmux kan non-login shells starten, die ~/.bashrc lezen en ~/.profile overslaan; ~/.profile wordt alleen uitgevoerd voor login shells. Een Node-installatie per gebruiker via een versiebeheerder zoals nvm bereikt hetzelfde; het doel is in beide gevallen dat npm install -g nooit sudo nodig heeft. npm werkt nog steeds naar behoren, of gebruik het native installatiescript van Anthropic, de momenteel gedocumenteerde standaard. Controleer de installatiedocumentatie van Anthropic voordat u commando's plakt, aangezien installatiemethoden kunnen veranderen.
Voer claude uit binnen een repository om het te starten. De eerste keer dat u het programma uitvoert, wordt u door het authenticatieproces geleid; een headless server heeft geen browser, dus de procedure geeft u een URL die u op uw eigen machine kunt openen en een code die u terug in de terminal invoert. (Een API-sleutel in de omgeving is de andere optie.) Hoe dan ook, die inloggegevens bevinden zich nu op de server, wat ons brengt bij het onderdeel dat mensen vaak overslaan.
De discussie over de blast radius
Een agent met shell-toegang is een shell. Deze kan alles lezen wat de gebruiker waaronder de agent draait kan lezen, en alles pushen waar die gebruiker schrijfrechten heeft. Dit is geen kritiek op de tool, maar de definitie ervan. Daarom is het account waaronder de agent draait belangrijker dan welke individuele instelling dan ook.
- Toegewezen, niet-bevoorrechte gebruiker. Geen
sudo-groep, geen home-directory die gedeeld wordt met uw eigen account. - Geen productie-inloggegevens op de machine. Geen
~/.aws/credentialsmet productie-keys, geen.envdie vanuit productie zijn gekopieerd, geen databasewachtwoord met schrijfrechten voor kritieke zaken. Geef de agent inloggegevens voor staging of alleen-lezen toegang. - Beperkte tokens. Een fijnmazig GitHub-token dat beperkt is tot één repository; een deploy key wanneer alleen-lezen toegang volstaat.
Claude Code bevat een flag die de toestemmingsvragen volledig overslaat. Op een laptop, bij een wegwerpproject, is dat uw eigen keuze. Op een server die tokens bevat, verwijdert dit de laatste barrière tussen een verkeerd begrepen instructie en een git push --force. De prompts die u overslaat zijn bovendien niet alles-of-niets, en met auto mode die de nieuwe standaard wordt is het nuttig om te weten op welke toestemmingsmodus een server die u niet actief monitort moet worden vastgezet. Wat de flag feitelijk wijzigt, en hoe u een agent die hiermee draait kunt isoleren — van de ingebouwde sandbox tot een wegwerp-VPS — wordt behandeld in veilig draaien van Claude Code op een server.
Deploy key versus SSH agent forwarding
Het is verleidelijk om ssh -A te gebruiken zodat git de key op uw laptop kan gebruiken. Begrijp wat dit toestaat: agent forwarding stelt de socket van uw lokale SSH-agent bloot aan processen die als die gebruiker op de machine draaien. Alles wat draait als agent, inclusief de agent, kan uw key vragen om te tekenen voor elke host die bereikbaar is, zolang u verbonden bent. Dat is veel meer dan "laat git deze ene repo ophalen".
Genereer in plaats daarvan een key op de server, registreer deze als een deploy key per repository (alleen schrijfrechten als de agent moet pushen), en stel een git-identiteit in zodat commits vanaf de machine herkenbaar zijn:
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"De tmux-workflow
Installeer het (sudo apt install tmux), en gebruik vervolgens een minimaal ~/.tmux.conf:
set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"Vier commando's dekken het dagelijks gebruik:
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 is het commando dat u moet onthouden; het koppelt een sessie als deze bestaat en maakt er een aan als dat niet zo is. Eén commando volstaat dus zowel voor het starten van de dag als voor het hervatten na een verbreking. Maak er een alias van. Binnen een sessie opent Ctrl-b c een venster, wisselen Ctrl-b n en Ctrl-b p tussen vensters, en activeert Ctrl-b [ de copy mode om terug te scrollen (q sluit deze af).
Eén belangrijk punt bij sessies die u nooit afsluit: de agent verstuurt bij elke interactie het volledige gesprek opnieuw. Lees daarom waar een langlopende Claude Code-sessie zijn tokens aan besteedt voordat u een sessie een week lang laat draaien.
Foutmodi
"Mijn sessie is verdwenen." tmux ls geeft no server running on /tmp/tmux-1000/default weer. Dit betekent bijna altijd dat het proces nooit binnen tmux draaide; u heeft ingelogd via SSH, claude direct uitgevoerd en de verbinding werd verbroken, waardoor het proces werd beëindigd. Er is niets te herstellen. De gewoonte die dit voorkomt: tmux new -A -s <project> is het eerste commando na elke login.
Het venster krimpt tot een klein vakje. tmux schaalt een sessie naar de kleinste verbonden client, dus een verouderde client die nog verbonden is vanaf een andere machine, verkleint de weergave. Forceer de anderen om de verbinding te verbreken bij het verbinden: tmux attach -d -t claude.
Een build geeft Killed weer. Eén woord, geen stack trace. Bevestig met sudo dmesg -T | grep -i -E 'out of memory|killed process'; de OOM-killer van de kernel heeft het grootste proces gekozen. Bij Node ziet u mogelijk in plaats daarvan FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory. Oplossingen, in volgorde: voeg swap toe (hierboven), beperk de parallellisme van tests en compilers, verhoog de heap van Node met NODE_OPTIONS=--max-old-space-size=..., of schaal de VPS op. De OOM-killer kan ook de tmux server kiezen in plaats van de build, waardoor uw sessie verloren gaat; als systemd-oomd draait, kan dit een hele user slice beëindigen met hetzelfde resultaat.
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. Een globale installatie in een prefix die eigendom is van root. Gebruik de ~/.npm-global prefix hierboven. Als u op enig moment sudo npm heeft uitgevoerd, ziet u mogelijk ook Your cache folder contains root-owned files; herstel dit met sudo chown -R $(id -u):$(id -g) ~/.npm.
claude: command not found, maar slechts soms. Uw PATH export staat in ~/.bashrc onder de "If not running interactively, don't do anything" beveiliging, waardoor niet-interactieve shells deze overslaan. Verplaats de export boven die beveiliging en houd deze in ~/.bashrc, niet ~/.profile: tmux kan non-login shells starten, die ~/.bashrc lezen en nooit ~/.profile aanraken.
Verkeerde kleuren na het verbinden. Een TERM mismatch; de default-terminal regel hierboven is de oplossing.
Sessies verdwijnen na een herstart. Geen bug: de tmux server is een proces en een herstart beëindigt dit. Controleer uptime.
Wat er misgaat bij schaalvergroting
Meer projecten. Eén tmux-sessie per repository, vernoemd naar het project; tmux ls fungeert dan als uw dashboard. Als u de naamgeving verwaarloost, krijgt u sessies als 0, 1 en 2. Wanneer er meerdere tegelijk draaien, hoeven ze niet in isolatie te werken, aangezien één sessie een bericht naar een andere op dezelfde machine kan sturen. Dit is nuttig wanneer de agent die een grote refactor uitvoert, een tweede sessie nodig heeft om de tests te draaien. Poorten raken op dezelfde manier verspreid; bij zes repositories die allemaal :3000 willen gebruiken, is het tijd om te stoppen met handmatige toewijzing en een Traefik reverse proxy via Docker Compose het verkeer op basis van hostnaam te laten routeren.
Meer mensen. tmux-sockets zijn per gebruiker; twee ontwikkelaars op dezelfde machine krijgen elk hun eigen tmux-server en kunnen elkaars sessies niet zien. Het delen van één sessie via een gedeelde socket betekent dat iedereen in dezelfde shell typt als dezelfde Unix-gebruiker, met alle gevolgen voor audit-logs en rechten van dien. Aparte gebruikersaccounts zijn de saaie, maar correcte oplossing.
Onbeheerd werk. tmux is bedoeld voor interactieve sessies waar u aan koppelt. Taken die volgens een schema draaien zonder toezicht, horen thuis in een systemd-unit en -timer. Hierbij krijgt u logging, een herstartbeleid en overleving na een reboot cadeau. Het gebruik van tmux voor een cron-achtige taak is een teken dat de taak als service moet worden ingericht.
Nog één opmerking: bind ontwikkelservers die de agent start aan 127.0.0.1, niet aan 0.0.0.0, en benader ze via een SSH-tunnel (ssh -L 3000:127.0.0.1:3000 agent@your-server) in plaats van poorten te openen in ufw. Zodra u een half dozijn poorten doorstuurt, of wanneer zowel een telefoon als een laptop dezelfde preview nodig hebben, kunt u beter een zelfgehoste WireGuard VPN op de VPS ervoor plaatsen: de ontwikkelservers binden aan een privé-interface en ufw blijft alles vanaf de publieke interface blokkeren. De firewall helpt alleen als u stopt met het maken van uitzonderingen.
Claude Code is niet de enige keuze: het draaien van een AI-coding-agent op een VPS biedt ook opties zoals Aider en Goose.
FAQ
Blijft Claude Code draaien nadat mijn SSH-verbinding is verbroken?
Alleen als u het binnen tmux heeft gestart. Een proces dat direct vanuit de SSH-shell wordt gestart, is een kindproces van die shell en wordt beëindigd zodra de pty sluit wanneer de verbinding wegvalt. Binnen tmux behoort de shell tot de losgekoppelde tmux-server, waardoor de agent tijdens de taak blijft werken en tmux attach u terugbrengt naar dezelfde scrollback. Maak tmux new -A -s <project> het eerste commando na elke login en het probleem is opgelost.
Moet ik de CLI installeren met sudo npm install -g?
Nee. Een globale prefix die eigendom is van root levert u bij latere installaties EACCES-fouten op en bestanden in de npm-cache die eigendom zijn van root. Stel de prefix van npm in op ~/.npm-global (of gebruik een versiebeheerder zoals nvm), installeer als de ongeprivilegieerde agent-gebruiker en exporteer ~/.npm-global/bin naar PATH vanuit ~/.bashrc, boven de interactieve guard. Als u sudo npm al een keer heeft uitgevoerd, repareer de cache dan met sudo chown -R $(id -u):$(id -g) ~/.npm.
Is ssh -A agent forwarding veilig op een server die een agent draait?
Het verleent veel meer rechten dan de taak nodig heeft. Forwarding stelt de socket van uw lokale SSH-agent bloot aan elk proces dat als die gebruiker draait, waardoor alles op de server uw sleutel kan vragen om te tekenen voor elke host die het kan bereiken, zolang u verbonden blijft. Genereer een ed25519-sleutel op de server en registreer deze als een deploy-sleutel per repository, met schrijftoegang alleen als de agent daadwerkelijk moet pushen.
Waarom print mijn build alleen Killed?
Eén woord zonder stacktrace duidt op de OOM-killer van de kernel. Bevestig dit met sudo dmesg -T | grep -i -E 'out of memory|killed process'; vanuit Node ziet u mogelijk JavaScript heap out of memory. Werk de oplossingen in volgorde af: voeg een swapfile toe, beperk de parallelliteit van tests en compilers, verhoog NODE_OPTIONS=--max-old-space-size=... en schaal vervolgens de VPS op. Let op dat de OOM-killer de tmux-server kan kiezen in plaats van de build, waardoor uw hele sessie verloren gaat.
tmux of een systemd-service?
tmux is geschikt voor interactieve sessies waar u aan koppelt, meekijkt en in typt; precies wat een agent-sessie is. Werk dat volgens een schema draait zonder toezicht hoort in een systemd-unit en timer thuis, waar logging, een herstartbeleid en overleving na een reboot standaard aanwezig zijn. Als u naar tmux grijpt om een cron-achtige taak uit te voeren, dan hoort die taak een service te zijn.