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.
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.
| Schritt | Dauer | Ergebnis |
|---|---|---|
| Backup der VM, Snapshot, inkrementell | 1 min 45 s und 5 min 51 s | ohne Fehler |
Erstlauf run, 119.605 Mails, 69.547 Blobs | 778 s | 0 Fehler, Server lief weiter |
Differenzlauf run | 32 s | 1 Mail nachgezogen |
Letzter Lauf run --final, Server gestoppt | 47 s | 0 Fehler, 0 Abweichungen |
Prüfung verify --full | 18 s | 5 Konten, 0 Abweichungen |
| Pause der Mail-Annahme | rund 2 min | Gateways hielten Mails zurück |
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.

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.

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.

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.

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
- Snapshot der VM, Backup prüfen.
- Auf 0.16.27 heben, Image-Zeile komplett ersetzen.
prepareim Terminal, Bericht lesen, Warnungen zu OAuth und Rollen notieren.runim Betrieb, mehrfach, Zeiten messen.run --finalmit gestopptem Server,--exclude spam_modelbei geteiltem Bucket.- OAuth-Client mit Redirect-URIs aller Sprachen und mit Scopes anlegen.
- Webmail auf eine Version bringen, die Stalwart 1.0 unterstützt.
- 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
- Stalwart v1.0.0, Release Notes (26. Oktober 2026, Herstellerangabe)
- Stalwart v1.0.2, Release Notes (2. November 2026, Herstellerangabe)
- stalwart-migrate, README und Werkzeug
- Bulwark 1.18.0, Release Notes
- Logos: Simple Icons (Dateien proxmox.svg, minio.svg, docker.svg), Marken bei den jeweiligen Inhabern
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.
senn-tech