Zum Inhalt springen
Q
QuoteNode

Installation

Installations-Fehlerbehebung

Häufige QuoteNode-Bereitstellungsprobleme mit Links, CORS, SMTP, DNS, Docker-Health-Checks und verlorenen Secrets.

Installations-Fehlerbehebung

Die meisten Installationsprobleme entstehen durch URL-Missmatch, fehlendes SMTP, blockierte Ports oder verlorene Secrets. Beginnen Sie mit der sichtbaren URL in Ihrem Browser und arbeiten Sie nach innen.

Überprüfen Sie, welche URL kopiert oder gesendet wurde.

  • http://localhost/... funktioniert nur auf demselben Gerät.
  • http://192.168.x.x/... funktioniert nur im LAN oder VPN. VPN kann eine valide Brücke für interne Teams sein, aber keine öffentliche Kundenlink-Strategie.
  • https://crm.example.com/... funktioniert öffentlich nur, wenn DNS, HTTPS, Proxy und Firewall korrekt sind.

Für öffentliche Kundenlinks stellen Sie mit einer öffentlichen Domain unter Verwendung von Ubuntu-Oeffentlichserver oder Coolify bereit.

Browser zeigt CORS-Fehler

CORS_ALLOWED_ORIGINS muss exakt mit dem Browser-Ursprung übereinstimmen. Dies sind unterschiedliche Ursprünge:

https://crm.example.com
https://www.crm.example.com
http://crm.example.com
https://crm.example.com:8443

Korrigieren Sie die Variable, stellen Sie erneut bereit und versuchen Sie es in einer frischen Browser-Sitzung.

E-Mail wird nicht zugestellt

Überprüfen Sie zuerst diese Punkte:

  • SMTP_HOST und SMTP_PORT sind korrekt.
  • SMTP_AUTH=true, wenn Ihr Provider Authentifizierung erfordert.
  • SMTP_STARTTLS=true für Port 587.
  • SMTP_USERNAME und SMTP_PASSWORD sind gültig.
  • Der Provider erlaubt SMTP/App-Passwort-Login.
  • NOTIFICATIONS_EMAIL_ENABLED=true, wenn Benachrichtigungs-E-Mails gesendet werden sollen.

Manuelles öffentliches-Link-Teilen und PDF-Download können auch ohne SMTP funktionieren.

Setzen Sie:

NOTIFICATIONS_PUBLIC_PREFERENCES_URL=https://<frontend-domain>/notifications/preferences

Lassen Sie es nicht in einer öffentlichen Bereitstellung auf localhost zeigen.

Docker-Container sind nicht gesund

Führen Sie aus:

docker compose ps
docker compose logs -f backend
docker compose logs -f frontend
docker compose logs -f gotenberg

Häufige Ursachen:

  • PostgreSQL startet noch.
  • DB_PASSWORD unterscheidet sich zwischen PostgreSQL und Backend.
  • DB_ENCRYPTION_KEY fehlt oder ist fehlerhaft.
  • Gotenberg ist nicht gesund, daher kann die PDF-Generierung nicht starten.
  • Host-Ports 80/443 werden bereits von einem anderen Dienst verwendet.

Nach der Korrektur der Konfiguration starten Sie den betroffenen Dienst oder den Stack neu:

docker compose restart backend
docker compose up -d

Meine IP ist blockiert — Anmeldung nicht möglich

Es gibt zwei häufige Ursachen: Die IP-Allowlist hat den Zugang gesperrt oder der Brute-Force-Schutz hat Ihre IP nach zu vielen fehlgeschlagenen Anmeldeversuchen blockiert.

Sperrung durch IP-Allowlist

QuoteNode verfügt über eine IP-Allowlist, die unter Einstellungen > Sicherheit > IP-Allowlist konfiguriert wird (oder über die Umgebungsvariable SECURITY_IP_WHITELIST). Wenn aktiviert, schützt sie die gesamte Admin-Oberfläche — einschließlich der Login- und 2FA-Bildschirme — sodass eine ausgeschlossene Adresse 403 IP_NOT_ALLOWED erhält und sich nicht einmal anmelden kann. (Health-Checks und kundenseitige öffentliche Angebotslinks bleiben erreichbar.)

