Headless ComfyUI: Als Dienst betreiben, von überall nutzen

Veröffentlicht
11 Juli 2026
Aktualisiert
11 Juli 2026
Von
Jacob Lloyd — mit KI-Unterstützung geschrieben, im Nachhinein
Lesezeit
8 Min. Lesezeit

Kurz gesagt: Mein Bildgenerierungs-Rechner betreibt ComfyUI dauerhaft im Hintergrund, ohne Monitor und ohne dass jemand an einem Desktop angemeldet ist. Ich kann ihn per Ein-Zeilen-Befehl starten, prüfen oder um ein Bild bitten, und ich kann die volle Oberfläche von meinem Laptop oder Smartphone aus über eine private, verschlüsselte Verbindung öffnen. Ein kleines Plugin, das ich gebaut habe, sorgt dafür, dass jeder Workflow, den ich von einem beliebigen Gerät speichere, auf genau diesem einen Rechner landet, sodass nichts verstreut wird.

Im letzten Artikel habe ich ComfyUI + Z-Image Turbo dazu gebracht, auf einem AMD-Mini-PC ein 1024px-Bild in ~27 Sekunden zu erzeugen. Das hier ist die Fortsetzung: diese Installation in ein dauerhaft laufendes Gerät zu verwandeln — keine Desktop-Sitzung, kein Überwachen im Terminal — das jedes Gerät im Haus nutzen kann, während alles, was es erzeugt, und jeder gespeicherte Workflow auf genau dieser einen Box bleibt.

Kurzfassung

  • Was es ist: ComfyUI, das headless unter einem systemd-Benutzerdienst läuft, gesteuert von einer kleinen CLI, per Skript über die HTTP-API angesprochen und von anderen Geräten über ein Mesh-VPN mit HTTPS erreicht.
  • Was es kostet: kostenlos. Auch die VPN-Stufe, die das leistet (Tailscale Personal), ist kostenlos.
  • Was du brauchst: eine funktionierende ComfyUI-Installation und etwa einen Abend Zeit.
  • Was dabei herauskommt: comfyctl generate "..." aus jeder Shell, die volle UI auf Laptop oder Smartphone, und eine Workflow-Bibliothek, die in einem einzigen Ordner auf dem Server lebt, egal von welchem Gerät gespeichert wurde.

Was dabei herauskommt

Im Alltag sieht "die Bildmaschine benutzen" wie eines von drei Dingen aus, bei keinem davon muss man davorsitzen:

comfyctl status     # Serverzustand + systemd-Status + neueste Ausgabedatei
comfyctl generate "a foggy harbor at dawn, cinematic"
comfyctl logs 100

…oder ein Skript auf einer anderen Maschine, das einen Prompt per POST an die HTTP-API schickt, oder die volle Node-Graph-UI in einem Browser-Tab auf meinem Laptop am anderen Ende des Hauses — über HTTPS, durch das VPN, mit Speicherungen, die auf der Festplatte des Servers landen.

Warum headless

Die Web-UI ist der richtige Ort, um einen Workflow zu entwerfen. Sie ist ein miserabler Ort, um denselben Workflow zum 200. Mal auszuführen. Sobald ein Workflow steht, will man eigentlich ein Gerät: eine Box, die immer an ist, immer bereit, und auf alles antwortet, was fragt — einen Shell-Einzeiler, einen Cron-Job, einen Chatbot, der um 3 Uhr morgens einen frischen Avatar braucht.

Headless bedeutet hier nicht "gar keine UI mehr". Der Server hat keine eigene Desktop-Sitzung und keinen eigenen Browser, aber die UI existiert weiterhin, ausgeliefert per HTTP an jedes Gerät, das sie haben will. Der Server erledigt die Arbeit, alles andere ist nur ein Bildschirm.

Der systemd-Benutzerdienst

Das Muster für den Dienst ist dasselbe wie im Einrichtungsartikel, deshalb kurz gefasst. Eine systemd-Benutzereinheit unter ~/.config/systemd/user/comfyui.service:

[Unit]
Description=ComfyUI server
After=network-online.target

[Service]
ExecStart=%h/comfy/start-comfyui.sh
WorkingDirectory=%h/comfy/ComfyUI
EnvironmentFile=-%h/comfy/comfy.env
Restart=on-failure
RestartSec=5
TimeoutStartSec=120

[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now comfyui.service
loginctl enable-linger $USER     # Benutzerdienste weiterlaufen lassen, ohne dass jemand angemeldet ist

Diese letzte Zeile ist der headless-spezifische Teil, den man leicht übersieht: Ohne Linger stoppen Benutzereinheiten, sobald die letzte Sitzung endet — auf einer Headless-Box bedeutet das "sofort". Die EnvironmentFile hält Port und Startflags an einem editierbaren Ort (comfy.env), sodass andere Tools denselben Wert lesen können, statt 8188 fest zu verdrahten.

Ein comfyctl-artiges Steuerskript

Rohes systemctl funktioniert, aber ein dünner Wrapper lässt die Box sich wie ein Produkt anfühlen. Meiner sind ~100 Zeilen Bash namens comfyctl:

comfyctl start      # systemctl start + Poll auf /system_stats, bis gesund
comfyctl stop
comfyctl restart
comfyctl status     # Gesundheit + Unit-Status + neueste Ausgabe-PNG
comfyctl generate "<prompt>" [--seed N --steps 8 --width 1024 --out PATH]
comfyctl logs [N]   # journalctl --user -u comfyui.service -n N

Die Details, die es angenehm statt nur kurz machen:

  • Gesundheit bedeutet, dass die API antwortet, nicht dass der Prozess existiert. curl -sf http://127.0.0.1:$PORT/system_stats in einer Schleife mit 60 Sekunden Budget. systemd sagt "active", sobald nur Python gestartet ist; ein Modellserver ist erst "wach", wenn er auf HTTP antwortet.
  • Es liest den Port aus derselben Env-Datei, die auch der Dienst verwendet, sodass eine Portänderung an einer Stelle das Tool nicht abhängen lässt.
  • generate startet den Server automatisch. Schlägt der Gesundheitscheck fehl, startet es die Einheit, wartet auf Gesundheit und reicht dann ein. Aufrufer müssen nie wissen, ob die Box wach war.
  • status zeigt die neueste Datei in output/ — die Antwort auf "hat meine letzte Generierung tatsächlich funktioniert", ohne irgendetwas zu öffnen.

Die HTTP-API aus Skripten heraus ansteuern

Alles, was die UI tut, läuft über dieselbe HTTP-API, und die Kernschleife besteht aus drei Endpunkten. Ein Workflow ist einfach JSON (Export über Save (API format) in der UI); ein Skript lädt ihn, tauscht Prompt und einen frischen Seed aus, und:

import json, time, urllib.request

SERVER = "http://127.0.0.1:8188"
graph = json.load(open("workflow_api.json"))
graph["6"]["inputs"]["text"] = "a foggy harbor at dawn, cinematic"   # deine Prompt-Node-ID

# 1. einreihen
req = urllib.request.Request(f"{SERVER}/prompt",
    data=json.dumps({"prompt": graph}).encode(),
    headers={"Content-Type": "application/json"})
pid = json.load(urllib.request.urlopen(req))["prompt_id"]

# 2. Verlauf abfragen, bis fertig
while True:
    hist = json.load(urllib.request.urlopen(f"{SERVER}/history/{pid}"))
    if pid in hist: break
    time.sleep(1)

# 3. Bild abholen
img = hist[pid]["outputs"]["9"]["images"][0]           # deine SaveImage-Node-ID
url = f"{SERVER}/view?filename={img['filename']}&subfolder={img['subfolder']}&type={img['type']}"
open("result.png", "wb").write(urllib.request.urlopen(url).read())

Nur Standardbibliothek — kein ComfyUI-Client-Paket, kein API-Schlüssel. Das ist das Muster hinter comfyctl generate, und es ist das Stück, das die Box zu Infrastruktur macht: Alles, was eine HTTP-Anfrage stellen kann, kann jetzt Bilder machen.

Das Workbench-Plugin: deine Workflow-Bibliothek lebt auf der Box

Hier ist das Problem, das schon in der ersten Woche der Fernnutzung auftauchte. ComfyUIs eingebauter Export speichert das Workflow-JSON über den Download-Mechanismus des Browsers — auf welchem Gerät auch immer man gerade surft. Nutzt man die UI von drei Geräten aus, ist die Workflow-Bibliothek über drei Downloads-Ordner verstreut, von denen keiner die Maschine ist, die die Workflows tatsächlich ausführt.

Also habe ich einen kleinen Custom-Node ("Workbench") gebaut, der die Richtung der Schwerkraft korrigiert. Es ist ein ganz normales custom_nodes-Plugin: Die Python-Seite registriert ein paar Routen auf ComfyUIs eigenem Webserver, die JS-Seite fügt einen Sidebar-Tab hinzu. Es erledigt zwei Aufgaben:

1. Speichern/Öffnen im Arbeitsordner

Der Speichern-Button in der Sidebar schickt den aktuellen Graphen per POST an das Plugin, das ihn in ComfyUIs kanonischen user/default/workflows-Ordner auf dem Server schreibt — bereinigter Dateiname, auf diesen Ordner beschränkt, atomares Schreiben (temporäre Datei + Umbenennen) und ein rollierendes .bak.json von allem, was überschrieben wird. "Öffnen" listet denselben Ordner, neueste zuerst.

Der entscheidende Punkt: Die UI läuft in einem entfernten Browser, aber das Speichern geht auf die lokale Festplatte des Servers. Vom Laptop speichern, vom Smartphone öffnen, per Skript ausführen — eine Bibliothek, ein Ordner, auf der Maschine mit der GPU. (Auf meiner Box ist dieser Ordner symbolisch mit einem einfachen ~/comfy/workflows-Verzeichnis verknüpft, sodass er auch trivial zu sichern ist.) Da das Frontend nur ursprungsrelative Aufrufe an den Server macht, von dem es geladen wurde, verhält es sich auf localhost und über einen Proxy identisch — keine Konfiguration pro Gerät.

2. Scan fehlender Modelle + Downloads mit Allowlist

Der andere Schmerzpunkt bei Fernnutzung: Man öffnet einen Workflow, und der will eine Modelldatei, die die Box nicht hat. 12 GB aufs Smartphone herunterzuladen und wieder hochzuladen ist offensichtlich falsch. Das Plugin scannt den geladenen Workflow gegen jeden registrierten Modellordner, listet auf, was fehlt, und lädt es serverseitig, direkt in den richtigen models/<ordner> herunter — gestreamt in eine .part-Datei, fortsetzbar per HTTP-Range, optionale sha256-Verifizierung, mit Fortschrittsanzeige, die per Websocket an die UI gepusht wird.

Weil "der Browser kann dem Server befehlen, beliebige URLs auf die Festplatte herunterzuladen" ein beängstigender Satz ist, ist das eingezäunt:

  • Nur HTTPS, und der Hostname muss zu einer Allowlist in config.json passen (z. B. huggingface.co, civitai.com, Wildcards wie *.hf.co) — und jeder Redirect-Hop wird erneut gegen dieselbe Liste geprüft, da Redirect-zu-irgendwohin der klassische Weg ist, eine Allowlist zu umgehen.
  • Dateinamen werden auf einen bereinigten Basisnamen reduziert und müssen mit einer erlaubten Endung (.safetensors etc.) enden; das Ziel muss ein registrierter Modellordner sein — keine Pfad-Traversal.
  • Bestehende Dateien werden nie überschrieben, es gibt eine Größenobergrenze pro Download (standardmäßig 30 GB), und Downloads werden nacheinander in einer Warteschlange abgearbeitet.

Sicherer Fernzugriff: Mesh-VPN + HTTPS

ComfyUI auf dieser Box lauscht nur auf 127.0.0.1 — fest verdrahtet, kein Flag. Es gibt keine Login-Seite, also darf es niemals dem Internet ausgesetzt werden, und ehrlich gesagt auch nicht dem rohen LAN. Die saubere Antwort ist ein Mesh-VPN wie Tailscale: Jedes Gerät bekommt eine private, verschlüsselte Adresse, nichts ist von außerhalb des eigenen Kontos erreichbar, und dessen serve-Funktion arbeitet als lokaler HTTPS-Reverse-Proxy:

tailscale serve --bg --https=8443 http://127.0.0.1:8188

Jetzt liefert https://<deine-maschine>.<dein-tailnet>.ts.net:8443 die volle UI, mit einem echten (automatisch ausgestellten) TLS-Zertifikat, nur an die eigenen Geräte aus. ComfyUI selbst hört immer noch ausschließlich auf localhost — der Proxy ist die einzige Tür.

HTTPS ist hier keine optionale Höflichkeit. Browser schränken auf reinem HTTP von Nicht-localhost-Ursprüngen immer mehr ein (Zwischenablage, manches Worker-/Websocket-Verhalten), und man schickt seine Prompts und Bilder übers Netzwerk. Das Mesh-VPN gibt einem ein echtes Zertifikat, ohne eine Domain zu besitzen oder einen Port zu öffnen. Ein praktischer Hinweis: über den DNS-Namen der Maschine verbinden, nicht über die rohe VPN-IP — das TLS-Zertifikat wird für den Namen ausgestellt, die IP fällt also bei der Zertifikatsprüfung durch.

Fallstricke

  • 403er, die nur remote auftreten, sind Origin-Checks, keine Authentifizierung. ComfyUIs Server-Middleware validiert Origin/Host und Verwandtes bei zustandsändernden Anfragen; manche Proxy-Konfigurationen leiten Header weiter, die nicht zu dem passen, was erwartet wird (genau das ist mir beim Verkabeln eines anderen Tools passiert — ein 403 wegen Sec-Fetch-Site). Die weitergeleiteten Header des Proxys korrigieren, oder ComfyUIs CORS-Einstellungen (--enable-cors-header) auf den einen vertrauenswürdigen Ursprung beschränken — niemals *.
  • Den VPN-Hostnamen verwenden, nicht die IP. HTTPS über das Mesh-VPN ist zertifikatsgebunden pro Name; die rohe Adresse anzusprechen liefert TLS-Fehler, die aussehen, als sei der Server kaputt.
  • Frontends von Custom-Nodes müssen ursprungsrelativ sein. Jedes fest verdrahtete http://127.0.0.1:8188/... im JS eines Plugins funktioniert auf der Box und bricht stillschweigend durch den Proxy. ComfyUIs api.fetchApi() und relative Pfade verwenden.
  • Websockets über einen Proxy können abbrechen. Fortschrittsereignisse laufen über den Websocket; hinter einem Proxy stirbt er gelegentlich lautlos. Workbench pollt genau deshalb als Fallback — dafür designen.
  • loginctl enable-linger vergessen, und der "Dienst" stirbt beim Abmelden.
  • "Unit ist active" ≠ "Server ist wach."
  • Die Download-Allowlist eng halten.
  • Das niemals per Port-Forwarding ins Internet stellen. Keine Authentifizierung + beliebige Workflow-Ausführung + (mit einem Downloader) Schreibzugriff auf die Festplatte. Mesh-VPN oder gar nichts.

Wo der erste Artikel mit einem Rechner endete, der Bilder erzeugen kann, endet dieser mit etwas Besserem: einer stillen Box im Regal, die jedes Gerät und Skript, das ich besitze, nutzen kann — und die alles, was sie erzeugt, an einem Ort aufbewahrt.


← Mehr KI & lokale LLMs