21. Juli 2026 12 Min. Lesezeit

Claude Code auf einem eigenen VPS sicher betreiben

Ein gehärteter Debian-VPS hält Claude-Code-Sitzungen dauerhaft erreichbar. Die Anleitung führt von Benutzerkonto und SSH-Schlüsseln bis zu Firewall, Datenhygiene, tmux und sicherem Zugriff vom iPhone.

Auf dem eigenen Rechner endet eine Claude-Code-Sitzung spätestens dann ungewollt, wenn der Laptop schläft oder die Netzwerkverbindung abbricht. Ein VPS läuft weiter und ist von mehreren Geräten erreichbar. Gleichzeitig hängt er dauerhaft am öffentlichen Internet und wird schon kurz nach dem Start automatisiert gescannt.

Diese Anleitung verbindet beide Anforderungen: Claude Code bleibt in einer tmux-Sitzung verfügbar, während der Debian-Server nur eine schlüsselgeschützte SSH-Verbindung nach aussen anbietet. Die Härtung ist nicht Claude-spezifisch und eignet sich auch für andere öffentlich erreichbare Linux-Server.

Weshalb ein VPS sinnvoll sein kann

Gegenüber einer rein lokalen Installation bietet der Server drei praktische Vorteile:

  • Persistenz. In einer tmux-Sitzung läuft Claude weiter, auch wenn die SSH-Verbindung getrennt wird. Ein Task, der zehn Minuten oder eine Stunde braucht, läuft zu Ende, ohne dass der Laptop offen bleiben muss.
  • Erreichbarkeit. Dieselbe Sitzung ist vom Desktop, vom Laptop und vom iPhone aus zugänglich. Man setzt am Schreibtisch einen Task an und schaut unterwegs nach dem Ergebnis.
  • Datenkontrolle. Was auf dem Server liegt, entscheidet man selbst. Kein Sync-Dienst, keine versehentlich mitgesicherten Zugangsdaten, vorausgesetzt, man geht bei der Migration sorgfältig vor (siehe unten).

tmux ist dabei ein reines Verfügbarkeits- und Komfortmerkmal, keine Sicherheitsmassnahme. Die eigentliche Arbeit steckt in der Absicherung.

Ausgangslage

Als Basis dient Debian 13 (Trixie), minimal installiert, ohne Desktop und ohne zusätzliche Netzwerkdienste. Der Provider stellt eine vorgelagerte Firewall bereit, die unabhängig vom Betriebssystem greift. Ziel ist ein Server, auf dem ausschliesslich SSH von aussen erreichbar ist, und auch das nur mit passphrasengeschützten Schlüsseln.

1. System aktualisieren

Direkt nach der Installation den kompletten Paketstand aktualisieren:

sudo apt update
sudo apt full-upgrade
Optionen erklärt
OptionWirkung
updateLiest die Paketlisten aller konfigurierten Quellen neu ein
full-upgradeAktualisiert alle Pakete und darf dafür auch neue Pakete installieren oder bestehende entfernen

full-upgrade löst im Gegensatz zu upgrade auch Abhängigkeiten auf, die neue oder entfernte Pakete erfordern. Bei einem frischen System ist das der richtige Weg, um wirklich alle verfügbaren Sicherheitsaktualisierungen einzuspielen. Nach Kernel-Updates einmal neu starten.

2. Eigener Benutzer statt root

Als root zu arbeiten ist unnötig riskant: Jeder Tippfehler wirkt systemweit, und der direkte root-Login ist das erste, was automatisierte Angriffe versuchen. Deshalb ein eigener Benutzer (hier claude) mit sudo-Rechten für die Fälle, in denen sie gebraucht werden:

