Wie ich von fünf VPS auf einen Dedizierten Server mit Proxmox umgezogen bin und aufhörte, Hoster zu füttern
TL;DR: Zersplitterte Infrastruktur aus fünf VPS bei drei Anbietern → ein dedizierter Server Ryzen 7 9700X / 64GB ECC / 2x NVMe ZFS-Mirror, darauf Proxmox VE. Darin – VMs/LXCs nach Rollen, NetBird-Mesh statt Portweiterleitung, eine Ausgangs-Node auf einem separaten VPS zur Umgehung von Blockaden und TLS-Terminierung, PBS für Backups. Geld positiv, Kontrolle positiv, Nerven positiv. In diesem Artikel – warum, wie und wohin ich auf dem Weg getreten bin, damit Sie nicht treten.
Warum habe ich das überhaupt angefangen?
Bis Ende letzten Jahres stellte ich fest, dass ich für fünf verschiedene VPS bei drei Hostern bezahlte. Eine – für ein Discourse-Forum, die zweite – für ein Legacy-Forum auf PHP mit MySQL, die dritte – für Abrechnung und Telegram-Bots, die vierte – VPN/Proxy, die fünfte – statische Inhalte und diverse kleine Tools. Und irgendwo dort lief auch n8n, und Vaultwarden, und ein paar Uptime Kumas, die sich gegenseitig rundherum überwachten (wie sollte es anders sein).
Was war schlecht daran:
- Geld. Insgesamt kamen ungefähr die Kosten für einen anständigen dedizierten Server mit ähnlichen Eigenschaften zusammen (aber dort Shared, hier dediziert).
- Keine Isolierung. Wenn bei einem Bot-Kunden der Speicher undicht wird – fängt mein Forum an zu stottern. Auf demselben Server. Herrlich.
- Backups – durch die Hintertür. Auf jeder VPS ein eigener rsync-Cronjob nach S3, überall unterschiedliche Aufbewahrungsfristen, keine Inkrementalität, die Wiederherstellung – ein ganzes Abenteuer.
- Netzwerk als Salat. SSH über verschiedene Ports, durch Cloudflare Tunnels geleitet, über manuell zwischen zwei VPS eingerichtetes WireGuard, mit Konfigurationen an drei Stellen. Jedes Mal, wenn ich eine neue Maschine hinzufügte – verging ein Tag für die Integration.
- Update-Apokalypse. Auf fünf verschiedenen Ubuntu/Debian-Versionen mit unterschiedlichen Versionen und Paketen mussten Upgrades manuell durchgeführt werden und niemals gleichzeitig. Alle sechs Monate habe ich heroisch die Quest „alles aktualisieren, nichts kaputt machen“ bestanden.
- CPU und RAM im Leerlauf. Jede VPS ist für den „Peak“ gebucht, aber im Durchschnitt nur zu 15-20% ausgelastet. Geld fliegt zum Fenster hinaus.
Kurz gesagt – ein klassischer Fall, in dem die Infrastruktur nach dem Prinzip wuchs: „Oh, ich brauche noch einen Dienst – ich kaufe eine VPS“. Es war an der Zeit, das alles zu konsolidieren.
Was ich gewählt habe und warum
Hardware
Ich habe einen dedizierten Server genommen:
- CPU: AMD Ryzen 7 9700X (8 Kerne / 16 Threads, Zen 5)
- RAM: 64 GB DDR5 ECC 5200 MHz
- Festplatten: 2x NVMe 512 GB Enterprise → ZFS-Mirror
- Netzwerk: 1 Gbps unmetered
- Preis: ungefähr wie zwei mittlere VPS beim selben OVH
Hypervisor
Proxmox VE 9.x. Alternativen habe ich betrachtet, aber kurz:
- Nacktes Docker – keine Isolierung, keine normalen Snapshots, Backup nur über Compose-Mittel, Festplatte – eine einzige Müllhalde.
- VMware ESXi – nicht mehr kostenlos, die Broadcom-Lizenz ist großartig, danke.
- XCP-ng – in Ordnung, aber persönlich kenne ich Proxmox auswendig und die Community ist lebendiger.
- Kubernetes – nein, ich bin eine einzelne Person, ich muss keinen Jumbo-Jet bauen, um zum Laden zu fahren.
Proxmox gibt mir alles, was ich brauche: KVM-Virtualmaschinen für alles, wo Isolierung benötigt wird (Docker, spezifische Kernel, verschiedene Distributionen); LXC-Container dort, wo Ressourcen gespart werden können (DBs, Monitoring, kleine Dienste); integriertes PBS für Backups; ZFS out-of-the-box; Snapshots; Live-Migration (wenn jemals ein zweiter dedizierter Server erscheint); eine Weboberfläche, auf der man mit der Maus klicken kann, wenn man keine Lust hat, im Terminal zu tippen.
Architektur: Übersichtsschema
Zuerst zeige ich, was daraus geworden ist, dann erzähle ich, wie ich dorthin gelangt bin.
Die Logik ist folgende:
- Auf dem dedizierten Server läuft Proxmox. Er hat die öffentliche IP
Y.Y.Y.Yaufvmbr0. - Innen wurde ein zweiter Bridge
vmbr1mit einem privaten Netzwerk10.10.10.0/24erstellt. Das ist das Äquivalent eines „internen LAN“ – alle VMs und LXCs hängen dort, nach außen über NAT vom Host, sie haben keine eingehenden öffentlichen Ports. - Auf dem Host selbst laufen nur drei Dinge:
Caddy(interner Reverse Proxy),nftables(Firewall) undNetBird(VPN-Agent). Kein Docker auf dem Host, keine Anwendungslogik auf dem Host. Der Host ist eine heilige Kuh, seine Aufgabe ist es, ein Hypervisor zu sein und nicht abzustürzen. - Die virtuellen Maschinen sind nach Rollen aufgeteilt: eine für das Forum, eine für Hintergrunddienste und Bots, eine für Dokploy mit benutzerdefinierten Anwendungen usw. Zwischen ihnen gibt es direkte Sichtbarkeit über
10.10.10.x, nach außen – nur über den Host-Caddy. - LXC-Container – für alles „Leichte“: Postgres-Instanzen verschiedener Versionen, Proxmox Backup Server, Monitoring-Dashboards. Sie sind ebenfalls in
vmbr1. - Eine separate Ausgangs-Node („Peer Caddy“) – das ist eine kleine VPS bei einem anderen Anbieter, in einem für mein Publikum angemessenen Netzwerk. Darauf läuft Caddy, der TLS (Let's Encrypt) terminiert und HTTP über das NetBird-Mesh in den dedizierten Server weiterleitet. Die DNS aller öffentlichen Domains zeigen auf diesen VPS, nicht auf den dedizierten Server.
- NetBird Mesh verbindet den dedizierten Server, die Ausgangs-Node und meinen Laptop (sowie ein paar alte Server, von denen noch nicht alles umgezogen ist). Das ist WireGuard über SSO-Authentifizierung, ohne offene Ports nach außen.
Warum eine solche Isolierung über die Ausgangs-Node – das ist ein separates Thema, zu dem ich noch zurückkommen werde.
Proxmox-Installation: Wie ich es eingerichtet habe
Ich habe den dedizierten Server bestellt, beim ersten Boot Proxmox über den Rescue-Modus/KVM installiert (OVH hat beides – mir ist KVM lieber, man sieht den gesamten Prozess). Bei der Installation:
- ZFS RAID1 auf beide NVMe (
ashift=12,compression=lz4sofort aktiviert) - Standard-Partitionierung, Proxmox erstellt selbst
rpool/ROOT,rpool/data, swap
Danach – grundlegende Bereinigung nach der Installation. Das ist ein Muss, ohne das Proxmox Abonnementfehler ausgibt und nicht optimal funktioniert.
1# 1. Enterprise-Repository entfernen (kein Abonnement)
2sed -i 's/^deb/#deb/' /etc/apt/sources.list.d/pve-enterprise.list
3echo "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
4 > /etc/apt/sources.list.d/pve-no-subscription.list
5apt update && apt full-upgrade -y
6
7# 2. Nag-Fenster "Kein gültiges Abonnement" entfernen
8sed -Ezi.bak "s/(Ext\.Msg\.show\(\{[^}]+?title: gettext\('No valid sub)/void\(\{ \/\/\1/g" \
9 /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
10systemctl restart pveproxy
11
12# 3. Hostname / Zeitzone
13hostnamectl set-hostname dedik-waw
14timedatectl set-timezone Europe/Warsaw
SSH-Härtung
Sofort. Nicht „später“. Später kommt niemals.
1cat > /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
2Port 22222
3PermitRootLogin prohibit-password
4PasswordAuthentication no
5MaxAuthTries 3
6ClientAliveInterval 300
7ClientAliveCountMax 2
8EOF
9
10mkdir -p ~/.ssh
11echo "ssh-ed25519 AAAA... your-key" >> ~/.ssh/authorized_keys
12chmod 600 ~/.ssh/authorized_keys
13systemctl restart sshd
Port 22 in der Firewall geschlossen, nur der nicht standardmäßige geöffnet. Das ist keine Sicherheit durch Obskurität, sondern eine Reduzierung des Hintergrundrauschens in den Logs – auf Port 22 kommen in der ersten Stunde 50.000 Brute-Force-Versuche an, in den Logs sieht man nichts. Auf 22222 – Stille, und man kann Anomalien wirklich überwachen.
ZFS – Konfiguration für NVMe und 64 GB RAM
ZFS möchte standardmäßig die Hälfte des RAMs für ARC verbrauchen. Das ist normal für einen Dateiserver und absolut kontraproduktiv für einen Hypervisor, wo der Speicher für virtuelle Maschinen benötigt wird. Wir reduzieren den ARC auf vernünftige 16 GB:
1cat > /etc/modprobe.d/zfs.conf << 'EOF'
2options zfs zfs_arc_max=17179869184
3options zfs zfs_arc_min=4294967296
4EOF
5update-initramfs -u
Speicherverteilung meinerseits:
- ~16 GB – ZFS ARC
- ~44 GB – VM/LXC
- ~4 GB – System, Puffer, Overhead
Nützliche Einstellungen des Pools selbst:
1zfs set compression=lz4 rpool
2zfs set atime=off rpool
3zfs set xattr=sa rpool
4zfs set dnodesize=auto rpool
5zfs set relatime=on rpool
6
7zfs set recordsize=64K rpool/data # für VM-Festplatten
8zfs set sync=standard rpool/data
9
10zpool set autotrim=on rpool # entscheidend für NVMe
compression=lz4 spart 20-30% Platz fast kostenlos für die CPU. recordsize=64K für VM-Blockgeräte – der beste Kompromiss zwischen Leistung und Schreibamplifikation.
Internes Netzwerk und Firewall
Ich habe eine zweite Bridge für das private Netzwerk mit NAT nach außen erstellt:
1auto vmbr1
2iface vmbr1 inet static
3 address 10.10.10.1/24
4 bridge-ports none
5 bridge-stp off
6 bridge-fd 0
7 post-up echo 1 > /proc/sys/net/ipv4/ip_forward
8 post-up iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE
9 post-down iptables -t nat -D POSTROUTING -s 10.10.10.0/24 -o vmbr0 -j MASQUERADE
Jetzt erhält jede VM/LXC in der Bridge vmbr1 eine lokale Adresse 10.10.10.x und geht über den Host ins Internet, ist aber von außen nicht sichtbar.
nftables mit policy drop. Nur das ist offen, was offen sein muss, der Rest wird still gedroppt. Minimale Konfiguration:
1table inet filter {
2 chain input {
3 type filter hook input priority 0; policy drop;
4
5 iif lo accept
6 ct state established,related accept
7 ip protocol icmp accept
8 ip6 nexthdr icmpv6 accept
9
10 tcp dport 22222 accept # SSH
11 tcp dport 8006 accept # Proxmox UI (nur aus NetBird, siehe unten)
12 tcp dport { 80, 443, 8080 } accept # Caddy
13 udp dport 29899 accept # NetBird
14
15 iif vmbr1 accept # internes Netzwerk – alles erlaubt
16 iif wt0 accept # NetBird-Schnittstelle – ebenfalls vertrauenswürdig
17 }
18
19 chain forward {
20 type filter hook forward priority 0; policy drop;
21 iif vmbr1 oif vmbr0 accept
22 ct state established,related accept
23 }
24}
Der Zugriff auf die Proxmox UI (Port 8006) ist physisch vom Internet über CF/Firewall gesperrt, und ich greife nur über NetBird darauf zu. Über die öffentliche IP reagiert die Oberfläche überhaupt nicht. Dasselbe gilt für PBS UI, Dashboards und alles Administrative – nur über das Mesh. Von außen sind nur die wirklich öffentlichen Ports der Dienste geöffnet.
NetBird Mesh: Warum nicht klassisches WireGuard?
Ich habe lange Zeit normales WireGuard benutzt, und das war in Ordnung, bis es mehr als drei Server gab. Danach wurde die Verwaltung von Konfigurationen zur Qual: Jede Peer-Hinzufügung wird zu „Konfiguration auf allen Nodes aktualisieren, allowed_ips nicht vergessen, Dämon neu starten, prüfen, ob die Route zum anderen Peer nicht kaputtgegangen ist“.
NetBird ist eine Management-Ebene über WireGuard. Technisch gesehen ist darunter immer noch dein übliches wg, aber:
- Nodes finden sich über einen Signalisierungsserver, NAT-Traversal ist automatisch
- Authentifizierung über SSO (bei mir – über Authentik), ohne Schlüsselteilung
- Der Zugriff wird über die Weboberfläche über Richtlinien gesteuert, man kann Maschinen-Gruppen und Regeln zwischen ihnen erstellen
- Integrierter SSH-Proxy: Man kann über
ssh user@machine.netbird.cloudzugreifen und SSO-Authentifizierung erhalten (mit Genehmigung in Authentik). Auf der Node muss nicht einmal Port 22 nach außen geöffnet werden.
Ich habe den NetBird-Agenten installiert:
- Auf dem Proxmox-Host
- Auf jeder VM, die für den Administrator oder andere VMs sichtbar sein soll
- Auf der Ausgangs-Node
- Auf meinem Laptop
Das Ergebnis: Ich greife von meinem Laptop auf jede Maschine mit einem einzigen Befehl zu, ohne Portweiterleitung, ohne Firewall-Lücken, ohne zusätzliche Bastions-Hosts. Und es gibt kein offenes SSH auf einer der privaten VMs – sie sind alle nur im vmbr1 plus dem wt0-Mesh.
Virtuelle Maschinen vs. Container: Wie ich das für mich gelöst habe
Eine einfache Regel, die fast immer funktioniert:
| Wann | Was nehmen |
|---|---|
| Docker-Workloads | VM (Docker in LXC kann launisch sein, besonders mit overlay2) |
| Datenbanken ohne Docker | LXC (leichter, schneller, direkter Festplattenzugriff über ZFS) |
| Systemdienste (PBS, Monitoring, kleine Tools) | LXC |
| Anwendungen, die einen eigenen Kernel / Netzwerk-Tricks benötigen | VM |
| VPN-Knoten (WG, AmneziaWG) | LXC (minimaler Overhead) |
Am Ende kam es bei mir ungefähr so heraus:
Virtuelle Maschinen (KVM):
forum– Discourse im Standard-Launcher-Container. Docker darin.services– viele Kleinigkeiten: Webhook-Bots, KI-Bot, Abrechnungspanel, Vaultwarden, Uptime Kuma. Alles in Docker Compose, pro Ordner pro Dienst.legacy– altes PHP-Forum (IPS) mit nativem PHP-FPM und Caddy auf der VM. Wollte es nicht in Docker verpacken – zu viel Legacy, einfacher, es so zu lassen, wie es war.dokploy– Docker Swarm + Dokploy für benutzerdefinierte Anwendungen (Next.js, Postgres, Redis), Deployment über Git.
LXC:
pbs– Proxmox Backup Server (siehe unten).pg16,pg17,pg18– drei separate Postgres-Instanzen verschiedener Major-Versionen. Grund: Unterschiedliche Anwendungen benötigen unterschiedliche Major-Versionen, und ich möchte nicht jede einzeln hoch- und runterskalieren.dashboards– ein Container mit Homarr (Startseite), Uptime Kuma und Beszel Hub. Alle drei in einem Compose, weil sie logisch dasselbe tun – beobachten.
Jede VM erhält eine statische IP in 10.10.10.x (nach Bereichen, so ist es einfacher zu suchen), 2-8 vCPU-Kerne und 2-8 GB RAM je nach Last. Kerne können sicher überprovisioniert werden – ich habe insgesamt mehr Kerne für VMs zugewiesen, als physisch vorhanden sind, und das funktioniert hervorragend, solange niemand gleichzeitig an die CPU-Grenzen stößt.
Reverse Proxy: Zweistöckiger Caddy
Innerhalb von Proxmox habe ich Caddy auf dem Host installiert, nicht in einer VM. Warum? Weil er die öffentlichen Ports des dedizierten Servers abhören und anhand des Host-Headers zur richtigen VM weiterleiten muss, und dafür eine separate VM mit Portweiterleitung zu erstellen – das ist eine zusätzliche Ebene ohne Nutzen. Caddy ist leicht, in Go geschrieben, systemd-Unit, Konfiguration in einer Datei.
Dieser Host-Caddy hört nur auf Port 8080 HTTP und leitet an Backends über 10.10.10.x weiter. Kein TLS darauf.
1:8080 {
2 @forum host forum.example.com www.forum.example.com
3 handle @forum {
4 reverse_proxy http://10.10.10.111:80 {
5 header_up X-Forwarded-Proto https
6 header_up X-Real-IP {header.X-Real-IP}
7 header_up Host {host}
8 }
9 }
10
11 @panel host panel.example.com api.panel.example.com
12 handle @panel {
13 reverse_proxy http://10.10.10.111:8081 {
14 header_up X-Forwarded-Proto https
15 header_up X-Real-IP {header.X-Real-IP}
16 header_up Host {host}
17 }
18 }
19
20 @dokploy host app1.example.com app2.example.com
21 handle @dokploy {
22 reverse_proxy http://10.10.10.114:80 {
23 header_up X-Forwarded-Proto https
24 header_up X-Real-IP {header.X-Real-IP}
25 header_up Host {host}
26 }
27 }
28}
Und die TLS-Terminierung und Zertifikate – auf einer separaten Ausgangs-Node, in Docker, nach demselben Prinzip:
1{
2 email me@example.com
3}
4
5forum.example.com {
6 reverse_proxy http://100.x.y.z:8080 {
7 header_up X-Forwarded-Proto {scheme}
8 header_up X-Forwarded-For {remote_host}
9 header_up X-Real-IP {remote_host}
10 header_up Host {host}
11 }
12}
100.x.y.z – das ist die NetBird-Adresse des dedizierten Servers. Die Anfrage kommt über HTTPS an der Ausgangs-Node an, wird entschlüsselt, geht per HTTP über das NetBird-Mesh zum dedizierten Server, dort matcht der Proxmox Caddy anhand des Hosts und leitet zur internen VM weiter. Zwischen der Ausgangs-Node und dem dedizierten Server ist HTTP per Design – wir vertrauen dem Mesh, kein externer Traffic hier.
Warum überhaupt eine Ausgangs-Node?
Mehrere Gründe:
- Umgehung von Blockaden. Ein Teil der OVH-Subnetze wird in Russland gesperrt. Die Ausgangs-Node befindet sich bei einem Anbieter, dessen IP nicht blockiert wird. Die DNS aller öffentlichen Domains zeigen dorthin.
- TLS-Terminierung an einem Ort. Nur sie gibt Zertifikate aus, nur ihre IP ist für Let's Encrypt beim Challenge. Das vereinfacht alle DNS-Cleared Checks und ermöglicht es dem dedizierten Server, für 80/443 unsichtbar vom Internet zu sein.
- Zusätzliche Isolationsschicht. Wenn jemand anfängt, DDoS-Angriffe durchzuführen – er greift die Ausgangs-Node an, nicht den dedizierten Server. Auf der Ausgangs-Node habe ich minimale Daten, sie neu zu erstellen – 10 Minuten.
- Trennung von öffentlichem Frontend und Backend. Ich kann bedenkenlos Caddy-Updates durchführen / auf dem dedizierten Server experimentieren, während die tatsächliche Schnittstelle zum Internet separat und stabil ist.
Ein Nachteil: Jeder Anfrage werden ~5-15 ms durch den zusätzlichen Hop hinzugefügt. Für das Web ist das unmerklich.
Für einige Dienste gibt es auch direkt Caddy auf dem Host unter 80/443 ohne Ausgangs-Node – für die Domains, die ich nicht über den Ausgangspunkt weiterleiten muss (z.B. Status-Endpunkte, Monitoring, Dienste, bei denen DNS sowieso auf den dedizierten Server zeigt). Das funktioniert parallel: Caddy auf dem Host hört sowohl :8080 (intern von der Ausgangs-Node) als auch :80/:443 (direkt aus dem Internet), die Routen sind unterschiedlich.
Migration: Wie ich ohne Tränen und fast ohne Ausfallzeit umgezogen bin
Das war die beängstigendste Phase. Fünf Server, etwa dreißig Dienste, ein Produktionsforum mit aktiven Nutzern, Abrechnung mit aktiven Abonnements, Datenbanken, die auf keinen Fall verloren gehen dürfen.
Strategie – Migration eines Dienstes nach dem anderen, in der Reihenfolge „vom am wenigsten kritischen zum kritischsten“:
- Zuerst zogen die kleinen Tools und statischen Websites um (wenn etwas schiefgeht – niemand wird es bemerken).
- Dann die Bots und Hintergrunddienste (auch sie verkraften eine Minute Ausfallzeit).
- Dann die Datenbanken (mit zuvor überprüfter Wiederherstellung).
- Dann die Anwendungen, die auf diese Datenbanken zugreifen.
- Ganz am Ende – das Hauptforum mit einer langen finalen Synchronisationsrunde und DNS-Umschaltung.
Migrationskanal
Zuerst habe ich einfach NetBird installiert auf den alten Servern. Das löste sofort zwei Probleme: Ich kann sicher per SSH auf die internen Netzwerke zugreifen, und rsync läuft über WireGuard über NetBird, ohne die Daten im offenen Internet preiszugeben.
Das Arbeitspferd für jeden Dienst war ungefähr so:
1# Vom neuen dedizierten Server, über die NetBird-IP des alten Servers:
2rsync -avzP --delete \
3 -e "ssh -p 5322" \
4 root@100.76.108.210:/var/discourse/shared/standalone/ \
5 /target/discourse/shared/standalone/
Für Datenbanken – Dump + Restore, keine Dateikopie. Kopieren Sie niemals Postgres/MySQL-Dateien „heiß“, das ist ein Ticket in die Hölle.
1# Auf dem alten
2docker exec -it shm-vsem-mysql mysqldump -u root -p shm-vsem | \
3 gzip > /tmp/shm-vsem.sql.gz
4
5# Auf dem neuen, über NetBird
6scp -P 5322 root@100.76.108.210:/tmp/shm-vsem.sql.gz /tmp/
7zcat /tmp/shm-vsem.sql.gz | docker exec -i shm-vsem-mysql mysql -u root -p shm-vsem
Für Discourse – die native discourse backup / discourse restore über ./launcher enter app. Intern sammelt es korrekt den Dump von Postgres + Uploads + Konfigurationen und stellt sie genauso wieder her.
DNS-Umschaltung
Nachdem der Dienst am neuen Ort gestartet und seine Funktionalität über die interne Adresse überprüft wurde – habe ich beide Installationen für 5-15 Minuten parallel laufen lassen, um die Datensynchronität zu beobachten, und dann das DNS auf den neuen Server umgeschaltet. Die TTL der Einträge hatte ich vorsorglich einen Tag vor der Umschaltung auf 60-300 Sekunden reduziert.
Für Discourse, bei dem aktive Benutzer ständig posten, habe ich es so gemacht:
- Alles am neuen Ort eingerichtet.
- Validierung durchgeführt (Login, Posten, Upload von Dateien nach S3, Suche).
- Auf der alten Installation den
read_only_modeaktiviert (Discourse kann das out-of-the-box). - Einen finalen rsync der Uploads + einen finalen Datenbank-Dump erstellt.
- Auf dem neuen gestartet, Read-only deaktiviert.
- DNS umgeschaltet.
- Auf der alten Installation nichts mehr angefasst für eine Woche – für den Fall der Fälle.
Die tatsächliche Ausfallzeit für Benutzer – etwa zwei Minuten pro Dienst.
Überraschungen unterwegs
- Berechtigungen bei
pct restore. LXC unterunprivileged 1bildet UIDs mit einem Offset von 100000 ab. Wenn auf dem alten Server Dateien unter UID 1000 waren – auf dem neuen sind sie unter UID 101000, und die Anwendung findet sie nicht. Löst sich entweder durchchownnach dem Restore oder--unprivileged 0, wenn Sie die Risiken verstehen. - Docker IPv6 in OVH-VMs startete manchmal fehlerhaft – behoben durch Deaktivieren von IPv6 im Docker-Daemon (
"ipv6": false). - Zeit auf VMs. Mehrfach Abweichungen der Systemzeit zwischen Host und VM festgestellt, was TLS und Signaturen brach. Heilmittel –
systemd-timesyncdoderchronyauf jeder VM undhost.use-timeim Proxmox-Agenten. - Discourse-Migration zwischen zwei Versionen. Wenn Discourse auf dem alten Server neuer ist als das, was Sie auf dem neuen installiert haben – schlägt
restorefehl. Die Version muss auf der neuen Installation gleich oder neuer sein. - Image Registry für Dokploy. Als ich die Metadaten von Dokploy über einen
dokploy-postgres-Dump übertrug, blieben in der Datenbank Links zum alten lokalen Registry mit der alten IP. Dienste fielen beim Start mitNo such imageaus, weil sie auf100.76.117.115:5000zugriffen. Lösung – jedes Anwendung in Dokploy UI neu bauen; der lokale Build legt das Image bereits auf dem neuen Server ab.
Backups: PBS innerhalb desselben dedizierten Servers + Offsite
Hier gab es eine separate philosophische Diskussion mit mir selbst: soll man auf derselben Hardware sichern oder nicht? Antwort: Sowohl ja als auch nein.
Level 1 – ZFS-Snapshots auf demselben Pool. Sie sind fast kostenlos (Copy-on-Write), werden sofort erstellt, sofort wiederhergestellt. Ich halte automatische Snapshots von rpool/data alle 15 Minuten mit einer Aufbewahrungsfrist von 24 Stunden und tägliche mit einer Aufbewahrungsfrist von einer Woche. Verwendet wird zfs-auto-snapshot. Schutz vor „Oh, ich habe gerade die Produktions-DB gelöscht“:
1apt install zfs-auto-snapshot
2# Danach erstellt es selbst über Cron frequent/hourly/daily/weekly/monthly
Level 2 – Proxmox Backup Server in einem separaten LXC auf demselben Host. Das sind vollwertige inkrementelle Backups von VM/LXC über vzdump, dedupliziert auf Chunk-Ebene. PBS speichert Snapshots in einem separaten ZFS-Datensatz, sieht sie als Repository, jede VM hat ihre eigene Kette von inkrementellen Backups.
Konfiguration in Proxmox: Datacenter → Backup → Add. Zeitplan: Jeden Tag um 4 Uhr morgens, Aufbewahrungsfrist keep-daily=7 keep-weekly=4 keep-monthly=3. Das alles per Maus.
Die Platzierung von PBS in LXC auf demselben Host – ein Kompromiss. Vorteil: Keine separate Hardware benötigt, Backup läuft über localhost, Geschwindigkeit wie bei lokalem SSD, Retention/Dedup funktioniert perfekt. Nachteil: Wenn der dedizierte Server komplett ausfällt – fallen auch die Backups aus. Deshalb gibt es...
Level 3 – Synchronisation des PBS-Repos nach Offsite S3. PBS unterstützt sync job in S3-kompatiblem Speicher. Jede Nacht übertrage ich die Snapshots in einen separaten Bucket in Selectel S3 (kann jeder sein – Backblaze, Wasabi, Cloudflare R2). Die Aufbewahrungsfrist dort – zwei Wochen, denn mehr ist nicht nötig: für die Langzeitaufbewahrung gibt es das lokale PBS, und Offsite ist eine Versicherung gegen „ganzes Rechenzentrum abgebrannt“.
Level 4 – Anwendungsdaten separat. Discourse selbst erstellt seine Backups und legt sie in S3 ab (das ist eine native Funktion). So habe ich selbst dann noch konsistente Discourse-Dumps in deren Cloud, wenn PBS und sogar der gesamte dedizierte Server ausfällt.
1# Wiederherstellung einer ganzen VM aus PBS – buchstäblich ein Befehl:
2pvesm list backup-pbs # um zu sehen, was vorhanden ist
3qmrestore backup-pbs:backup/vzdump-qemu-201-2026_04_06-04_00_03.vma.zst 999 \
4 --storage local-zfs
5# und nach einer Minute läuft auf Ihrer VMID 999 eine Kopie des Forums-VM vom Zeitpunkt 4 Uhr morgens.
Das ist ein kritischer Punkt: Backups, die Sie nicht wiederherzustellen versucht haben – sind keine Backups. Ich mache einmal im Monat einen Test-Restore einer zufälligen VM auf eine leere VMID, prüfe, ob sie startet, ob die Anwendung darin funktioniert, und lösche sie. Langweilig, aber einmal fand ich so heraus, dass einer der Cronjobs nach /tmp/... schreibt, was beim Backup übersprungen wird, und die Anwendung nach dem Restore einen manuellen Schritt erfordert.
Monitoring
Ich sammle keine Komplexe wie Prometheus + Grafana + Alertmanager + Loki, wenn ich 15 Maschinen habe. Deshalb habe ich mich für drei leichte Tools in einem LXC entschieden:
- Beszel – sammelt Metriken von Agenten auf jeder VM (CPU, RAM, Festplatten, Netzwerkschnittstellen, Docker-Container). Der Hub läuft in LXC, die Agenten – auf jeder VM/LXC über systemd-Unit. Auth bei Beszel ist eigener, über PocketBase, was manchmal unpraktisch ist (siehe unten wegen Stolperstein), aber im Allgemeinen funktioniert.
- Uptime Kuma – Checks über HTTP/HTTPS/Ping/TCP. Ich habe dort alles öffentliche (Domains), alle internen Dienste (über NetBird) und Pings zu allen Mesh-Nodes eingetragen. Alarme – in Telegram.
- Homarr – Startseite mit Links zu allen Admin-Oberflächen. Um nicht vergessen zu müssen, über welchen Port die PBS UI erreichbar ist, über welchen Dokploy, über welchen Vaultwarden. Ich öffne einfach Homarr und klicke.
Alle drei in einem Docker Compose, in einem LXC, nur über NetBird erreichbar.
Stolperstein mit Beszel, damit Sie nicht treten: Beszel hat zwei Benutzertabellen in PocketBase –
_superusers(für CLI/API) undusers(für die Weboberfläche). Wenn Sie das Passwort über die CLIsuperuser upsertzurücksetzen – setzen Sie nur_superuserszurück, und in der UI melden Sie sich überusersan und das Passwort passt nicht. Lässt sich durch einen PATCH-Request an/api/collections/users/records/<id>über die REST-API beheben.
Was ich letztendlich erhalten habe
Nach etwa drei Wochen Migration, als die letzten Dienste stabil liefen, zog ich Bilanz:
| Parameter | Vorher | Nachher |
|---|---|---|
| Server / Rechnungen | 5 bei 3 Hostern | 1 dedizierter Server + 1 Mikro-VPS |
| monatliche Kosten | ~$X | ~$X/2 |
| Freie Ressourcen | „scheint zu reichen“ | 8 vCPU und 30 GB RAM Reserve |
| Backups | rsync-Cronjob in S3 auf jeder VPS | PBS + Offsite + ZFS-Snapshots |
| Wiederherstellung | „nun, ein Tag irgendwo“ | 2-5 Minuten pro VM aus PBS |
| SSH-Zugriff | 5 verschiedene Ports und Schlüssel | ein NetBird-Mesh mit SSO |
| Dienstisolierung | gemeinsame Müllhalde | jede in ihrer eigenen VM/LXC |
| Snapshot vor Upgrade | „bete“ | qm snapshot 201 pre-update |
| Weboberfläche für Routineaufgaben | welche Weboberfläche? | Proxmox UI |
Das Wichtigste, was mir das emotional gegeben hat – ich habe keine Angst mehr, etwas anzufassen. Jede gefährliche Aktion (Upgrade, Migration, Experiment) beginnt jetzt mit einem Snapshot und endet entweder mit einem Commit oder einem Rollback in 30 Sekunden. Ich habe angefangen, öfter Neues auszuprobieren, weil die Kosten für Fehler um eine Größenordnung gesunken sind.
Checkliste, falls Sie es wiederholen möchten
Wenn Sie sich bei sich selbst auf dieselben Symptome wie bei mir ertappen – hier ist eine kurze Checkliste, in welcher Reihenfolge es sinnvoll ist, vorzugehen:
- Berechnen Sie, wie viel Sie für alle VPS zusammen bezahlen, und vergleichen Sie es mit den Preisen für dedizierte Server bei OVH/Hetzner/LeaseWeb. Sie werden überrascht sein.
- Nehmen Sie ECC RAM, wenn Sie ZFS planen. Sparen Sie nicht.
- Nehmen Sie mindestens zwei Festplatten im Spiegel. Eine einzelne Festplatte ist keine Option für den Produktionseinsatz.
- Installieren Sie Proxmox über KVM/IPMI, nicht über Rescue + Debootstrap. Weniger Schmerzen.
- Sofort SSH-Schlüssel einrichten, nicht standardmäßigen Port verwenden, Passwörter deaktivieren, fail2ban installieren.
- Sofort ZFS ARC begrenzen. Standardmäßig frisst er die Hälfte des RAMs.
- Erstellen Sie eine zweite Bridge
vmbr1für das private Netzwerk mit NAT. Alle VMs/LXCs dorthin. Keine öffentlichen IPs auf den virtuellen Maschinen. - Richten Sie NetBird (oder Tailscale, oder Headscale) ein, bevor Sie mit der Migration beginnen. Das ist Ihr Migrationskanal und Admin-Zugriff.
- Öffnen Sie die Proxmox UI nicht nach außen. Nur über VPN-Mesh.
- Eine kleine ausgehende VPS bei einem anderen Anbieter für TLS-Terminierung und Umgehung von Blockaden – nicht zwingend erforderlich, aber sehr nützlich. Die Kosten sind gering, die Vorteile zahlreich.
- PBS in separatem LXC + Offsite Sync in S3. Backups müssen automatisch erfolgen.
- Machen Sie mindestens einmal im Monat einen Test-Restore. Sonst sind es keine Backups, sondern Dateien zur Selbstberuhigung.
- Migrieren Sie einen Dienst nach dem anderen, in aufsteigender Reihenfolge der Kritikalität. Versuchen Sie nicht, „alles am Wochenende zu verlegen“.
- Reduzieren Sie die DNS-TTL einen Tag vor der Umschaltung, nicht eine Stunde.
- Führen Sie Dokumentation für jeden Dienst: wo die Konfigurationen sind, wie man startet, wie man sichert, wie man wiederherstellt. Nach einem halben Jahr werden Sie sich selbst danken.
Was noch zu tun bleibt
Der Vollständigkeit halber: Die Migration ist bei mir noch nicht zu 100 % abgeschlossen. Übrig bleiben:
- Ein paar Remnawave-Panels vom alten Server (geplant für nächstes Wochenende).
- Übertragung von Caddy mit Plugins (dort eine benutzerdefinierte Kompilierung), funktioniert derzeit nur temporär.
- Alter CrowdSec – wird separat neu installiert, nicht migriert.
Aber alles Kritische (Foren, Abrechnung, Bots, Datenbanken) – ist bereits auf dem dedizierten Server, läuft stabil, wird gesichert und überwacht.
Wenn Sie Fragen zu bestimmten Schritten, Konfigurationen oder Stolpersteinen haben, auf die ich detaillierter getreten bin – schreiben Sie in die Kommentare, ich versuche, sie zu erklären. Besonders interessiert mich zu hören, wie Sie ähnliche Aufgaben auf anderen Hypervisoren (XCP-ng, ESXi oder generell auf nacktem Docker Swarm) lösen – vielleicht übersehe ich etwas.
Viel Erfolg bei der Konsolidierung Ihrer Infrastrukturen. Weniger Rechnungen, mehr Kontrolle – das ist es wert.