Der Edge lief. Eigentlich hätte ich ihn einfach in Ruhe lassen können.
Auf dem Proxmox-LXC lief mein Edge-Hosting seit Monaten stabil: Debian als Basis, verwaltet mit Ansible, davor OpenResty mit nativem HTTP/3 und dahinter getrennte PHP-8.4-FPM-Pools pro Domain. Die VHosts wurden sauber aus group_vars/sites_php.yml und group_vars/sites_proxy.yml generiert.
Ein funktionierendes Setup.
Beim routinemäßigen Aufräumen bin ich dann über ein paar Dinge gestolpert.
In sites-available lagen noch drei alte Nginx-VHosts. Dazu zwei PHP-Pools und Zertifikatsreste von Diensten, die ich vor über einem Jahr abgestellt hatte. Ansible hatte über die Jahre genau das getan, was in den Playbooks stand. Aber was ich irgendwann aus den Variablen gelöscht hatte, existierte auf dem Host einfach weiter.
Da habe ich mich gefragt, ob Ansible für genau diesen Server noch das richtige Werkzeug ist. Orchestrierung löscht nicht automatisch das, was du nicht mehr erwähnst.
Der Unterschied zwischen Konvergenz und Deklaration
Ansible beschreibt gewünschte Zustände und arbeitet weitgehend idempotent. Wenn eine Variable definiert ist, sorgt das Playbook dafür, dass Datei, User und Service vorhanden sind.
Das Problem liegt auf der anderen Seite: Wenn ich eine Domain aus meiner Variablen-Datei entferne, weiß das Zielsystem nichts davon. Ansible führt die Tasks für diese Domain schlicht nicht mehr aus. Der VHost bleibt in /etc/nginx/sites-enabled/ liegen, der System-User bleibt im Betriebssystem, und der PHP-Pool läuft weiter, bis man ihn von Hand aufräumt oder explizit state: absent dafür baut.
Auf einem klassischen, persistenten Host kann sich so über die Jahre erstaunlich viel Drift ansammeln.
Klassisches Linux: Systemkonfiguration = Basis-OS + Updates + Historie aller Änderungen
NixOS: Systemkonfiguration = reproduzierbares Ergebnis der aktuellen Flake-Konfiguration
Deshalb habe ich den Schnitt gemacht und den Edge im August auf NixOS 26.05 migriert.
Die Architektur auf dem Edge
Am Netzwerkfluss hat sich nichts geändert. Nach außen steht die OPNsense-Firewall, die TCP 80/443 und UDP 443 (für HTTP/3 QUIC) an den internen Edge-Container weiterleitet.
WAN ──► OPNsense (NAT TCP/UDP 443) ──► NixOS Edge LXC
├── OpenResty (HTTP/3 + Lua)
├── PHP-FPM Pools (nerdbear, bearwave, ...)
└── Upstream Proxies (Forgejo, Vaultwarden)
Der Unterschied liegt darin, wie der Host definiert wird. Die gesamte Betriebssystem-Konfiguration liegt in einem Git-Repository:
hosts/edge.nix: Host-Definition, SSH-Keys, Firewall-Regeln und globale Dienste.sites/php.nix: PHP-Sites als strukturierte Attribute.sites/proxy.nix: Reverse-Proxy-Ziele inklusive Puffer- und Timeout-Werte.sites/shared.nix: Redirects und Aliase.
Mehr Abstraktion wollte ich an der Stelle bewusst nicht. Eine PHP-Site wie nerdbear.de oder bearwave.app ist in Nix nur ein kompakter Datensatz:
"nerdbear.de" = {
type = "php";
user = "nerdbear";
phpVersion = "8.4";
deployRootSubdir = "index";
docrootSubdir = "index/public";
tls = {
enable = true;
mode = "external";
certName = "nerdbear.de";
};
};
Daraus generiert NixOS den System-User nerdbear, die Systemd-Unit für den PHP-8.4-Pool mit Unix-Socket unter /run/phpfpm/nerdbear.de.sock und die OpenResty-Konfiguration.
Wenn ich diesen Block aus der Datei lösche und nixos-rebuild switch --flake .#edge ausführe, werden VHost, Systemd-Unit, Socket und die deklarativ erzeugten Systembestandteile mit der neuen Generation entfernt. Was nicht mehr konfiguriert ist, wird nicht mehr mitgeschleppt.
User-Isolation und Dateirechte
Ich halte nichts von globalen Webserver-Benutzern. Wenn auf einem Server zehn verschiedene Webseiten laufen, gehört jede einzelne in ein eigenes Gefängnis.
| Domain | Linux-User | PHP-FPM Socket |
|---|---|---|
nerdbear.de |
nerdbear |
/run/phpfpm/nerdbear.de.sock |
palencsar.de |
nerdbear (Shared Root) |
/run/phpfpm/nerdbear.de.sock |
Die Trennung zwischen deklarativer Systemkonfiguration und persistenten Nutzdaten ist dabei strikt:
- Site-Inhalte (Content): Liegen unter
/srv/www/<domain>/index/public/. Das Deployment läuft Git-basiert (Push-to-Deploy via Bare-Repository und Hook) als Benutzerragnar. - Gruppenrechte: Verzeichnisse unter
/srv/www/gehören dem Domain-User und der Gruppewebdeploymit SetGID (chmod 2775). Neu ausgecheckte Dateien erben automatisch die richtige Gruppenberechtigung. - Zertifikate: Werden nicht auf dem Edge erzeugt, sondern zentral über meinen PowerDNS-Host per ACME bezogen und unter
/etc/ssl/hosting/<cert>/abgelegt. Der Webserver liest sie über die Gruppetls-cert-readers.
Wo NixOS im Alltag nervt
NixOS ist kein System für spontane Experimente direkt auf dem Server. Wenn man von Debian kommt, stolpert man über Eigenheiten, die anfangs schlicht Umgewöhnung verlangen.
Kein Standard-FHS: Es gibt kein klassisches /usr/bin/php oder /etc/nginx. Die Binaries und Konfigurationen liegen unveränderlich und gehasht im /nix/store/....
In der Praxis bedeutet das:
-
Keine schnellen Hotfixes in
/etc: Mal eben in/etcherumeditieren und den Fix dort vergessen funktioniert nicht mehr. Die dauerhafte Änderung gehört zurück in die Nix-Konfiguration, danach folgt der Rebuild. -
Socket-Berechtigungen: OpenResty läuft als
webdeploy, PHP-FPM als jeweiliger Domain-User. Wenn die Socket-Berechtigungen im PHP-Modul nicht penibel auflisten.mode = 0660undlisten.group = webdeploystehen, antwortet Nginx beim ersten Request mit einem502 Bad Gateway. -
LXC auf Proxmox: In unprivilegierten Containern muss man das UID/GID-Mapping und die nötigen Systemd-Berechtigungen gegenüber dem Proxmox-Host sauber abgleichen, damit Sockets und Dateizugriffe ohne Reibung funktionieren.
Warum sich der Umstieg trotzdem gelohnt hat
Der Vorteil zeigt sich bei Updates und Wartung.
Wenn ich heute eine neue PHP-Version oder OpenResty-Konfiguration ausrolle und etwas hakt, aktiviert nixos-rebuild switch --rollback sofort die vorherige Systemgeneration. Pakete, Services und Konfigurationen springen in Sekunden auf den letzten funktionierenden Stand zurück. Die persistenten Daten unter /srv/www bleiben davon unberührt.
Sollte der Container zerstört werden, brauche ich kein Root-Dateisystem-Backup. Ich erstelle einen leeren NixOS-LXC, binde /srv/www sowie die Zertifikate ein, lasse den Flake durchlaufen — und der Edge steht in wenigen Minuten wieder exakt so da wie zuvor.
Der Wechsel war kein Hype-Projekt. Ich wollte ein System, das sich reproduzierbar kontrollieren lässt, ohne nach einem Jahr raten zu müssen, was auf dem Host eigentlich noch aktiv ist.