Zum Inhalt springen
senn-techsenn-tech
Zurück zum Blog
Infrastruktur
Server, Proxmox und Netzwerk2026-11-02· Von Franz Senn

Stalwart 1.0: Migration im Homelab, Schritt für Schritt und mit allen Stolpersteinen

Stalwart 1.0 ist seit dem 26. Oktober 2026 draußen, und es gibt dafür kein Update-Kommando. Wir haben unseren Mailserver am 2. November 2026 migriert: fünf Konten, 119.605 Mails, 7,4 GB RocksDB, Blobs in MinIO. Dieser Beitrag hält fest, was dabei gemessen wurde, welche Befehle liefen und wo wir hängen geblieben sind.

In Kürze: Die Migration von Stalwart 0.16 auf 1.0 ist ein Kopiervorgang mit dem Werkzeug stalwart-migrate. Es liest den alten Store nur lesend und schreibt Konten, Mails und Einstellungen in einen neuen, leeren v1-Store. Der alte Server läuft währenddessen weiter und steht nur für den letzten Lauf still. Danach braucht jeder Client einen registrierten OAuth-Client.

Die Bausteine

Unser Mailserver läuft in einer Proxmox-VM mit 6 GB RAM, Stalwart und Bulwark als Docker-Container, die Mailblobs liegen in einem MinIO-Bucket. Vor jedem riskanten Schritt lief ein Snapshot-Backup.

Logo von Proxmox
Proxmox VE und Backup Server: Die VM mit dem Mailserver wurde vor Stufe 1 und vor dem Umstieg per Snapshot gesichert, in 1 min 45 s und 5 min 51 s. Logo: Simple Icons (Datei proxmox.svg, Marke: Proxmox Server Solutions GmbH). (Quelle: Hersteller)
Logo von MinIO
MinIO hält die Mailblobs im Bucket stalwart-mail-blobs: 69.553 Objekte, 4,9 GB. Dass der Bucket geteilt ist, hat die Migration am meisten beeinflusst. Logo: Simple Icons (Datei minio.svg, Marke: MinIO, Inc.). (Quelle: Hersteller)
Logo von Docker
Docker führt Stalwart, Bulwark und das Migrationswerkzeug aus. Auf unserem Host gibt es nur docker-compose in Version 1, kein docker compose. Logo: Simple Icons (Datei docker.svg, Marke: Docker, Inc.). (Quelle: Hersteller)

Stalwart (stalw.art) und Bulwark (bulwarkmail.org) stellen keine Logodatei bei Simple Icons oder Wikimedia Commons bereit, deshalb stehen sie hier nur als Link. Wie dieser Stack zusammenhängt, steht im Beitrag Der EU-Mail-Stack aus Bulwark, Stalwart und Proxmox Mail Gateway.

Was wir gemessen haben

Alle Werte sind eigene Messungen vom 2. November 2026. Messträger: Proxmox-VM mit 6 GB RAM, Stalwart 0.16.27 und 1.0.2, stalwart-migrate 1.0.5, das Werkzeug auf 2 CPUs begrenzt und mit 500 Operationen pro Sekunde gedrosselt.

SchrittDauerErgebnis
Backup der VM, Snapshot, inkrementell1 min 45 s und 5 min 51 sohne Fehler
Erstlauf run, 119.605 Mails, 69.547 Blobs778 s0 Fehler, Server lief weiter
Differenzlauf run32 s1 Mail nachgezogen
Letzter Lauf run --final, Server gestoppt47 s0 Fehler, 0 Abweichungen
Prüfung verify --full18 s5 Konten, 0 Abweichungen
Pause der Mail-Annahmerund 2 minGateways hielten Mails zurück
Ablauf der Migration: nur der vorletzte Schritt stoppt den ServerBackupVM-Snapshot0.16.27Pflicht-AusgangsstandprepareFragen beantwortenrunim Betrieb, wiederholbarrun --finalServer gestopptv1 startenneuer StoreClientsOAuth, Bulwark
Die Reihenfolge entspricht dem README von stalwart-migrate. Die Prüfung nach jedem Schritt ist unsere Ergänzung. (Quelle: stalwart-migrate README, eigener Ablauf)
Laufzeit der Migrationsschritte in Sekunden, 2.11.2026Erstlauf run778 · 778 s, Server läuftrun --final47 · 47 s, Server stehtDifferenzlauf32 · 32 sverify --full18 · 18 s0800
Eigene Messung auf einer VM mit 6 GB RAM. Die Zeit bei gestopptem Server ist nur der Abschnitt run --final, hinzu kommen Stopp und Start des Containers. (Quelle: senn-tech, eigene Messung vom 02.11.2026)

So gehst du vor