sudo adduser claude
sudo usermod -aG sudo claude
Optionen erklärt
OptionWirkung
-aAnhängen: ergänzt die Gruppenliste des Benutzers, statt sie zu ersetzen; nur zusammen mit -G gültig
-G sudoErgänzungsgruppe(n), in die der Benutzer aufgenommen wird
claudeDer betroffene Benutzer; bei adduser der Name des neu anzulegenden Kontos

Ab jetzt läuft alle Administration über claude und sudo, nicht mehr über den direkten root-Zugang.

3. Ed25519-Schlüssel mit Passphrase, einer pro Gerät

Die Anmeldung soll ausschliesslich über SSH-Schlüssel laufen, nicht über Passwörter. Ed25519 ist der aktuelle Standard: kurz, schnell, kryptografisch solide. Entscheidend ist, dass der Schlüssel auf dem Client erzeugt wird, also auf dem PC, nicht auf dem Server, und mit einer Passphrase geschützt ist. Die Passphrase ist die zweite Verteidigungslinie, falls der private Schlüssel je in falsche Hände gerät.

Auf dem PC:

ssh-keygen -t ed25519 -C "pc-thinkpad"
Optionen erklärt
OptionWirkung
-t ed25519Schlüsseltyp, hier das elliptische Verfahren Ed25519
-C "pc-thinkpad"Kommentar, der an den öffentlichen Schlüssel angehängt wird

Der Kommentar (-C) benennt das Gerät. Das zahlt sich später aus: Für jedes Gerät wird ein eigener Schlüssel erzeugt: einer für den PC, ein separater fürs iPhone. Geht ein Gerät verloren, entfernt man gezielt dessen öffentlichen Schlüssel aus ~/.ssh/authorized_keys, ohne alle anderen Zugänge neu ausrollen zu müssen.

Nur der öffentliche Schlüssel gehört auf den Server. Der private Schlüssel verlässt das Gerät nie. In authorized_keys stehen am Ende ausschliesslich öffentliche Schlüssel, jeweils mit ihrem Gerätekommentar:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...pc  pc-thinkpad
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...ios iphone-15

Den öffentlichen PC-Schlüssel initial übertragen. Solange die Passwortanmeldung noch aktiv ist, geht das am einfachsten mit:

ssh-copy-id claude@SERVER
Optionen erklärt
OptionWirkung
claude@SERVERBenutzer und Zielhost; der öffentliche Standardschlüssel wird dort an ~/.ssh/authorized_keys angehängt

Danach testen, dass der Login mit Schlüssel funktioniert, bevor im nächsten Schritt die Passwortanmeldung abgeschaltet wird. Die Dateirechte müssen stimmen, sonst ignoriert sshd die Datei: ~/.ssh auf 700, authorized_keys auf 600.

4. SSH härten: kein root, kein Passwort

Die Server-Konfiguration liegt in /etc/ssh/sshd_config und (bei Debian 13) in Drop-in-Dateien unter /etc/ssh/sshd_config.d/. Änderungen gehören in eine eigene Drop-in-Datei; so bleibt die Hauptdatei unangetastet und Paket-Updates überschreiben nichts. Datei /etc/ssh/sshd_config.d/99-haertung.conf anlegen:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes

Das deaktiviert den direkten root-Login und die Passwortanmeldung. Ab jetzt kommt nur noch herein, wer einen passenden privaten Schlüssel besitzt. Vor dem Neuladen die Konfiguration syntaktisch prüfen:

sudo sshd -t
Optionen erklärt
OptionWirkung
-tTestmodus: prüft Konfigurationsdatei und Schlüssel auf Gültigkeit, ohne den Dienst zu starten

Meldet sshd -t nichts, ist die Datei valide. Erst dann neu laden:

sudo systemctl reload ssh
Optionen erklärt
OptionWirkung
reloadWeist den Dienst an, seine Konfiguration neu zu laden, ohne bestehende Verbindungen zu trennen
sshDie Ziel-Unit, hier der OpenSSH-Dienst

