Von einer Shared-Hosting-PHP-App zu moderner Infrastruktur migrieren

#PHP-Shared-Hosting-Migration
Sandor Farkas - Founder & Lead Developer at Wolf-Tech

Sandor Farkas

Gründer & Lead Developer

Experte für Softwareentwicklung und Legacy-Code-Optimierung

Erstaunlich viele funktionierende europaeische Unternehmen betreiben ihre Kernsoftware noch immer auf Shared-Hosting-Konten, die Anfang der 2010er-Jahre eingerichtet wurden. Die Anwendung ist PHP 7.2 (manchmal 5.6), laeuft unter suPHP auf einem Plesk-Control-Panel, wird durch Hineinziehen von Dateien in FileZilla deployt, und die einzige "Backup-Strategie" ist das, was der Hosting-Anbieter zufaellig um drei Uhr morgens tut. Es gibt keine Git-Historie. Es gibt drei Ordner namens /admin_old/, /admin_old2/ und /admin_FINAL/. Die einzige Person, die den Deployment-Workflow jemals verstanden hat, nahm 2019 einen Job in Muenchen an.

Das ist kein seltener Sonderfall. Ein erheblicher Teil des deutschen Mittelstands, franzoesische B2B-Dienstleister und familiengefuehrte europaeische E-Commerce-Betriebe laufen genau auf dieser Art von Stack. Die Anwendung generiert echten Umsatz. Sie macht aber auch jede Aenderung riskant, jedes Audit unbeantwortbar und jedes Entwicklergespraech schwierig.

Eine erfolgreiche PHP-Shared-Hosting-Migration zu moderner Infrastruktur (containerisiert, versionskontrolliert, von einer Pipeline deployt) erfordert keinen Neuschrieb. Sie erfordert eine bewusste Abfolge kleiner, umkehrbarer Schritte, die die Anwendung durchgehend am Laufen halten. Dieser Beitrag fuehrt durch das Playbook, das wir bei Wolf-Tech anwenden, wenn wir diese Kategorie von Legacy-PHP-Deployments modernisieren.

Warum Shared Hosting zur Belastung wird

Der Grund, warum Teams bei Shared Hosting bleiben, ist einfach: Es funktioniert. Die Anwendung bedient Kunden, die monatliche Rechnung ist klein, und etwas zu aendern birgt das Risiko, die einzige Umsatzquelle des Unternehmens zu zerstoeren. Die Traegheit ist rational.

Was die Rechnung veraendert, ist der Moment, in dem die verborgenen Kosten einen einholen. Shared-Hosting-Konten begrenzen PHP-Versionen typischerweise auf das, was der Anbieter unterstuetzt, was zwei oder drei Hauptversionen hinterherhinkt. Bis 2026 laeuft ein Konto mit PHP 7.4 seit ueber zwei Jahren auf einer nicht mehr unterstuetzten Runtime, und die Sicherheitspatches, von denen die Anwendung abhaengt, werden nicht mehr ausgeliefert. Composer-Abhaengigkeiten verweigern die Installation, weil sie PHP 8.1+ benoetigen. Moderne Bibliotheken sind nicht verfuegbar.

Betrieblich bedeutet das Fehlen von Versionskontrolle, dass jede Aenderung eine manuelle Dateibearbeitung auf einem Produktionsserver ist, und die einzige Moeglichkeit, herauszufinden, was sich geaendert hat, ist der Vergleich von Zeitstempeln. Rollbacks erfordern ein Backup, das es vielleicht gibt oder nicht. Es gibt keine Staging-Umgebung, weil das Erstellen einer solchen ein weiteres zu verwaltendes Hosting-Konto bedeuten wuerde. Mehrere Entwickler koennen nicht gleichzeitig an der Codebasis arbeiten, ohne sich per Chat abzustimmen, um nicht gegenseitig ihre Aenderungen zu ueberschreiben.

Diese Einschraenkungen zeigen sich nicht als ein einzelnes dramatisches Versagen. Sie zeigen sich als stetig langsamere Feature-Auslieferung, wachsende Scheu, irgendetwas anzufassen, und schliesslich als Unfaehigkeit, kompetente Entwickler einzustellen, weil kein erfahrener Ingenieur so arbeiten will. Bis ein Unternehmen sich zur Modernisierung entschliesst, hat die Anwendung oft jahrelang zufaellige Komplexitaet angesammelt, die die Migration schwerer macht, als sie frueher gewesen waere.