Warten Sie zunächst zwei Minuten, wenn Sie die Liste gerade in der App geändert haben. Ein Aktivieren oder Einschränken in der App ist sperrgeschützt: Es muss von Ihrer Sitzung innerhalb eines kurzen Fensters (Standard 120 s) erneut bestätigt werden, und wenn Ihre aktuelle Adresse nicht abgedeckt ist, wird die Liste automatisch auf die vorherige zurückgesetzt. Eine versehentliche Selbstsperre über den Sicherheitsbildschirm heilt sich also selbst — warten Sie und versuchen Sie es erneut. (Dieses Sicherheitsnetz gilt nicht für die Variable SECURITY_IP_WHITELIST, die beim Start durchgesetzt wird.)

Wiederherstellung (Operator-Override): Wenn Sie weiterhin ausgesperrt sind — z. B. durch ein Operator-SECURITY_IP_WHITELIST, eine ungültige gespeicherte Liste oder weil Sie nicht warten können — fügen Sie diese Variable zur Backend-Container-Umgebung hinzu (.env oder environment:-Abschnitt in docker-compose) und starten Sie neu:

SECURITY_IP_WHITELIST_FORCE_DISABLE=true
docker compose restart backend

Dies umgeht die Allowlist vollständig und ermöglicht die Anmeldung. Korrigieren Sie nach der Anmeldung die Allowlist unter Einstellungen > Sicherheit, entfernen Sie dann SECURITY_IP_WHITELIST_FORCE_DISABLE und starten Sie erneut.

Lassen Sie SECURITY_IP_WHITELIST_FORCE_DISABLE=true nicht in der Produktion aktiviert. Es deaktiviert die gesamte IP-Zugriffskontrolle.

Sperrung durch Brute-Force-Schutz

QuoteNode blockiert IP-Adressen nach zu vielen fehlgeschlagenen Anmeldeversuchen innerhalb einer Stunde:

Schwelle Fehlversuche in 1 h Auswirkung
Soft 10 Progressive Verzögerung (1 s pro Versuch über Schwelle, max. 30 s)
Temporäre Sperre 20 Gesperrt für 5 Minuten
Dauerhafte Sperre 50 Gesperrt bis zur manuellen Freigabe

Entsperren:

  1. Admin-Panel — ein anderer Administrator öffnet Einstellungen > Sicherheits-Dashboard > Blockierte IPs und klickt Entsperren.
  2. Warten — temporäre Sperren laufen nach SECURITY_TEMP_LOCK_MINUTES (Standard 5 Minuten) ab. Dauerhafte Sperren laufen nicht von selbst ab.
  3. Schwellenwerte temporär erhöhen, wenn Sie gesperrt sind und kein anderer Admin helfen kann. Zur .env hinzufügen, neu starten, anmelden, dann entfernen:
SECURITY_MAX_FAILED_LOGINS_SOFT=9999
SECURITY_MAX_FAILED_LOGINS_TEMP_LOCK=9999
SECURITY_MAX_FAILED_LOGINS_HARD_LOCK=9999

Setzen Sie diese Werte nach Wiedererlangung des Zugangs auf die Standardwerte zurück.

Der Rate-Limiter für öffentliche Links blockiert IP-Adressen, die Probe- oder Fehlerlimits überschreiten. Diese Sperren sind im Arbeitsspeicher und laufen nach PUBLIC_RATE_LIMIT_IP_BLOCK_MINUTES (Standard 60 Minuten) oder nach einem Backend-Neustart ab.

Update stoppt mit “cannot enforce offer-section ownership”

Nach einem Update startet das Backend nicht, und docker compose logs backend zeigt:

Migration V145 cannot enforce offer-section ownership: 3 offer_items row(s) point at a
section of another offer or tenant.

Was das bedeutet

Ein Angebot besteht aus Abschnitten (“Hardware”, “Dienstleistungen”) und den darin platzierten Positionen. Diese Version macht diese Verknüpfung verbindlich: Eine Position darf nur in einem Abschnitt ihres eigenen Angebots liegen. Einige alte Zeilen in Ihrer Datenbank verletzen diese Regel, deshalb stoppt das Update und bittet Sie um eine Entscheidung, statt Ihre Angebote eigenmächtig umzuschreiben.

Nichts ist kaputt, und die Verweigerung selbst hat nichts geändert. Diese eine Migration wurde vollständig zurückgerollt — keine halb angewandte Änderung und nichts, was in der Migrationshistorie zu reparieren wäre. Beachten Sie jedoch: Frühere Migrationen desselben Updates können bereits angewandt sein; Ihre Datenbank steht unmittelbar vor diesem Schritt, nicht zwangsläufig wieder auf der Ausgangsversion. Damit ist auch offen, ob die bisherigen Container noch dagegen laufen — das hängt davon ab, was jene früheren Migrationen geändert haben. Verlassen Sie sich nicht darauf, die Images zurückzurollen.