1. Backup und Ausgangsstand: Erst ein Snapshot der VM, dann Stalwart auf 0.16.27 heben. Das Werkzeug verlangt genau diesen Stand. Wer sein Image per Digest festgenagelt hat, muss in der Compose-Datei die ganze Image-Zeile ersetzen und den Tag allein reicht nicht.

2. Fragen beantworten: prepare stellt Fragen zu Blob-Store, Datenkategorien, OAuth, neuen Rechten und geänderten Standardwerten und braucht dafür ein Terminal. Wir haben die Empfehlungen des Werkzeugs übernommen und nur den Blob-Modus auf copy gestellt (siehe Stolperstein 6).

docker run --rm -it --user 2000:2000 \
  -v /root/stalwart-migrate/state:/state \
  -v root_stalwart_data:/var/lib/stalwart:ro \
  -v /root/securemailproxy/stalwart/config.json:/etc/stalwart/config.json:ro \
  stalwartlabs/stalwart-migrate:1.0.5 \
  prepare --source /etc/stalwart/config.json --state /state

3. Kopieren, während der Server läuft: Das Ziel ist ein neuer, leerer Ordner. Die Datei config.json des Ziels enthält nur den Datenspeicher:

{ "@type": "RocksDb", "path": "/var/lib/stalwart-v1/" }
docker run --rm --user 2000:2000 --security-opt apparmor=unconfined \
  -v /root/stalwart-migrate/state:/state \
  -v /root/stalwart-migrate/target:/var/lib/stalwart-v1 \
  -v /root/stalwart-migrate/target-etc/config.json:/etc/stalwart-v1/config.json:ro \
  -v root_stalwart_data:/var/lib/stalwart:ro \
  -v /root/securemailproxy/stalwart/config.json:/etc/stalwart/config.json:ro \
  stalwartlabs/stalwart-migrate:1.0.5 run \
  --source /etc/stalwart/config.json --target /etc/stalwart-v1/config.json \
  --state /state --max-ops-per-sec 500

Den Lauf kannst du beliebig oft wiederholen. Jeder weitere Lauf kopiert nur, was sich geändert hat.

4. Letzter Lauf mit gestopptem Server: docker stop stalwart, dann derselbe Befehl mit run --final --yes --verify-full. Die Option --exclude spam_model verhindert, dass das Spam-Modell im geteilten Bucket überschrieben wird.

5. Neuen Server starten: In der Compose-Datei das Image auf stalwartlabs/stalwart:1.0.2, die Konfiguration auf die Ziel-config.json und das Datenverzeichnis auf den neuen Store stellen, dann up -d. Nach 40 Sekunden meldete der Container healthy.

Sieben Stolpersteine

1. Unknown client id: Version 0.16 nahm jede client_id beim OAuth-Login an. Version 1.0 verlangt registrierte Clients, der prepare-Bericht warnt davor. Bulwark nutzt die feste ID webmail, und der Login endete bei Stalwart.

Fehlerseite von Stalwart mit dem Text Unknown client id
Erster Fehler nach dem Umstieg: Stalwart 1.0.2 kennt die client_id von Bulwark nicht.

2. Die Redirect-URI enthält die Sprache: Nach der Registrierung kam redirect_uri parameter is missing or does not match. Bulwark baut die Adresse als https://<domain>/<sprache>/auth/callback. Wir tragen deshalb /auth/callback, /de/auth/callback und /en/auth/callback ein.

Fehlerseite von Stalwart: Der Parameter redirect_uri fehlt oder passt nicht zur Registrierung
Zweiter Fehler: Der Client war angelegt, aber ohne den Sprachpfad in der Redirect-URI.

3. Der Client braucht Scopes: Der Login klappte danach, aber Bulwark lud keine Postfächer und endete auf einer Fehlerseite. Im Stalwart-Log stand The token scope does not allow this request. Der Client hatte keine erlaubten Scopes. Gültig sind die Kurznamen mail, contacts, calendars und files. Die URN-Schreibweise aus der Discovery lehnt der Server als ungültigen Schlüssel ab.

Bulwark-Fehlerseite Authentifizierung fehlgeschlagen mit Schaltfläche Zurück zur Anmeldung
Dritter Fehler: Anmeldung ok, aber das Token durfte nichts abrufen.

Der Client entsteht per JMAP-Verwaltungsaufruf mit x:OAuthClient/set. Mengenfelder wie redirectUris und scopes sind Objekte mit dem Wert true, keine Arrays:

{"using":["urn:ietf:params:jmap:core","urn:stalwart:jmap"],
 "methodCalls":[["x:OAuthClient/set",{"accountId":"<Admin-Konto-ID>",
  "create":{"c1":{"clientId":"webmail",
   "redirectUris":{"https://webmail.example.org/de/auth/callback":true}}}},"a"]]}