Schritt eins: Die Codebasis unter Versionskontrolle bringen, bevor irgendetwas geaendert wird

Der erste Schritt jeder PHP-Shared-Hosting-Migration ist, eine Quelle der Wahrheit fuer den Code zu schaffen. Bevor sich irgendetwas anderes aendert (kein PHP-Versions-Upgrade, keine Infrastrukturarbeit, kein Refactoring), muss die Anwendung in Git leben, mit dem Produktionsserver als kanonischem Ausgangspunkt.

Die Mechanik ist banal, aber es lohnt sich, sie sorgfaeltig zu erledigen. Verwende SFTP oder rsync, um das gesamte Document-Root und alle zugehoerigen Ordner (Cron-Skripte, Includes ausserhalb des Web-Roots, Konfigurationsdateien) in ein lokales Arbeitsverzeichnis zu ziehen. Initialisiere ein Git-Repository, erstelle eine .gitignore, die Runtime-Ordner ausschliesst (hochgeladene Nutzerdateien, Cache-Verzeichnisse, Logs), und committe alles als ersten Commit mit einer Nachricht wie "Initial import from production, 2026-04-22".

# Anwendung vom Shared Hosting ziehen
rsync -avz --exclude='cache/' --exclude='logs/' --exclude='uploads/' \
  user@oldhost.example.com:/var/www/vhosts/app/httpdocs/ ./app-import/

cd app-import
git init
git add .
git commit -m "Initial import from production, 2026-04-22"
git remote add origin git@github.com:company/legacy-app.git
git push -u origin main

Der naechste Commit sollte eine README.md sein, die dokumentiert, was beim Import entdeckt wurde: die PHP-Version in Produktion, den Namen und die Version des Datenbankservers, die im Control-Panel registrierten Cron-Jobs, alle Drittanbieterdienste, die die Anwendung aufruft, sowie alle Umgebungsvariablen oder Konfigurationskonstanten. Dieses Dokument ist der Anker der Migration. Es haelt die Welt fest, wie sie ist, bevor jemand versucht, sie zu aendern.

Wichtig: Refactore noch nichts. Die Versuchung, das offensichtliche Chaos aufzuraeumen (die doppelten Ordner, der tote Code, die inline hinterlegten Zugangsdaten), ist gross. Widerstehe ihr. Refactoring, bevor du eine Baseline hast, macht es unmoeglich zu wissen, ob ein Bug durch das Refactoring eingefuehrt wurde oder vorher existierte. Die Codebasis muss exakt so reproduzierbar sein, wie sie in Produktion lief, bevor irgendeine Bereinigung beginnt.

Schritt zwei: Containerisieren, ohne die Anwendung zu aendern

Der zweite Schritt ist, die Anwendung lokal und in einer kontrollierten Umgebung zum Laufen zu bringen, identisch zu der Art, wie sie auf dem Shared Host laeuft. Das richtige Werkzeug dafuer ist Docker, konfiguriert, um die Produktions-Runtime so genau wie moeglich nachzubilden.

Der Containerisierungsschritt fuer PHP ist der Punkt, an dem die meisten Modernisierungsbemuehungen scheitern, indem sie zu viel auf einmal versuchen. Das Ziel hier ist nicht, PHP zu aktualisieren, Webserver zu wechseln oder die Anwendung umzustrukturieren. Das Ziel ist, ein Docker-Image zu erstellen, das die bestehende Anwendung unveraendert ausfuehrt (auf derselben PHP-Version, mit denselben Erweiterungen, gegen eine Datenbank, die die Produktion spiegelt), damit das Team entwickeln und testen kann, ohne den Live-Server anzufassen.

Ein pragmatisches Dockerfile fuer diese Phase sieht so aus:

# Die Produktions-PHP-Version exakt nachbilden, auch wenn sie alt ist
FROM php:7.4-apache

# Dieselben Erweiterungen installieren, die der Shared Host aktiviert hatte
RUN apt-get update && apt-get install -y \
    libpng-dev libjpeg-dev libfreetype6-dev libzip-dev unzip \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install gd mysqli pdo_mysql zip opcache

# Produktions-php.ini-Einstellungen nachbilden (memory_limit, Upload-Groessen, Zeitzone)
COPY docker/php.ini /usr/local/etc/php/php.ini

