Guides

n8n auf Hetzner Cloud selbst hosten

Eine n8n-Instanz auf einem Server in Deutschland: Maschine wählen, Standort setzen, mit Docker Compose aufsetzen. Gemessen: 355 MB im Leerlauf, 16,6 Sekunden bis zur Anmeldeseite, und eine Sicherung, die beim Wiederherstellen bestanden hat.

André Loreth9 Min. Lesezeit

Stand
21. September 2026
n8n auf Hetzner Cloud selbst hosten
Teilen

Fast jede Anleitung zum Selbsthosten von n8n hört an der Stelle auf, an der die Oberfläche zum ersten Mal aufgeht. Was danach kommt, steht seltener da: die Sicherung, die Aktualisierung und die Frage, wer davon erfährt, wenn ein Ablauf still steht.

Was du danach in der Hand hast

  • Einschätzen, welche Maschine für deine Abläufe reicht
  • Eine n8n-Instanz mit Compose aufsetzen, mit einem Standort in Deutschland
  • Sie so sichern und aktualisieren, dass sie den ersten Ausfall übersteht, und die Sicherung einmal beweisen lassen, dass sie eine ist

Was Selbsthosten hier heißt und was nicht

Selbsthosten heißt, dass n8n auf einer Maschine läuft, für die du die Rechnung bekommst. Ein Rechner in einem Rechenzentrum, ein Konto beim Anbieter, ein Zugang über die Kommandozeile. Kein Tarif mit einem Kontingent an Ausführungen und keine Oberfläche, die jemand anders für dich betriebsbereit hält. Was n8n ist und was die Cloud-Tarife kosten, steht im Beitrag „Was ist n8n“.

Ein Ablauf ist bei n8n eine Kette von Knoten: ein Auslöser am Anfang, Daten dazwischen, ein Ergebnis am Ende. Kein Programm, das jemand von Hand startet, sondern etwas, das auf ein Ereignis wartet und dann von selbst durchläuft.

Nicht gemeint ist n8n auf dem eigenen Laptop. Das geht in fünf Minuten und ist für einen Nachmittag die schnellere Antwort. Ein Ablauf, der morgens um sieben ein Postfach abholt, braucht eine Maschine, die morgens um sieben läuft und nicht in einer Tasche liegt.

Was die Maschine können muss

Ein Ablauf, der viermal am Tag ein Postfach abholt, verbraucht fast nichts. Die Last entsteht an drei Stellen: beim Start der Container, bei Abläufen, die viele Datensätze auf einmal durch die Knoten schieben, und bei den Task Runnern.

Ein Task Runner ist ein eigener Prozess, der den Code aus einem Code-Knoten ausführt, getrennt vom Hauptprozess von n8n. Seit 2.0 läuft er ohne Zutun mit. Er ist keine zweite Maschine, sondern ein weiterer Prozess auf derselben, und er belegt Arbeitsspeicher.

Wie viel, haben wir am 19.09.2026 gemessen, allerdings nicht auf der Hetzner-Maschine, sondern mit derselben Compose-Datei in Docker Desktop 29.3.1 auf einem Arbeitsrechner, ohne Caddy. Zwei Minuten nach dem Start, ein importierter Ablauf, keine Ausführung, zweimal mit docker stats abgelesen:

Container    Prozessor   Arbeitsspeicher
n8n          0,4-0,7 %   355 MB  (Hauptprozess 329 MB, Task Runner 121 MB RSS)
postgres     0,0 %        41 MB

Rund 400 MB für die Instanz im Leerlauf. Auf einer Maschine mit 4 GB bleibt damit Luft für das System, für Caddy und für Abläufe, die ein paar hundert Datensätze auf einmal bewegen. Was wir nicht gemessen haben: den Verbrauch unter Last, etwa bei einem Ablauf, der eine Tabelle mit zehntausend Zeilen durch einen Code-Knoten schiebt. Da wächst der Hauptprozess mit den Daten, und wer solche Abläufe plant, misst das mit denselben zwei Befehlen auf seiner Maschine nach.

Die CX-Reihe rechnet auf x86, die CAX-Reihe auf ARM. Für n8n gibt es Abbilder für beide Architekturen. Was auf ARM klemmt, sind einzelne Erweiterungen aus der Gemeinschaft, die eine mitgelieferte Binärdatei für x86 erwarten und deshalb beim Start des Knotens abbrechen.

