Eine TYPO3-Installation von Version 10.4 ELTS auf v12 LTS zu migrieren ist nichts, was du an einem Nachmittag machst.
Es geht über drei Major-Versionen, erfordert die Umstellung einer Classic-Installation auf Composer und betrifft eigene Extensions, die nie dafür gebaut wurden, ein Upgrade zu überleben.
Dieser Beitrag dokumentiert genau, wie ich vorgegangen bin – Schritt für Schritt, ohne Downtime, und mit automatisierten Tests als Nachweis, dass nichts kaputtgegangen ist.
Der Ausgangspunkt
Das Projekt: ein Multisite-TYPO3-10.4-ELTS-Setup, Classic-Installation (kein Composer), mehrere eigene Extensions über den Extension Manager installiert, und die harte Anforderung, dass alle Websites nach der Migration identisch aussehen und sich identisch verhalten müssen. Keine visuellen Regressionen, keine kaputten Routen, keine fehlenden Inhalte.
Das Ziel: TYPO3 v12.4 LTS mit vollständigem Composer-Setup auf PHP 8.2.
Der Upgrade-Pfad ist kein einzelner Sprung. Du musst durch jede Major-Version:
10.4 → 11.5 → 12.4
Versionen zu überspringen wird nicht unterstützt. Jeder Schritt hat eigene Upgrade-Wizards, Deprecations und Änderungen am Datenbankschema. Respektier den Pfad.
Bevor du eine einzige Datei anfasst, mach ein vollständiges Backup. Datenbank und Dateisystem. Und dann noch eins. Migrationen gehen schief, und „ich dachte, ich hätte ein Backup" ist keine Wiederherstellungsstrategie.
Schritt 1: Erst mal alles prüfen
Bevor ich eine Zeile Code geschrieben habe, habe ich die komplette Installation geprüft:
- Welche Fremd-Extensions sind installiert, und gibt es davon v12-kompatible Versionen?
- Welche eigenen Extensions gibt es, was tun sie, und welche TYPO3-APIs nutzen sie?
- Welche PHP-Version läuft auf dem Server? (v12 braucht PHP 8.2+)
- Gibt es TypoScript, das veraltete oder entfernte Funktionen nutzt?
Für Fremd-Extensions schaust du im TYPO3 Extension Repository (TER) und auf Packagist. Wenn eine Extension nicht für v12 aktualisiert wurde, musst du entweder eine Alternative finden, einen Fork pflegen oder sie neu schreiben. Finde das vorher heraus – nicht mitten in der Migration.
Für eigene Extensions lässt du den TYPO3 Extension Scanner im Install Tool laufen. Er markiert veraltete API-Nutzung und sagt dir genau, was für jede Version angepasst werden muss. Lass ihn vor der Migration laufen und heb die Ausgabe auf.
Schritt 2: Funktionstests aufsetzen, bevor du irgendetwas anfasst
Das Wichtigste, was du vor einer Migration tun kannst, ist eine Baseline des aktuellen Systems festzuhalten. Damit hast du nach jedem Upgrade-Schritt etwas zum Vergleichen. Wenn der Test durchläuft, weißt du, dass die Seite noch wie erwartet funktioniert. Wenn er fehlschlägt, weißt du genau, was wann kaputtgegangen ist.
Mein Ansatz: alle URLs aus der Sitemap jeder Site crawlen, den Hauptinhaltsbereich extrahieren, dynamische Teile entfernen und den MD5-Hash des Rests speichern. Nach jedem Migrationsschritt denselben Crawl laufen lassen und die beiden Snapshots vergleichen.
Ein MD5-Hash der kompletten Seite ist zu fragil – Zeitstempel, Session-Tokens, Cache-Buster und Nonces im gerenderten HTML erzeugen einen anderen Hash, auch wenn sich inhaltlich nichts geändert hat. Stattdessen extrahiere ich nur den Hauptinhaltsbereich jeder Seite (das -Tag oder das primäre Content-Div), entferne alle Tags und dynamischen Attribute wie data-* und nonce und hashe nur das, was übrig bleibt. Das ist der Inhalt, den deine Nutzer tatsächlich sehen – und genau den darf die Migration nicht kaputtmachen.
Für jede URL hält das Skript drei Dinge fest: den HTTP-Statuscode, den Seitentitel und den Inhalts-Hash. Alles landet in einer JSON-Datei. Nach jedem Upgrade-Schritt lässt du das Skript erneut gegen die neue Umgebung laufen und vergleichst die beiden Dateien. Jede URL mit geändertem Hash, abweichendem Statuscode oder geändertem Titel ist eine mögliche Regression, die untersucht werden muss, bevor es weitergeht.
Hashe nicht das komplette HTML — Zeitstempel, Cache-Buster und Nonces machen jeden Hash anders, auch wenn sich nichts geändert hat. Hashe nur den Hauptinhaltsbereich, nachdem du dynamische Attribute entfernt hast. Das ist das, was deine Nutzer wirklich sehen.
Schritt 3: Eigene Extensions in Composer-Pakete umwandeln
Wenn Extensions über den TYPO3 Extension Manager installiert wurden, liegen sie in typo3conf/ext/ und haben keine composer.json. Composer weiß nicht, dass es sie gibt. Das musst du als Erstes beheben.
Leg für jede eigene Extension eine composer.json im Wurzelverzeichnis an:
{
"name": "vendor/my-extension",
"type": "typo3-cms-extension",
"require": {
"typo3/cms-core": "^10.4 || ^11.5 || ^12.4 "
},
"extra": {
"typo3/cms": {
"extension-key": "my_extension"
}
}
}
Dann registrierst du die Extension als lokales Path-Repository in der Root-composer.json:
"repositories": [
{
"type": "path",
"url": "packages/my_extension"
}
]
Verschieb die Extension von typo3conf/ext/my_extension nach packages/my_extension und fordere sie dann an:
composer require vendor/my-extension:@dev
Über den Extension Manager installierte Extensions sind für Composer unsichtbar. Ohne composer.json und Eintrag als Path-Repository nimmt Composer sie beim Bauen des vendor-Verzeichnisses schlicht nicht mit — und deine Extension verschwindet stillschweigend.
Schritt 4: TYPO3 lokal in der alten Version aufsetzen
Mit allen Extensions als Pakete habe ich eine frische lokale TYPO3-10.4-Installation per Composer aufgesetzt und die Produktionsdatenbank importiert. Das wird die Basis für den Upgrade-Prozess. Das Ziel an dieser Stelle ist eine laufende 10.4-Installation unter Composer, die funktional identisch zum Live-System ist.
Lass das Snapshot-Skript gegen diese lokale Installation laufen, um zu bestätigen, dass die Hashes mit Produktion übereinstimmen, bevor es weitergeht.
Schritt 5: Versions-Upgrades Schritt für Schritt
Für jede Major-Version ist der Ablauf derselbe. Hier das Upgrade von 10.4 auf 11.5 als Beispiel:
# TYPO3-Core und kompatible Extensions aktualisieren
composer require typo3/cms-core:"^11.5" --update-with-all-dependencies
# Datenbankschema aktualisieren
vendor/bin/typo3 database:updateschema
# Alle offenen Upgrade-Wizards ausführen
vendor/bin/typo3 upgrade:run
Lass nach jeder Version den Extension Scanner im Install Tool gegen deine eigenen Extensions laufen und behebe jede markierte Deprecation, bevor du zur nächsten Version gehst. Überspring diesen Schritt nicht. Deprecations in v11 werden in v12 zu fatalen Fehlern.
Wiederhol das für 11.5 → 12.4. Beim Schritt auf 12.4 stell sicher, dass dein Server (oder Docker-Container) mindestens auf PHP 8.1 läuft.
Außerdem bei v12: Die TypoScript-Syntax hat sich deutlich geändert. Das alte page.10-Setup funktioniert noch, aber die neue Site-Konfiguration übernimmt vieles, was vorher in TypoScript lag. Schau dir dein Setup genau an – besonders bei einer Multisite-Installation.
Schritt 6: Stage-Deployment und Tests
Sobald das lokale Upgrade sauber ist und die Snapshot-Tests durchlaufen, geht die migrierte Codebase auf die Stage. Die Stage-Umgebung fährt dieselbe Datenbank wie Produktion (bei Bedarf anonymisiert) und ist der erste echte Test des kompletten Server-Stacks.
Lass das Snapshot-Skript gegen die Stage laufen und vergleich mit der Baseline. Geh jede Site zusätzlich manuell durch, für alles, was die automatisierten Tests übersehen könnten — Formulare, Frontend-Login, Suche, Sprachumschalter.
Schritt 7: Produktion auf einer nicht-öffentlichen Domain
Bevor ich den Live-Traffic umschalte, deploye ich auf die Produktionsinfrastruktur, aber hinter einem nicht-öffentlichen Hostnamen. Damit testest du die echte Serverumgebung — die echte Datenbank, das echte Dateisystem, die echte PHP-Version — ohne irgendetwas für Nutzer sichtbar zu machen.
Lass das Snapshot-Skript ein letztes Mal gegen diese Umgebung laufen. Wenn die Hashes passen, sind wir bereit für den Livegang.
Schritt 8: Umschalten ohne Downtime per HAProxy
Hier zahlt sich ein vernünftiges Proxy-Setup aus. Statt eines DNS-Wechsels (mit TTL-Propagation und einem Zeitfenster, in dem manche Nutzer auf dem alten Server landen) schalte ich das Backend in HAProxy um. Eine Konfigurationsänderung, ein Reload — sofortige Umschaltung, null Downtime.
# Vorher: alter TYPO3-10.4-Server
backend typo3
server srv old_backend:80
# Nachher: neuer TYPO3-12-Server — ändern und haproxy neu laden
backend typo3
server srv new_backend:80
systemctl reload haproxy
Der Traffic wechselt sofort. Der alte Server läuft noch 30 Minuten weiter als Rollback-Option. Wenn irgendetwas komisch aussieht, zeigt ein weiterer Reload den Traffic zurück. Nach 30 Minuten sauberem Monitoring wird der alte Server abgeschaltet.
DNS-basierte Umschaltungen geben dir ein Propagationsfenster, in dem manche Nutzer auf der alten und manche auf der neuen Seite landen — möglicherweise stundenlang. Eine Umschaltung auf Proxy-Ebene ist sofort und in Sekunden reversibel. Wenn du einen Load Balancer davor hast, nutz ihn.
Schritt 9: Den Cache aufwärmen
Nach der Umschaltung ist der Varnish-Cache kalt — jeder Request geht bis zu TYPO3 durch, bis die Seiten wieder gecacht sind. Bei einer Seite mit viel Traffic kann das direkt nach dem Livegang eine Lastspitze verursachen. Wärm den Cache auf, indem du direkt nach dem Umschalten deine Sitemap-URLs crawlst:
# Varnish-Cache aus der Sitemap aufwärmen
curl -s example.com \
| grep -oP '(?<=)[^<]+' \
| xargs -P 4 -I {} curl -s -o /dev/null {}
Das Flag -P 4 fährt 4 parallele Requests. Pass das an, je nachdem wie viele Seiten du hast und wie viel Last dein Server während des Aufwärmens verträgt.