# Apache mod_rewrite aktivieren, falls die Legacy-App .htaccess verwendet
RUN a2enmod rewrite

# Anwendungscode kopieren
COPY . /var/www/html/

# Dateibesitz nachbilden, den der Shared Host verwendet hat
RUN chown -R www-data:www-data /var/www/html

Fuehre dies mit einer docker-compose.yml aus, die den Anwendungscontainer mit einem MySQL- oder MariaDB-Container in derselben Hauptversion wie die Produktion paart, befuellt mit einem bereinigten Snapshot der Produktionsdatenbank. Sobald die Anwendung in dieser Umgebung das erste Mal startet und eine echte Seite ausliefert, hat das Team eine bedeutsame Schwelle ueberschritten: Die Anwendung existiert nun an einem anderen Ort als dem Shared-Hosting-Konto, und Aenderungen koennen vor dem Deployment getestet werden.

Das ist auch der Moment, die impliziten Abhaengigkeiten zu entdecken, die der Shared Host stillschweigend bereitstellte. Fehlende PHP-Erweiterungen, von der Anwendung angenommene Dateiberechtigungen, Pfade, die fest auf die Verzeichnisstruktur des alten Hosting-Anbieters codiert sind, Mail-Konfiguration, die funktionierte, weil der Shared Host ein lokales SMTP-Relay hatte: All das taucht waehrend der Containerisierung auf. Dokumentiere jeden Punkt und behebe ihn entweder in der Container-Konfiguration oder notiere ihn als Deployment-Anforderung fuer die neue Infrastruktur.

Schritt drei: Parallele moderne Infrastruktur aufsetzen

Sobald die Anwendung containerisiert ist, besteht der naechste Schritt darin, die Zielinfrastruktur bereitzustellen. Das Ziel haengt von der Groesse der Anwendung und der betrieblichen Reife des Teams ab, doch fuer die meisten europaeischen mittelgrossen PHP-Anwendungen ist der richtige Ausgangspunkt ein einzelner virtueller Server bei einem Anbieter mit europaeischen Rechenzentren (Hetzner, OVH, Scaleway oder AWS Frankfurt), der Docker betreibt, mit Nginx und Let's-Encrypt-TLS-Zertifikaten davor und einem Managed-Database-Dienst fuer MySQL oder PostgreSQL.

Die Architektur vermeidet bewusst Over-Engineering. Kubernetes, Multi-Region-Deployments und Serverless-Plattformen sind verlockend, aber in dieser Phase unnoetig. Der Erfolg der Migration haengt davon ab, die Zahl der gleichzeitig veraenderten Variablen zu minimieren. Ein einzelner VPS, der Docker Compose betreibt, ist enorm viel leistungsfaehiger als das Shared-Hosting-Konto, das er ersetzt, und er bietet ein Fundament, das sich spaeter zu ausgefeilteren Architekturen entwickeln kann.

Die Managed Database ist der eine Punkt, an dem es sich lohnt, Geld auszugeben, statt selbst zu hosten. Backup, Point-in-Time-Recovery, Versions-Upgrades und Replikation sind betriebliche Anliegen, die Managed Services gut loesen und die niemand in einem kleinen Team um drei Uhr morgens manuell erledigen will. RDS, Hetzners Managed PostgreSQL oder DigitalOceans Managed MySQL funktionieren alle gut fuer dieses Profil.

Die Migration der Datenbank selbst verdient explizite Planung. Fuer eine kleine Datenbank (unter 10 GB) genuegt ein mysqldump-Export vom Shared Host gefolgt von einem Import in die Managed-Instanz. Fuer groessere Datenbanken oder Anwendungen mit geringer Toleranz fuer Schreib-Ausfallzeiten laesst MySQL-Replikation von der alten zur neuen Datenbank (die ein paar Tage vor dem Cutover laeuft) die neue Instanz schrittweise aufholen und reduziert die Cutover-Zeit auf Sekunden:

# Auf dem Shared Host (falls SSH-Zugriff verfuegbar ist)
mysqldump --single-transaction --master-data=2 \
  --databases app_production > app_dump.sql

# Auf der neuen Managed Database
mysql -h new-db.example.com -u admin -p < app_dump.sql