Die Ausstattung der beiden Reihen, Preise netto je Monat ohne IPv4-Adresse nach der Hetzner-Preisanpassung vom 15.06.2026, abgerufen am 19.09.2026:

Typ     Reihe   vCPU   RAM     SSD      EUR/Monat
CX23    x86     2      4 GB    40 GB    5,49
CX33    x86     4      8 GB    80 GB    8,49
CX43    x86     8      16 GB   160 GB   15,99
CX53    x86     16     32 GB   320 GB   29,49
CAX11   ARM     2      4 GB    40 GB    5,99
CAX21   ARM     4      8 GB    80 GB    10,49
CAX31   ARM     8      16 GB   160 GB   20,99
CAX41   ARM     16     32 GB   320 GB   40,99

Die Preise stammen von der Seite zur Preisanpassung in der Hetzner-Dokumentation. Die Ausstattung je Typ haben wir nicht in der Hetzner-Konsole nachgesehen, sondern auf Vergleichsseiten, die die Hetzner-Schnittstelle auslesen; sie deckt sich mit dem, was wir vom eigenen CX23 kennen. Wer bestellt, sieht die Zahlen im Auswahlfeld noch einmal.

Zwischen den beiden Reihen entscheidet deshalb nicht der Name, sondern die Ausstattung und die Frage, ob die Knoten, die du brauchst, auf ARM laufen. Für den Anfang reicht die kleinste. Eine Maschine bei Hetzner lässt sich später auf einen größeren Typ heben, ohne dass du sie neu aufsetzt. Beim Wechsel gibt es zwei Wege: die Plattengröße behalten, dann steht der Weg zurück auf einen kleineren Typ weiter offen, oder die Platte mitwachsen lassen, was diesen Weg versperrt und die Partition danach von Hand im Rettungssystem vergrößert werden muss.

Der Standort, und warum er zuerst kommt

Hetzner betreibt Parks in Falkenstein, in Nürnberg und in Helsinki. Der Standort wird beim Anlegen des Servers ausgewählt, und wer die Auswahl überspringt, bekommt, was voreingestellt ist. Ein Umzug heißt später: neue Maschine, Wiederherstellung aus der Sicherung, neuer Eintrag im Namensdienst.

Helsinki liegt in Finnland, und Finnland liegt in der EU. Die DSGVO gilt dort genauso wie in Sachsen, und für die Rechtsgrundlage macht der Park keinen Unterschied. Der Unterschied liegt woanders: eine Zusage über einen Server in Deutschland hält nur, wenn beim Anlegen Falkenstein oder Nürnberg ausgewählt wurde.

Von der Bestellung bis zur ersten Anmeldung

Sechs Schritte liegen dazwischen. Vorausgesetzt werden eine Domain, bei der du einen A-Eintrag setzen kannst, und ein öffentlicher SSH-Schlüssel.

  1. 01

    Projekt anlegen und Standort wählen

    In der Hetzner-Konsole ein Projekt anlegen. Beim Anlegen des Servers Falkenstein oder Nürnberg auswählen, nicht Helsinki. Das ist die Entscheidung aus dem Abschnitt davor, und sie fällt in diesem einen Auswahlfeld.

  2. 02

    Server bestellen

    Typ CX23, und als Abbild nicht das nackte Ubuntu, sondern Docker CE aus dem Bereich Apps. Darauf liegt ein aktuelles Ubuntu LTS mit fertig eingerichtetem Docker und Compose-Plugin, womit der Installationsschritt auf der Maschine entfällt. Den öffentlichen SSH-Schlüssel hinterlegen und kein Passwort vergeben. Ein Server mit Passwortanmeldung sammelt ab der ersten Minute Anmeldeversuche.

  3. 03

    Firewall setzen

    In der Konsole eine Firewall anlegen und dem Server zuweisen. Eingehend offen bleiben nur 22, 80 und 443. Der Port 5678 gehört nicht dazu, weil n8n von außen über Caddy erreicht wird und nicht direkt.

  4. 04

    Namen auf die Adresse zeigen lassen

    Einen A-Eintrag für die Unterdomain auf die IPv4-Adresse des Servers setzen. Ohne diesen Eintrag bekommt Caddy kein Zertifikat, und der erste Aufruf endet in einer Warnung. Warte, bis der Eintrag beantwortet wird, bevor du weitermachst.

  5. 05

    Verzeichnis und Zugangsdaten anlegen

    Auf dem Server ein Verzeichnis anlegen und darin eine Datei .env ablegen, mit der Fassung, der Adresse, dem Verschlüsselungsschlüssel und dem Datenbankpasswort. Der Schlüssel verschlüsselt die in n8n hinterlegten Zugangsdaten. Geht er verloren, sind sie nicht mehr lesbar.

  6. 06

    Starten und Konto anlegen

    Die Container starten und danach die eigene Adresse im Browser aufrufen. n8n legt beim ersten Aufruf das Besitzerkonto an. Wer das nicht sofort tut, überlässt es dem Nächsten, der die Adresse kennt.