Wichtig: Die bestehende SSH-Sitzung offen lassen und den neuen Zugang in einem zweiten Terminal testen. Erst wenn der Login mit Schlüssel dort nachweislich klappt, darf die alte Sitzung geschlossen werden. Diese Vorsichtsmassnahme reduziert das Aussperrrisiko auf praktisch null. Ein Fehler in der Konfiguration kostet sonst den kompletten Zugang.

5. SSH auf einen unüblichen Port legen

Der Standardport 22 wird rund um die Uhr von Bots durchprobiert. Ein Wechsel auf einen hohen, frei gewählten Port (im Beispiel 61417) lässt den Grossteil dieses automatisierten Rauschens ins Leere laufen. Das ist ausdrücklich kein Sicherheitsgewinn im eigentlichen Sinn: Ein Portwechsel ersetzt keine starke Authentifizierung, er reduziert nur die Logmenge und die Scan-Last. Die Schlüsselpflicht aus Schritt 4 bleibt die eigentliche Absicherung.

Welcher Port das wird, ist nicht beliebig. Die IANA unterscheidet drei Zonen: 0–1023 (well-known ports) sind Standarddiensten vorbehalten (SSH selbst auf 22, HTTP auf 80, HTTPS auf 443), erfordern root zum Binden und haben auf einem selbst gewählten SSH-Port nichts verloren, genau diese Ports erwarten Scanner ebenso wie später installierte Standarddienste. 1024–49151 (registrierte Ports) sind auf Anfrage einzelnen Anwendungen zugeteilt, etwa 3306 (MySQL), 5432 (PostgreSQL), 6379 (Redis) oder 8080/8443 als verbreitete HTTP-Alternativen; ein zufällig gewählter Port aus diesem Bereich kollidiert später leicht mit Software, die genau ihren registrierten Port erwartet. 49152–65535 (dynamische/private Ports) sind laut IANA keinem Dienst zugewiesen und für temporäre, private Zwecke gedacht, der richtige Bereich für einen dauerhaften, selbst gewählten Port.

Ein Vorbehalt bleibt: Viele Linux-Systeme, auch Debian, nutzen einen Teil desselben Bereichs als Quellport für eigene ausgehende Verbindungen (net.ipv4.ip_local_port_range, standardmässig um 32768–60999). Ein dauerhaft lauschender Dienst kollidiert dadurch nicht wirklich, der Kernel vergibt keinen bereits gebundenen Port, aber ein Port oberhalb von 60999 umgeht auch diese theoretische Unschärfe. Das Beispiel in diesem Artikel (61417) liegt deshalb bewusst dort. Vor der Umstellung zusätzlich mit ss -lntup (siehe Schritt 7) prüfen, ob der gewählte Port auf dem eigenen Server nicht schon belegt ist.

Bei Debian 13 gibt es hier eine Besonderheit: SSH kann über systemd-Socket-Aktivierung gestartet werden. Ist das der Fall, wird die Port-Angabe in der sshd_config schlicht ignoriert; der Port muss dann am Socket gesetzt werden. Zuerst prüfen, welcher Fall vorliegt:

systemctl is-enabled ssh.socket
Optionen erklärt
OptionWirkung
is-enabledZeigt, ob die Unit für den Systemstart aktiviert ist
ssh.socketDie Socket-Unit des SSH-Diensts

Antwortet der Befehl mit enabled, läuft SSH über den Socket. Dann den Port dort ändern:

sudo systemctl edit ssh.socket
Optionen erklärt
OptionWirkung
editLegt eine Drop-in-Override-Datei für die Unit an und öffnet sie im Editor
ssh.socketDie zu überschreibende Socket-Unit

Im Editor folgende Zeilen eintragen. Die erste, leere ListenStream=-Zeile löscht den voreingestellten Port 22, die zweite setzt den neuen:

[Socket]
ListenStream=
ListenStream=61417

Anschliessend übernehmen:

sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
Optionen erklärt
OptionWirkung
daemon-reloadLiest alle Unit-Dateien neu ein, inklusive der eben angelegten Override-Datei
restart ssh.socketStartet die Socket-Unit neu, damit sie auf dem neuen Port lauscht

Ist die Socket-Aktivierung nicht aktiv (disabled), gehört stattdessen Port 61417 in die Drop-in-Datei aus Schritt 4, gefolgt von sudo sshd -t und sudo systemctl restart ssh.

Auch hier gilt: Erst den neuen Port in der Firewall öffnen (nächster Schritt), dann verbinden und testen, und die alte Sitzung offen lassen, bis der Zugang über den neuen Port bestätigt ist.

6. Firewall: standardmässig dicht

Die vorgelagerte Provider-Firewall ist die wirksamste Grenze, weil sie Pakete abfängt, bevor sie das Betriebssystem überhaupt erreichen. Zwei Grundregeln:

  • Eingehende Standardaktion auf DROP. Alles, was nicht ausdrücklich erlaubt ist, wird verworfen, kommentarlos, ohne Rückmeldung an den Absender.
  • Eine einzige Ausnahme: eingehendes TCP auf den Ziel-Port 61417. Mehr muss von aussen nicht erreichbar sein.

Der ausgehende Verkehr bleibt erlaubt. Das ist bewusst so: Der Server muss Pakete laden, die Zeit synchronisieren und für Claude Code die API erreichen. Eine restriktive Ausgangsfilterung bringt bei einem Einzelserver wenig zusätzlichen Schutz, macht den Betrieb aber spürbar umständlicher.

Wer zusätzliche Tiefenstaffelung möchte, kann dieselben Regeln hostseitig mit nftables oder ufw doppeln. Für den beschriebenen Aufbau genügt die Provider-Firewall.

7. Angriffsfläche prüfen

Nach der Härtung kontrollieren, was der Server tatsächlich nach aussen anbietet. Zwei Befehle reichen. Erstens: Welche Dienste lauschen auf welchen Adressen?

sudo ss -lntup
Optionen erklärt
OptionWirkung
-lNur lauschende Sockets anzeigen
-nNumerische Ausgabe: Ports und Adressen werden nicht in Namen aufgelöst
-tTCP-Sockets einbeziehen
-uUDP-Sockets einbeziehen
-pZeigt den Prozess hinter jedem Socket; dafür ist sudo nötig

Entscheidend ist die Adressspalte: Ein Dienst auf 0.0.0.0 oder [::] ist von aussen erreichbar, einer auf 127.0.0.1 oder [::1] nur lokal. Im abgesicherten Zustand sollte ausschliesslich SSH öffentlich erscheinen. Dienste wie chronyd (Zeitsynchronisation) dürfen auftauchen, aber nur an lokale Adressen gebunden. Hört chronyd ausschliesslich auf 127.0.0.1 und ::1, ist er nicht von aussen ansprechbar und damit unkritisch.

Zweitens: Gibt es fehlgeschlagene Systemdienste, die auf ein Konfigurationsproblem hindeuten?

systemctl --failed
Optionen erklärt
OptionWirkung
--failedListet ausschliesslich Units im Fehlerzustand

Die Antwort sollte 0 loaded units listed lauten, kein einziger fehlgeschlagener Dienst. Fehlerhafte Units sind nicht nur ein Betriebs-, sondern potenziell auch ein Sicherheitsproblem, wenn dahinter ein halb gestarteter, falsch konfigurierter Netzwerkdienst steckt.

8. Claude Code installieren und betreiben

Claude Code braucht eine aktuelle Node.js-Laufzeitumgebung. Nach deren Installation die CLI gemäss der offiziellen Anleitung einrichten und auf dem Server frisch authentifizieren, nicht die lokalen Zugangsdaten hochladen (dazu gleich mehr).

Für den dauerhaften Betrieb tmux:

tmux new -s claude
Optionen erklärt
OptionWirkung
newErzeugt eine neue Sitzung
-s claudeVergibt den Sitzungsnamen, unter dem sie später wieder aufgenommen wird

Innerhalb der Sitzung Claude starten. Mit Ctrl-b, dann d löst man sich von der Sitzung, ohne sie zu beenden; Claude läuft weiter. Zurück geht es mit:

tmux attach -t claude
Optionen erklärt
OptionWirkung
attachVerbindet das Terminal wieder mit einer laufenden Sitzung
-t claudeWählt die Zielsitzung anhand ihres Namens

So überlebt eine laufende Aufgabe getrennte Verbindungen, Gerätewechsel und die Nachtruhe des Laptops.

9. Datenhygiene bei der Migration

Der heikelste Teil beim Umzug auf den Server ist nicht die Technik, sondern die Frage, was man mitnimmt. Drei Regeln:

  • Keine privaten Schlüssel auf den Server. In authorized_keys liegen ausschliesslich öffentliche Schlüssel. Private Schlüssel bleiben auf den Endgeräten.
  • Keine Zugangsdaten pauschal kopieren. Sensible lokale Dateien wie eine .credentials.json gehören nicht ungeprüft auf den VPS. Stattdessen authentifiziert man sich frisch auf dem Server.
  • Konfiguration erst in einen Migrationsordner. Bestehende Claude-Memories und -Konfiguration nicht direkt in die aktiven Konfigurationspfade schreiben, sondern zunächst in einen separaten Migrationsordner übertragen und dort prüfen, was wirklich übernommen werden soll. Was man nicht mehr braucht, etwa alte MCP-Einträge oder verwaiste Einstellungen, bleibt bewusst zurück, statt unbesehen mitzuwandern.

10. Web-Vorschauen über einen SSH-Tunnel

Für Web-Vorschauen, etwa einen lokalen Entwicklungsserver, den Claude startet, ist die Versuchung gross, einfach einen weiteren Port zu öffnen. Das sollte man nicht tun. Jeder zusätzliche offene Port ist zusätzliche Angriffsfläche. Stattdessen läuft die Vorschau durch einen verschlüsselten SSH-Port-Tunnel: Der Dienst hört nur lokal auf dem Server, und SSH reicht ihn auf den Client durch.

Vom PC aus einen lokal auf Port 4321 laufenden Dienst erreichbar machen:

ssh -p 61417 -L 4321:localhost:4321 claude@SERVER
Optionen erklärt
OptionWirkung
-p 61417Port, auf dem der SSH-Server lauscht (der in Schritt 5 gewählte)
-L 4321:localhost:4321Lokale Portweiterleitung: Verbindungen auf den lokalen Port 4321 werden durch den Tunnel an localhost:4321 aus Sicht des Servers weitergereicht
claude@SERVERBenutzer und Zielhost der SSH-Verbindung

Danach öffnet man im lokalen Browser http://localhost:4321. Der Verkehr läuft vollständig durch die bestehende, authentifizierte SSH-Verbindung, ohne dass in der Firewall auch nur ein einziger weiterer Port geöffnet werden muss.

Zugriff vom iPhone

Der Zugriff von unterwegs funktioniert mit demselben Sicherheitsmodell wie vom PC. Man braucht nur einen SSH-Client mit Schlüsselverwaltung. Verbreitet sind Termius, Blink Shell und Secure ShellFish; alle können Ed25519-Schlüssel erzeugen und im iOS-Schlüsselbund ablegen, teils abgesichert per Face ID.

Das Vorgehen entspricht Schritt 3, nur auf dem iPhone:

  1. Im SSH-Client einen eigenen Ed25519-Schlüssel fürs iPhone erzeugen, nicht den PC-Schlüssel kopieren. Der private Schlüssel bleibt im Schlüsselbund des Geräts.
  2. Den öffentlichen Schlüssel des iPhones als zusätzliche Zeile in ~/.ssh/authorized_keys auf dem Server eintragen, mit sprechendem Kommentar (iphone-15).
  3. Im Client die Verbindung anlegen: Server-Adresse, Benutzer claude, Port 61417, der iPhone-Schlüssel als Authentifizierung.