# Replikation konfigurieren, falls der Cutover nahezu sofort erfolgen muss
# (anbieterspezifisch; Managed-DB-Dokumentation konsultieren)

Fuer Shared Hosts, die keinen SSH-Zugriff erlauben (haeufig bei Plesk und cPanel), verwende die nativen Backup-Tools des Control-Panels oder die Datenbank-Export-Schnittstelle. Verifiziere den Import, indem du repraesentative Abfragen der Anwendung gegen die neue Datenbank ausfuehrst, bevor irgendein Produktionsverkehr involviert ist.

Schritt vier: CI/CD, das FTP-Deployment ersetzt

Die wirkungsvollste einzelne Aenderung in dieser Migration ist, manuelle FTP-Deploys durch ein automatisiertes, von Git gesteuertes CI/CD-Setup zu ersetzen. Sobald der Code in Versionskontrolle lebt und in Containern laeuft, wird Deployen zur Sache, ein Image zu bauen, es in eine Registry zu pushen und den Produktionsserver anzuweisen, die neue Version zu ziehen.

GitHub Actions ist fuer die meisten Teams die pragmatische Wahl, weil es sich in das Git-Repository integriert, in dem die Codebasis bereits lebt. Ein minimaler Workflow fuer eine dockerisierte PHP-Anwendung:

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Log in to container registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ghcr.io/company/legacy-app:${{ github.sha }}

      - name: Trigger deployment
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: deploy
          key: ${{ secrets.DEPLOY_SSH_KEY }}
          script: |
            cd /opt/app
            export IMAGE_TAG=${{ github.sha }}
            docker compose pull
            docker compose up -d --no-deps app
            docker image prune -f

Diese Pipeline laeuft in wenigen Minuten, erzeugt ein mit dem Commit-SHA getaggtes Docker-Image und deployt es ohne manuelle Eingriffe auf den Produktionsserver. Rollbacks werden zur Ein-Zeilen-Operation: per SSH auf den Server, IMAGE_TAG auf den SHA des vorherigen Commits setzen und erneut docker compose up -d ausfuehren.

Die Disziplin, die dies erzwingt, ist betraechtlich. Jede Aenderung lebt nun in einem Git-Commit mit Nachricht und Autor. Jedes Deployment ist aus dem Commit reproduzierbar, der es erzeugt hat. Mehrere Entwickler koennen an der Codebasis arbeiten, ohne sich gegenseitig zu ueberschreiben. Das Team, das zuvor jede Produktionsaenderung fuerchtete, liefert nun mehrmals pro Woche aus.

Schritt fuenf: DNS-Cutover mit definiertem Rollback-Fenster

Der letzte Schritt ist, den Produktionsverkehr vom Shared-Hosting-Konto auf die neue Infrastruktur zu verlagern. Dies ist der einzige Schritt, der sich wirklich nicht teilweise durchfuehren laesst. Irgendwann muss der DNS-Eintrag auf den neuen Server zeigen.

Der Cutover-Plan, der das Risiko minimiert, hat vier Elemente. Erstens: Senke die DNS-TTL des Anwendungs-Hostnamens mindestens 48 Stunden vor dem Cutover auf 300 Sekunden (5 Minuten), damit DNS-Aenderungen schnell propagieren und ein Rollback schnell geht. Zweitens: Plane den Cutover fuer die verkehrsaermste Stunde der Woche, die fuer die meisten europaeischen B2B-Anwendungen sonntags zwischen 02:00 und 04:00 Uhr MEZ liegt. Drittens: Halte beide Umgebungen mindestens 48 Stunden nach dem Cutover parallel am Laufen, mit dem Shared Host warmgehalten und bereit, bei Bedarf erneut Verkehr zu empfangen. Viertens: Ueberwache Fehlerraten, Antwortzeiten und Datenbankverbindungen kontinuierlich waehrend und nach dem Cutover.

Wenn die neue Datenbank als Replik aufgesetzt wurde, stoppe die Replikation und befoerdere sie im Cutover-Moment zur Primaerdatenbank, dann aendere den DNS-Eintrag. Wenn die Datenbank per mysqldump migriert wurde, nimm dir ein kurzes Wartungsfenster (typischerweise 15 bis 30 Minuten) fuer einen finalen Sync, dann fuehre den Cutover durch.