Die Befehle für die ersten Minuten auf der Maschine. Docker und das Compose-Plugin bringt das Abbild schon mit, es bleiben das Aktualisieren des Systems und das Anlegen der Dateien. Die Adresse und die Domain sind Platzhalter.

ssh root@203.0.113.10
apt update && apt full-upgrade -y

mkdir -p /opt/n8n && cd /opt/n8n
printf 'N8N_VERSION=2.39.8\n' > .env
printf 'N8N_HOST=n8n.beispiel.de\n' >> .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" >> .env
chmod 600 .env

cat > Caddyfile <<'CADDY'
{
	email admin@beispiel.de
}

n8n.beispiel.de {
	reverse_proxy n8n:5678
}
CADDY

Die Compose-Datei

Docker Compose ist eine Textdatei, die festhält, welche Container auf der Maschine laufen und wie sie miteinander reden. Sie ist kein Aktualisierungsplan und keine Sicherung. Sie beschreibt nur den Zustand, den ein einzelner Startbefehl herstellen soll.

Drei Container stehen darin: n8n selbst, Postgres für die Daten und Caddy, das die Anfragen aus dem Netz entgegennimmt und das Zertifikat besorgt.

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:${N8N_VERSION}
    restart: unless-stopped
    environment:
      - N8N_HOST=${N8N_HOST}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://${N8N_HOST}/
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      # ein Reverse Proxy (Caddy) vor n8n, deshalb 1
      - N8N_PROXY_HOPS=1
      - N8N_DIAGNOSTICS_ENABLED=false
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    # n8n 2.39.8 warnt bei Postgres 16 ("compatibility support only"), deshalb 17
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=n8n
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - n8n

volumes:
  n8n_data:
  postgres_data:
  caddy_data:
  caddy_config:

Zwei Stellen weichen von den Anleitungen im Netz ab. Die Fassung steht als volle Nummer in der .env und nicht als Reihe „2“ oder als „latest“, damit ein Neustart nie unbemerkt eine neue Fassung zieht. Und Postgres steht auf 17, weil n8n 2.39.8 bei 16 in jedes Protokoll schreibt, dass diese Fassung nur noch aus Kulanz unterstützt wird. Fast alle Anleitungen nennen 16 oder älter.

Der benannte Datenträger n8n_data hängt unter /home/node/.n8n. Dort liegt, was n8n über einen Neustart hinaus behalten muss. Die Abläufe und ihre Ausführungen liegen nicht dort, sondern in Postgres, und deshalb reicht eine Kopie dieses Verzeichnisses als Sicherung nicht aus.

Starten und mitlesen:

docker compose up -d
docker compose logs -f n8n

Am schnellsten merkst du die Trennung von Speichern und Veröffentlichen. Ein gespeicherter Ablauf läuft noch nicht, und wer das übersieht, wartet auf etwas, das niemand freigegeben hat. Drei weitere Änderungen fallen erst auf, wenn du ältere Abläufe übernimmst: die Task Runner sind ab Werk eingeschaltet, Code-Knoten kommen nicht mehr an Umgebungsvariablen heran, und ExecuteCommand sowie LocalFileTrigger sind ab Werk abgeschaltet.

Ein Ablauf aus der 1er-Reihe, der sein Passwort aus einer Umgebungsvariable gezogen hat, läuft nach der Umstellung nicht mehr durch. Leg die Zugangsdaten stattdessen in der Anmeldedaten-Verwaltung von n8n ab. Das war vorher schon der bessere Ort, jetzt ist es der einzige.

Sicherung, Aktualisierung und die Arbeit, die niemand zählt

Eine Sicherung ist eine zweite Kopie der Daten an einem anderen Ort, aus der sich der Zustand wiederherstellen lässt. Ein Abbild der Platte im selben Rechenzentrum ist nur eine halbe. Es hilft gegen einen kaputten Datenträger und nicht gegen ein gesperrtes Konto.