Genau deshalb lohnt sich der separate Schlüssel pro Gerät: Geht das iPhone verloren, löscht man auf dem Server die eine iphone-15-Zeile aus authorized_keys, und das Gerät ist ausgesperrt, während PC-Zugang und alle anderen Schlüssel unberührt weiterlaufen.

Nach dem Verbinden holt man die laufende Claude-Sitzung mit tmux attach -t claude zurück und arbeitet dort weiter, wo man am Schreibtisch aufgehört hat. Auch der Port-Tunnel aus Schritt 10 funktioniert von iOS aus; Termius und Secure ShellFish beherrschen Port-Weiterleitung.

Checkliste

Zusammengefasst der komplette Ablauf:

  1. Debian 13 installiert, mit apt full-upgrade vollständig aktualisiert.
  2. Eigener Benutzer claude mit sudo-Rechten; direkter root-Login nicht mehr genutzt.
  3. Passphrasengeschützte Ed25519-Schlüssel, pro Gerät einer, nur öffentliche Schlüssel in authorized_keys.
  4. sshd gehärtet: PermitRootLogin no, PasswordAuthentication no; vor dem Neuladen mit sshd -t geprüft, bestehende Sitzung bis zum Test offen gelassen.
  5. SSH auf Port 61417, bei Socket-Aktivierung am ssh.socket gesetzt, sonst in der sshd-Konfiguration.
  6. Provider-Firewall: eingehend Default DROP, einzige Ausnahme TCP 61417; ausgehend erlaubt.
  7. Angriffsfläche geprüft mit ss -lntup (nur SSH öffentlich, chronyd lokal) und systemctl --failed (keine Fehler).
  8. Claude Code auf dem Server frisch authentifiziert, Betrieb in einer tmux-Sitzung.
  9. Datenhygiene: keine privaten Schlüssel und keine Zugangsdaten auf den Server, Konfiguration erst über einen Migrationsordner geprüft.
  10. Keine zusätzlichen Ports; Web-Vorschauen laufen durch einen SSH-Tunnel.

Nach dieser Einrichtung ist von aussen nur SSH auf dem festgelegten Port erreichbar, und auch dort ausschliesslich mit einem passphrasengeschützten Schlüssel. Claude Code läuft unabhängig vom Endgerät in tmux; Web-Vorschauen bleiben über SSH-Tunnel zugänglich, ohne einen zusätzlichen Port zu öffnen.

Quellen

  1. OpenSSH Manual – sshd_config(5)

    Referenz aller sshd-Direktiven, darunter PermitRootLogin, PasswordAuthentication und PubkeyAuthentication.

    https://man.openbsd.org/sshd_config
  2. Debian Wiki – SSH

    Debian-spezifische Hinweise zur SSH-Konfiguration inklusive der Drop-in-Dateien unter /etc/ssh/sshd_config.d/.

    https://wiki.debian.org/SSH
  3. systemd.socket(5) – freedesktop.org

    Funktionsweise der Socket-Aktivierung und der ListenStream=-Direktive, relevant für den SSH-Portwechsel unter Debian 13.

    https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html
  4. ss(8) – iproute2 Manpage

    Optionen von ss, um lauschende Sockets samt Prozess und Bindungsadresse aufzulisten.

    https://man7.org/linux/man-pages/man8/ss.8.html
  5. Claude Code – Offizielle Dokumentation

    Installation, Authentifizierung und Betrieb von Claude Code.

    https://docs.claude.com/en/docs/claude-code/overview

Kommentare

Die Kommentare werden von GitHub / Giscus geladen.