Die Rollback-Bedingung muss im Voraus definiert werden. Ein typischer Schwellenwert ist "wenn die 5xx-Fehlerrate fuer mehr als 10 Minuten 1 % uebersteigt oder sich die mediane Antwortzeit mehr als verdoppelt, sofort auf den Shared Host zurueckrollen". Dies vor dem Cutover zu definieren verhindert die panische "Sollen wir zurueckrollen?"-Debatte um 03:00 Uhr, wenn das Urteilsvermoegen am schlechtesten ist.

Haeufige Fehler bei Shared-Hosting-Migrationen

Der mit Abstand haeufigste Fehler ist, PHP zur selben Zeit zu aktualisieren, in der die Infrastruktur verlagert wird. Gleichzeitig von Shared Hosting zu Docker und von PHP 7.4 zu PHP 8.3 zu migrieren, kombiniert zwei unabhaengige Risiken zu einem Ereignis. Tue es nacheinander: Containerisiere zuerst auf der bestehenden PHP-Version, fuehre den Cutover auf die neue Infrastruktur durch, aktualisiere dann PHP als separate, kleinere Migration mit eigenem Testzyklus.

Der zweite haeufige Fehler ist, die Migration als den Moment zu behandeln, alles zu reparieren. Inline-Zugangsdaten, fehlende Tests, die doppelten admin_old/-Ordner, die Funktion namens final_v3_real_use_this(): All das sind echte Probleme, die es verdienen, angegangen zu werden. Keines davon gehoert in den Migrations-Commit. Nimm dir vor, sie zu beheben, nachdem die Anwendung auf moderner Infrastruktur laeuft, wo jeder Fix in Minuten deployt und zurueckgerollt werden kann, falls er etwas zerstoert.

Der dritte haeufige Fehler ist, vor dem Cutover zu wenig in Observability zu investieren. Der Shared Host hatte wahrscheinlich kein echtes Monitoring ueber Uptime-Checks hinaus. Die neue Infrastruktur sollte mit strukturiertem Logging (Monolog, das JSON schreibt), grundlegenden Metriken (Server-CPU, Speicher, Antwortzeiten) und Uptime-Monitoring von mindestens einem externen Standort ausgeliefert werden. Ohne dies bleibt ein Problem nach dem Cutover unsichtbar, bis Kunden es melden.

Eine fokussierte Optimierung von Legacy-Code vor Beginn der Migration identifiziert typischerweise die meisten dieser Fallen im Voraus und erzeugt einen sequenzierten Plan, der sie vermeidet.

Was Modernisierung freisetzt

Der strategische Wert dieser abgeschlossenen Migration ist nicht die neue Infrastruktur selbst, sondern die Veraenderungen, die die neue Infrastruktur moeglich macht. Mit der Anwendung in Git und per CI/CD deployt wird das Einstellen eines zweiten Entwicklers ohne betriebliches Chaos machbar. Mit Docker ist das Erstellen einer Staging-Umgebung eine Sache, dasselbe Image gegen eine separate Datenbank laufen zu lassen. Mit verwalteten Backups und Point-in-Time-Recovery kann der Gruender aufhoeren, sich um das Szenario eines Festplattenausfalls um 03:00 Uhr zu sorgen.

Diese Faehigkeiten ermoeglichen dann die naechste Arbeitsphase: inkrementelles Refactoring, Framework-Adoption (oft Symfony fuer PHP-Anwendungen dieser Groesse), Testabdeckung kritischer Geschaeftslogik und schliesslich die Art von Feature-Geschwindigkeit, die das Unternehmen seit dem Start nicht mehr gesehen hat. Nichts davon ist moeglich, solange die Anwendung noch auf Shared Hosting lebt. Alles davon wird zur Routine, sobald die Migration abgeschlossen ist.

Wenn dein Unternehmen eine umsatzgenerierende PHP-Anwendung auf Shared Hosting betreibt und du das Risiko der Modernisierung gegen die Kosten des Weiterbetriebs abwaegst, bietet Wolf-Tech eine strukturierte Migrationsbewertung, die einen sequenzierten, risikoarmen Plan erzeugt, der auf die spezifischen Abhaengigkeiten und Rahmenbedingungen deiner Anwendung zugeschnitten ist. Kontaktiere uns unter hello@wolf-tech.io oder besuche wolf-tech.io fuer eine kostenlose Erstberatung.