Drei Dinge müssen mit: der Inhalt der Postgres-Datenbank, das Verzeichnis unter /home/node/.n8n und die Datei .env mit dem Verschlüsselungsschlüssel. Fehlt der Schlüssel, liegt die Datenbank zwar vor, die hinterlegten Zugangsdaten sind darin aber nicht mehr lesbar. Das Skript sicherung.sh aus dem Paket holt alle drei in einen Ordner mit Datum; der Kern sind drei Befehle:

docker compose exec -T postgres pg_dump -U n8n n8n | gzip > "$ziel/n8n.sql.gz"
docker compose exec -T n8n tar czf - -C /home/node/.n8n . > "$ziel/n8n-daten.tar.gz"
tar czf "$ziel/konfiguration.tar.gz" .env docker-compose.yml Caddyfile

Das Datenverzeichnis wird aus dem laufenden Container heraus gepackt, nicht vom Datenträger des Hosts. So muss das Skript den Namen des Docker-Volumes nicht kennen, der vom Ordnernamen abhängt. Zieh die Dateien danach vom Server weg, auf einen Rechner oder einen Speicher, der nicht bei Hetzner steht.

Die Aktualisierung ist ein Abruf und ein Neustart, aber weil die Fassung fest in der .env steht, geht ihr ein Eintrag voraus. Das Skript aktualisieren.sh zeigt ohne Argument, welche Fassung eingetragen ist und welche auf GitHub als stabil gilt, und ändert nichts. Mit einer Nummer sichert es zuerst, trägt die Nummer ein, zieht das Abbild und startet n8n neu. „latest“ und „2“ lehnt es ab. Lies vor dem Sprung die Anmerkungen zur Fassung, Suchwort „breaking“.

Eine Sicherung, aus der noch nie wiederhergestellt wurde, ist eine Vermutung. Deshalb hat sicherung.sh eine zweite Betriebsart: --probe mit dem Ordner einer Sicherung. Sie startet ein frisches Postgres und ein frisches n8n als Wegwerf-Container, spielt den Dump ein und lässt n8n die Zugangsdaten mit dem Schlüssel aus der gesicherten .env entschlüsseln, nicht mit dem aus der laufenden Instanz und nicht mit dem aus dem Kopf. Die laufende Instanz wird dabei nicht angefasst. So sah der Lauf am 19.09.2026 gegen die Sicherung der Messinstanz aus, in der ein Ablauf und ein Zugangsdatensatz lagen:

Probe: frisches Postgres 17 und n8n 2.39.8 in Wegwerf-Containern
- Dump einspielen
- Zugangsdaten mit dem Schlüssel aus der Sicherung entschlüsseln
- Abläufe lesen
  Abläufe in der Sicherung: 1
  - Formulareingang ins CRM, mit Bestätigung (6 Knoten)
  Zugangsdaten in der Sicherung: 1
  - Prüf-Zugang CRM (httpHeaderAuth): entschlüsselt
PROBE BESTANDEN

Und so, wenn in der gesicherten .env ein falscher Schlüssel steht, was wir absichtlich ausprobiert haben:

  Zugangsdaten: Entschlüsselung abgebrochen.
  Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.
PROBE FEHLGESCHLAGEN: der Schlüssel aus der Sicherung passt nicht zu den Daten in der Sicherung.

Die Probe gehört einmal im Quartal in den Kalender, und das Datum des letzten Laufs steht in der Prüfliste. Diese Arbeit taucht in keiner Anleitung auf, und sie entscheidet, ob die Instanz in einem Jahr noch läuft.

Was das nicht abdeckt

Ein Server ist noch keine Überwachung. Die Compose-Datei startet die Container nach einem Absturz neu, aber sie sagt niemandem, dass ein Ablauf seit drei Tagen scheitert. Dafür braucht es einen Ablauf mit Error Trigger, der an ein Postfach schreibt, das jemand liest; die Prüfliste im Paket hat die Zeile dafür.

Die Zahlen hier sind lokal gemessen. 355 MB und 16,6 Sekunden stammen aus Docker Desktop auf einem Arbeitsrechner mit vierzehn Kernen, nicht von einem CX23 mit zwei geteilten. Die Größenordnung überträgt sich, die Startzeit nicht.

Und die Probe ersetzt keine Wiederherstellung. Sie beweist, dass Dump und Schlüssel zusammenpassen. Der Weg auf eine frische Maschine, mit neuem A-Eintrag und neuem Zertifikat, bleibt Handarbeit und steht als Anleitung in der README.