Wählen Sie, was geschehen soll

Variante Was sie tut Was sich für Sie ändert
Problem prüfen (Standard) Listet die betroffenen Angebote und Positionen auf. Nichts. Nur lesend.
Falsche Zuordnungen lösen Entfernt nur bei diesen Positionen den Abschnitt. Die Positionen bleiben in ihrem Angebot mit gleichem Preis, gleicher Menge und Beschreibung. Sie verlieren ihre Abschnittsüberschrift und erscheinen als nicht gruppierte Zeilen.
Abschnitt je Position wählen Fragt pro Position und bietet nur Abschnitte des eigenen Angebots an. Sie behalten die gewünschte Gruppierung. Übersprungene Positionen bleiben offen und das Update stoppt erneut.

Bereits versendete Angebote zeigen weiterhin genau das, was der Kunde gesehen hat — der Inhalt eines versendeten Angebots wird beim Versand eingefroren, also ändern sich weder der öffentliche Link noch das PDF.

Finden Sie zuerst die richtige Installation (Coolify oder mehrere Stacks auf einem Server)

Jeder Befehl unten muss Ihre Installation treffen. Ein Server kann mehrere beherbergen, und mehr als eine davon kann einen Dienst namens backend haben. Listen Sie daher zuerst die Kandidaten auf — jede Zeile ist eine Installation, mit Projektnamen und der Compose-Datei, aus der sie gestartet wurde:

docker inspect $(docker ps -aq --filter "label=com.docker.compose.service=backend" \
                             --filter "label=com.docker.compose.oneoff=False") \
  --format '{{index .Config.Labels "com.docker.compose.project"}} | {{index .Config.Labels "com.docker.compose.project.config_files"}}'

Erhalten Sie mehr als eine Zeile, wählen Sie Ihre aus; raten Sie nicht. Führen Sie dann jeden Befehl mit explizit genanntem Projekt und Datei aus statt mit einem blanken docker compose, damit Sie nicht auf der falschen Installation arbeiten:

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections

Ersetzen Sie <project> und <compose-file> durch die Werte Ihrer Zeile und behalten Sie sie in jedem folgenden Befehl bei — genau das lässt jeden davon auf Ihrer Installation wirken und auf keiner anderen. Zeigt die Zeile mehrere durch Kommas getrennte Dateien, wiederholen Sie -f für jede, in dieser Reihenfolge. Ist die Liste leer, wurden die Container nicht mit Docker Compose gestartet — verwenden Sie das Verzeichnis, das Ihr Anbieter oder die Installationsanleitung nennt, oder fragen Sie die Person, die den Server eingerichtet hat. Führen Sie dies in Coolify in einem Terminal auf dem Server aus, nicht auf dem Container: Das Container-Terminal braucht ein laufendes Backend, und Ihres läuft nicht. (Die Meldungen des Werkzeugs zeigen der Lesbarkeit halber ein kurzes docker compose … ohne diese Optionen — ergänzen Sie beim Kopieren Ihr -p und -f.)

Zuerst sichern

PostgreSQL läuft auch dann, wenn das Backend nicht startet, Sie können also ohne die App sichern. Der Befehl liest Datenbankname und Benutzer aus dem Container selbst, funktioniert also unabhängig davon, wie Sie sie genannt haben, und es erscheint kein Passwort auf dem Bildschirm:

docker compose -p <project> -f <compose-file> exec -T postgres sh -c \
  'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom -f /tmp/before-repair.dump \
   && pg_restore --list /tmp/before-repair.dump | head -3'
docker compose -p <project> -f <compose-file> cp postgres:/tmp/before-repair.dump ./before-repair.dump
ls -lh before-repair.dump

Die Zeile pg_restore --list gibt die ersten Einträge des Archivs aus: Sehen Sie diese und zeigt ls -lh eine nicht leere Datei, ist die Sicherung lesbar. Bewahren Sie before-repair.dump neben Ihrer docker-compose.yml auf — sie liegt auf der Festplatte des Servers, nicht in einem Container, und übersteht damit alles Folgende.

Problem prüfen

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections

Dieser Befehl liest nur. Er zeigt die betroffenen Angebote, was an jeder Position falsch ist, und die Varianten:

Found 3 offer item(s) in 2 offer(s) assigned to a section that does not belong to that offer.

  Offer OF-2026-014 [DRAFT]
    item:    Mounting frame
    section: section "Installation" — section belongs to another offer

Vorschau, dann anwenden

Ohne --apply=true ist jeder Befehl eine Vorschau: Er zeigt genau, was er tun würde, und endet, ohne etwas geändert zu haben — in jedem Terminal.

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink

Fügen Sie --apply=true --acknowledge-backup=true hinzu, um ihn auszuführen. Nur diese Form schreibt:

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=unlink --apply=true --acknowledge-backup=true

Um jede Position selbst zu entscheiden, verwenden Sie --plan=choose. Das Werkzeug stellt eine Frage pro Position und listet die Abschnitte dieses Angebots auf, Sie tippen also nie eine Datenbank-ID. Ohne --apply=true zeigt es weiterhin nur den Plan:

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections --plan=choose --apply=true --acknowledge-backup=true

Update abschließen

Sobald das Werkzeug meldet, dass keine Position mehr auf einen falschen Abschnitt zeigt:

docker compose -p <project> -f <compose-file> up -d

Die gestoppte Migration hat nichts hinterlassen, sie läuft beim nächsten Start einfach durch. Sie brauchen weder flyway repair noch baseline — und sollten beides nicht verwenden, da es das Datenproblem verdecken statt lösen würde.

Wie gewohnt prüfen: /health öffnen, dann unter Admin > Lizenz die neue Version kontrollieren.

Wenn gemeldet wird, dass sich die Daten seit Ihrer Vorschau geändert haben

Das Werkzeug wendet den von Ihnen freigegebenen Plan an, nicht das, was es im Moment des Schreibens vorfindet. Ist während Ihrer Bestätigung eine falsche Zuordnung hinzugekommen oder verschoben worden oder ein von Ihnen gewählter Abschnitt gelöscht worden, verweigert es die gesamte Reparatur und ändert nichts. Führen Sie die Prüfung erneut aus, um den aktuellen Stand zu sehen, und treffen Sie Ihre Wahl noch einmal.

Wenn gemeldet wird, dass die Reparatur gespeichert wurde — oder dass es nicht bestätigt werden konnte

Die Reparatur läuft in einer Transaktion, sie wird also entweder vollständig gespeichert oder gar nicht. Zwei Ausgänge brauchen Aufmerksamkeit. Schlägt ein späterer Schritt fehl — das Schreiben des Protokolls oder das bestätigende erneute Lesen —, behält das Werkzeug das Wort SAVED in der Meldung: Die Reparatur hat funktioniert, spielen Sie also keine Sicherung zurück. Steht dort stattdessen UNCONFIRMED, hat die Datenbank nie geantwortet und niemand kann sagen, wie es ausgegangen ist; spielen Sie keine Sicherung zurück und wiederholen Sie die Reparatur nicht. Führen Sie in beiden Fällen die nur lesende Prüfung erneut aus: Meldet sie nichts zu reparieren, sind Sie fertig und können den Stack starten; listet sie dieselben Positionen weiterhin auf, wurde nichts gespeichert und Sie können Ihre Wahl wiederholen.

docker compose -p <project> -f <compose-file> run --rm backend --repair-offer-sections

Eines sollten Sie genau lesen: Haben Sie Abschnitt je Position wählen benutzt und einige übersprungen, werden diese übersprungenen Positionen von der Prüfung weiterhin aufgelistet. Das ist so gewollt und bedeutet nicht, dass die Reparatur fehlgeschlagen ist. Vergleichen Sie die Liste mit den Positionen, die Sie tatsächlich reparieren wollten: Bleiben nur Ihre übersprungenen übrig, hat die Reparatur funktioniert und offen ist nur die Entscheidung, die Sie vertagt haben — führen Sie das Werkzeug dafür später erneut aus. Stehen dort noch Positionen, die Sie reparieren wollten, wurde nichts gespeichert.

Was geändert wurde und was zu tun ist, wenn es weiterhin fehlschlägt

Jeder Lauf, der etwas ändert, schreibt die IDs der betroffenen Position, des Angebots und des Abschnitts nach /app/data/backups/ im Backend-Volume. Die Datei enthält keine Kundendaten und kann einer Supportanfrage beigelegt werden. Kann die Datei nicht geschrieben werden, gibt das Werkzeug dasselbe Protokoll auf dem Bildschirm aus — kopieren Sie es, bevor Sie das Terminal schließen:

docker compose -p <project> -f <compose-file> run --rm --entrypoint sh backend \
  -c 'ls -1 /app/data/backups/offer-section-repair-*.json'

Wenn das Update danach weiterhin stoppt:

  • führen Sie die Prüfung erneut aus — übersprungene Positionen werden weiterhin aufgelistet;
  • nennt die Meldung eine andere Migration, gilt dieses Werkzeug nicht dafür. Es repariert ausschließlich Abschnittszuordnungen von Angebotspositionen;
  • ist die Meldung unverändert und die Prüfung meldet nichts zu reparieren, melden Sie das Problem mit dem Backend-Log und dem obigen Protokoll.

HTTPS-Zertifikat wird nicht ausgestellt

Für öffentliche Bereitstellungen:

  • DNS muss auf den Bereitstellungsserver zeigen.
  • Ports 80 und 443 müssen aus dem Internet erreichbar sein.
  • Nur ein Proxy sollte HTTPS für dieselbe Domain terminieren.
  • In Coolify weisen Sie die Domain dem frontend-Dienst zu.

Ich habe .env verloren

Wenn Sie .env verloren haben, stoppen Sie und bewerten Sie, bevor Sie Werte rotieren.

  • DB_PASSWORD kann normalerweise mit PostgreSQL-Admin-Zugang zurückgesetzt werden.
  • TIMING_TOKEN_SECRET und PUBLIC_LINK_PASSWORD_SESSION_SECRET können neu generiert werden, aber bestehende Sitzungen/Tokens können ablaufen.
  • DB_ENCRYPTION_KEY ist kritisch. Ohne den ursprünglichen Schlüssel können verschlüsselte Werte nicht entschlüsselt werden.
  • Wenn PII-Verschlüsselung aktiviert ist, hängen verschlüsselte Kunden-/Kontaktfelder von DB_ENCRYPTION_KEY ab.

Stellen Sie .env wann immer möglich aus einem Backup wieder her.

Wiederherstellung bei Verlust von .env mit vorhandenem Datenbank-Backup

  1. Erstellen Sie eine neue .env mit neuen Secrets.
  2. Stellen Sie die Datenbank aus Ihrem Backup wieder her (pg_dump / pg_restore).
  3. Alle Geschäftsdaten (Kunden, Angebote, Produkte, Benutzer) sind vollständig zugänglich.
  4. Nur wenige Felder gehen verloren: MFA-Codes (Benutzer registrieren sich erneut), SMTP-Passwort (in den Einstellungen neu eingeben), FX-API-Schlüssel (neu eingeben). Wenn ENCRYPT_PII=true aktiviert war, gehen auch verschlüsselte Kundendaten verloren.

Wenn Ihre Backups mit age verschlüsselt sind (Standardeinstellung), benötigen Sie die age-Identitätsdatei aus Ihrem Wiederherstellungs-Kit, um sie zu entschlüsseln. Siehe die Anleitung Backup und Wiederherstellung.

Admin-Passwort vergessen

Verwenden Sie einen nicht-destruktiven Wiederherstellungspfad:

  1. Wenn SMTP konfiguriert ist, nutzen Sie die Login-Seite: Passwort vergessen?
  2. Wenn ein anderer Administrator sich noch anmelden kann, öffnen Sie Benutzer im Admin-Panel und setzen Sie das Passwort zurück.
  3. Wenn kein Administrator sich anmelden kann und der E-Mail-Reset-Ablauf nicht verfügbar ist, stellen Sie den Zugang aus einem bekannten guten Backup wieder her oder verwenden Sie Ihre interne Wartungsprozedur.

docker compose down -v ist kein Passwort-Reset-Verfahren. Es löscht alle Daten — Benutzer, Angebote, Kunden, Einstellungen und Backups. Verwenden Sie es nur für einwegige Testumgebungen.

Lizenzverifizierung schlägt fehl

Für echte Bereitstellungen halten Sie:

LICENSE_SKIP_SIGNATURE_CHECK=false

Wenn eine gültige Lizenz trotzdem fehlschlägt, überprüfen Sie, ob die Bereitstellung die erwartete App-Version und eine NTP-synchronisierte Systemuhr hat. Bestätigen Sie, dass die Lizenz zur bereitgestellten Edition/Version passt, dann kontaktieren Sie den Support oder ersetzen Sie die Lizenzdatei über den dokumentierten Betreiberpfad. Setzen Sie die Signatur-Umgehung nicht als Produktions-Workaround auf true.

Last reviewed: Recently