Danach ein zweiter Aufruf mit update und scopes auf {"mail":true,"contacts":true,"calendars":true,"files":true}.

4. Alte Sitzungen sind weg: Direkt nach dem Umstieg stand in Bulwark "Ihre Sitzung ist abgelaufen". Das ist erwartet: Die Tokens aus 0.16 gelten nicht mehr, jeder Nutzer muss sich einmal neu anmelden.

Bulwark-Anmeldeseite mit dem Hinweis, dass die Sitzung abgelaufen ist, und der Schaltfläche Mit SSO anmelden
Erwartet nach der Migration: Die Tokens aus 0.16 sind ungültig.

5. Bulwark muss mit: Laut den Release Notes von Bulwark 1.18.0 ist das die erste Version, die mit Stalwart 1.0 arbeitet. Wir liefen auf 1.17.2 und sind auf 1.19.1 gegangen. Das ist eine Herstellerangabe, wir haben 1.17.2 nicht gegen 1.0.2 getestet.

6. Ein geteilter Bucket wird beschrieben: Im Modus copy schrieb das Werkzeug alle 69.553 Objekte unter denselben Schlüsseln zurück in den Produktiv-Bucket. Anzahl und Größe blieben gleich, und laut README schreibt copy im geteilten Store nur dieselben Bytes unter ihre inhaltsadressierten Schlüssel. Wir haben die Gleichheit nicht per Hash geprüft. Wichtiger: v1 löscht laut README Blobs, die es nicht mehr kennt, und danach ist 0.16 kein sicherer Rückweg mehr.

7. AppArmor und Docker-Details: Auf unserem Host verbietet das AppArmor-Profil docker-default Unix-Sockets. Sowohl stalwart-migrate als auch Stalwart 1.0.2 brachen mit failed to create UnixStream: Permission denied ab. Der bestehende Compose-Block setzte apparmor:unconfined schon, für docker run mussten wir die Option selbst setzen. Dazu kam, dass docker compose fehlte und der erste Befehl ohne Wirkung blieb.

Was wir nicht geprüft haben

Zwölf Spam-Tags LLM_* aus dem alten Bestand hat v1 verworfen, ein Gegenstück gibt es nicht. Wir wissen noch nicht, ob ihr Fehlen die Spam-Bewertung verändert. Der Suchindex baut sich nach der Migration neu auf, deshalb lässt sich die Suche in alten Mails erst später beurteilen. Und ob die Admin-Rolle nach dem Wegfall von mehr als 40 Rechten alles abdeckt, haben wir nicht durchgespielt. Lizenzfragen für die Enterprise-Ausgabe sind mit dem Hersteller zu klären.

Checkliste zum Mitnehmen

  1. Snapshot der VM, Backup prüfen.
  2. Auf 0.16.27 heben, Image-Zeile komplett ersetzen.
  3. prepare im Terminal, Bericht lesen, Warnungen zu OAuth und Rollen notieren.
  4. run im Betrieb, mehrfach, Zeiten messen.
  5. run --final mit gestopptem Server, --exclude spam_model bei geteiltem Bucket.
  6. OAuth-Client mit Redirect-URIs aller Sprachen und mit Scopes anlegen.
  7. Webmail auf eine Version bringen, die Stalwart 1.0 unterstützt.
  8. Mit einem eigenen Konto anmelden, Mail lesen, Mail senden, Antwort abwarten.

Solche Umstiege planen wir für Kunden auf Wunsch mit, die Details stehen auf der Seite Leistungen. Wer die Betriebsfragen drumherum sucht, findet mehr im Themenbereich Server und Netzwerk, zum Beispiel zu Backups mit der 3-2-1-Regel auf Proxmox.

Weiterführende Quellen

Fragen?
Kann ich Stalwart 0.16 direkt auf 1.0 aktualisieren?+

Nein. Die Release Notes von 1.0.0 sagen, dass sich 0.16 nicht an Ort und Stelle aktualisieren lässt. Das Werkzeug stalwart-migrate kopiert Konten, Mails und Einstellungen in einen neuen, leeren v1-Store. Es verlangt als Ausgangsstand 0.16.27.

Wie lange steht der Mailserver bei der Migration still?+

Bei uns rund zwei Minuten. Der Erstlauf mit 119.605 Mails dauerte 778 Sekunden, lief aber im laufenden Betrieb. Nur der letzte Lauf brauchte 47 Sekunden bei gestopptem Server. Die Zahl gilt für fünf Konten und 7,4 GB Datenbestand.

Warum kommt nach der Migration der Fehler Unknown client id?+

Stalwart 0.16 akzeptierte jede client_id beim OAuth-Login. Version 1.0 verlangt registrierte OAuth-Clients. Webmail-Oberflächen wie Bulwark brauchen deshalb ein Client-Objekt mit passenden Redirect-URIs und Scopes.