Zum Nachbauen

Das Paket zu diesem Beitrag enthält die drei Dateien für den Server, die beiden Skripte und die Prüfliste. Entpacken, nach /opt/n8n kopieren, .env aus der Vorlage anlegen, dann:

  • docker-compose.yml: die drei Container aus diesem Beitrag, Fassung fest über .env
  • .env.example und Caddyfile: die Vorlagen für Zugangsdaten und Adresse
  • sicherung.sh: die Sicherung, und mit --probe die Wiederherstellungsprobe, deren Ausgabe oben steht
  • aktualisieren.sh: Sicherung, Eintrag der Fassung, Abruf, Neustart
  • haertung-pruefliste.md: die Prüfliste zum Aufsetzen und für das Quartal, mit einem Befehl je Zeile
  • compose.lokal.yml: die Zusatzdatei, mit der die Messung oben auf dem Arbeitsrechner lief, ohne Caddy

n8n selbst hosten: Compose-Datei, Sicherung mit Wiederherstellungsprobe und Härtungsprüfliste

ZIP · 9,8 kB

Die drei-Container-Compose-Datei aus dem Beitrag mit fester Fassung, .env-Vorlage, Caddyfile, ein Sicherungsskript mit Wiederherstellungsprobe in Wegwerf-Containern, ein Aktualisierungsskript und die Härtungsprüfliste.

Herunterladen

Stand dieses Artikels

Erstfassung gebaut mit n8n 2.x auf einem Hetzner CX23 in Falkenstein. Nachgeprüft am 19.09.2026 mit n8n 2.39.8, Postgres 17.11 (Alpine-Abbild), Docker Desktop 29.3.1 und Compose 5.1.1 auf einem Mac: Start der Instanz mit leerer Datenbank, Import eines Ablaufs und eines Zugangsdatensatzes über die Kommandozeile, sicherung.sh, beide Ausgänge der Probe, aktualisieren.sh ohne Argument sowie mit „latest“ und mit der bereits eingetragenen Nummer. Die Messinstanz wurde danach mit docker compose down -v entfernt.

Preise aus der Hetzner-Preisanpassung vom 15.06.2026, abgerufen am 19.09.2026; die Ausstattung je Typ aus Vergleichsseiten, nicht aus der Konsole. Nicht geprüft: die Dauer des Aufbaus auf einem frischen Server, der Verbrauch auf dem CX23 und unter Last, der Sprung auf eine neuere Fassung mit aktualisieren.sh (am Prüftag war 2.39.8 die neueste stabile) und der IPv4-Aufschlag bei Hetzner.

Häufige Fragen

Reicht die kleinste Maschine für n8n?

Für ein paar Abläufe am Tag ja. Die Instanz belegt im Leerlauf rund 400 MB samt Postgres, gemessen am 19.09.2026 auf einem Arbeitsrechner, und die kleinste Maschine hat 4 GB. Eng wird es erst bei Abläufen, die tausende Datensätze in einem Lauf durch Code-Knoten schieben. Wer das vorhat, misst mit docker stats auf der eigenen Maschine nach und hebt den Typ, wenn der Hauptprozess dauerhaft über 2 GB liegt.

Warum Postgres und nicht die eingebaute SQLite-Datenbank?

Weil sich eine Instanz mit Postgres sauber sichern und in einem Befehl neu anlegen lässt: pg_dump aus dem laufenden Container, ohne n8n anzuhalten. Und weil die Probe im Paket den Dump in ein frisches Postgres einspielt, ohne die laufende Instanz zu berühren. Für einen Nachmittag auf dem Laptop reicht SQLite.

Was passiert, wenn der Verschlüsselungsschlüssel verloren geht?

Die Abläufe bleiben lesbar, die hinterlegten Zugangsdaten nicht. n8n bricht den Export mit dem Hinweis ab, dass ein anderer encryptionKey benutzt wurde; genau diese Meldung erzeugt die Probe, wenn der Schlüssel nicht passt. Deshalb liegt der Schlüssel in der .env, die .env in jeder Sicherung, und zusätzlich in einem Passwortmanager.

© 2026 ernsthaft.ai, ein Angebot der abi group GmbH

Alle genannten Produktnamen, Logos und Marken sind Eigentum der jeweiligen Inhaber. Ihre Nennung bedeutet keine Empfehlung oder Verbindung zu ernsthaft.ai oder der abi group GmbH.

Build v1.10.0+e670d45