parallel verwalten unter Linux
Wer mehrere Kundenprojekte auf einem Server betreibt, kommt an unterschiedlichen PHP-Versionen nicht vorbei, weil Magento, WordPress und Legacy-Anwendungen selten dieselbe Version verlangen. Mit versionierten FPM-Paketen, update-alternatives und sauber getrennten Pools pro vhost lassen sich mehrere PHP-Versionen konfliktfrei und produktionssicher parallel betreiben.
Inhaltsverzeichnis
- 1. Warum ein Server mehrere PHP-Versionen braucht
- 2. Versionierte Pakete installieren: php8.1-fpm neben php8.3-fpm
- 3. update-alternatives: Wie der PHP-CLI-Standard verwaltet wird
- 4. PHP-FPM Pools: Ein eigener Prozess pro Version
- 5. Nginx vhost pro Projekt an den richtigen Pool binden
- 6. CLI-Version sicher pro Projekt umschalten
- 7. Extensions und php.ini pro Version pflegen
- 8. Magento-Projekte: Versionsanforderungen konkret zuordnen
- 9. Umschalt-Methoden im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum ein Server mehrere PHP-Versionen braucht
Agenturen und Freelancer, die mehrere Kundenprojekte auf einem gemeinsamen Server hosten, stoßen fast zwangsläufig auf das Problem unterschiedlicher PHP-Versionsanforderungen. Ein Magento-2.4.6-Shop verlangt PHP 8.1 oder 8.2, ein frisch aufgesetztes Magento 2.4.8 dagegen PHP 8.3, und daneben läuft vielleicht noch ein älteres WordPress-Projekt, das mit PHP 8.3 wegen veralteter Plugins Fehler wirft. Einen einzigen globalen PHP-Interpreter zu betreiben bedeutet in diesem Alltag entweder ständige Kompatibilitätsprobleme oder ein riskantes Downgrade, das andere Projekte gefährdet.
Die Lösung liegt nicht in virtuellen Maschinen oder Containern für jedes einzelne Projekt, sondern in der nativen Fähigkeit von Debian- und Ubuntu-basierten Distributionen, mehrere PHP-Versionen als eigenständige, parallel installierbare Pakete zu führen. Jede Version bekommt ihren eigenen FPM-Daemon, ihre eigene php.ini und ihren eigenen Satz an Extensions, ohne dass sich die Versionen gegenseitig überschreiben. Dieser Ansatz ist ressourcenschonender als vollständige Container pro Projekt und lässt sich mit den Bordmitteln von apt und systemd vollständig abbilden.
2. Versionierte Pakete installieren: php8.1-fpm neben php8.3-fpm
Die Basis für parallele PHP-Versionen ist das PPA von Ondřej Surý, das für Ubuntu und Debian gepflegte, versionierte PHP-Pakete bereitstellt. Anders als das Standard-Repository, das nur eine einzige PHP-Version pro Distributionsversion anbietet, liefert das PPA php8.1, php8.2, php8.3 und weitere Versionen gleichzeitig installierbar, jede mit eigenem Namenspräfix. Das Paket php8.1-fpm installiert einen komplett eigenständigen FPM-Daemon, der parallel neben php8.3-fpm läuft, jeweils mit eigenem systemd-Service und eigenem Unix-Socket.
Wichtig ist, alle benötigten Extensions für jede Version einzeln zu installieren, da apt Pakete wie php8.1-mysql und php8.3-mysql als komplett getrennte Pakete behandelt. Ein häufiger Anfängerfehler ist, nur die Extensions einer Version zu installieren und beim zweiten Interpreter fehlende Module erst im Produktionsbetrieb zu bemerken, wenn eine Magento-Installation mit Class GD not found oder ähnlichen Fehlern abbricht. Ein kurzes Vergleichsskript mit dpkg -l | grep php8.1 gegen dpkg -l | grep php8.3 deckt fehlende Pakete zuverlässig auf.
# Add the Ondrej Sury PPA for versioned PHP packages (Ubuntu/Debian)
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
# Install PHP 8.1 and PHP 8.3 side by side, each with its own FPM daemon
sudo apt install -y php8.1-fpm php8.1-cli php8.1-mysql php8.1-xml \
php8.1-mbstring php8.1-curl php8.1-zip php8.1-gd php8.1-intl php8.1-bcmath
sudo apt install -y php8.3-fpm php8.3-cli php8.3-mysql php8.3-xml \
php8.3-mbstring php8.3-curl php8.3-zip php8.3-gd php8.3-intl php8.3-bcmath
# Verify both FPM services are active independently
systemctl status php8.1-fpm --no-pager | head -n 5
systemctl status php8.3-fpm --no-pager | head -n 5
# List installed PHP CLI binaries
ls -la /usr/bin/php8.*
3. update-alternatives: Wie der PHP-CLI-Standard verwaltet wird
Nach der Installation existieren die Binärdateien /usr/bin/php8.1 und /usr/bin/php8.3 parallel, aber der einfache Aufruf php -v in der Shell muss auf genau eine davon zeigen. Genau dafür ist update-alternatives gedacht, ein Debian-Mechanismus, der symbolische Links mit Prioritäten verwaltet und einen einzigen generischen Namen wie /usr/bin/php auf eine konkrete Version umbiegt. Jede installierte PHP-Version registriert sich beim Paket-Setup automatisch als Alternative mit einer Priorität, die grob der Versionsnummer entspricht.
Der Befehl sudo update-alternatives --config php öffnet ein interaktives Menü, das alle registrierten PHP-Versionen mit Priorität auflistet und den Nutzer eine Nummer eingeben lässt. Für Automatisierungsskripte, in denen keine Interaktion möglich ist, funktioniert stattdessen update-alternatives --set php /usr/bin/php8.3 ohne Rückfrage. Entscheidend zu verstehen: Dieser Wechsel betrifft ausschließlich den CLI-Aufruf php in der Shell und in Cronjobs, er hat keinerlei Einfluss auf die FPM-Pools, die Webserver über Unix-Sockets ansprechen und komplett unabhängig davon konfiguriert werden.
# List all registered PHP alternatives with their priorities
sudo update-alternatives --list php
# Interactive selection menu
sudo update-alternatives --config php
# Selection Path Priority Status
# 0 /usr/bin/php8.3 83 auto mode
#* 1 /usr/bin/php8.1 81 manual mode
# 2 /usr/bin/php8.3 83
# Non-interactive switch, useful in provisioning scripts
sudo update-alternatives --set php /usr/bin/php8.1
# Confirm the active CLI version
php -v | head -n 1
# Same mechanism also applies to php-config and phpize
sudo update-alternatives --config php-config
sudo update-alternatives --config phpize
4. PHP-FPM Pools: Ein eigener Prozess pro Version
PHP-FPM organisiert Arbeitsprozesse in sogenannten Pools, die jeweils in einer eigenen Konfigurationsdatei unterhalb von /etc/php/8.3/fpm/pool.d/ definiert werden. Jede installierte PHP-Version bringt standardmäßig einen Default-Pool namens www.conf mit, der bereits einen eigenen Unix-Socket unter /run/php/php8.3-fpm.sock öffnet. Für den produktiven Mehrprojektbetrieb reicht der Default-Pool aber selten aus, weil jedes Projekt eigene Ressourcenlimits, einen eigenen Systembenutzer und ein eigenes Prozessmanagement braucht, um sich nicht gegenseitig zu beeinflussen.
Ein sauberes Setup legt für jedes Projekt einen eigenen Pool mit eindeutigem Namen an, etwa shop-kunde-a.conf, mit eigenem user, group und eigenem Socket-Pfad. Die Direktive pm = dynamic mit individuell abgestimmten Werten für pm.max_children, pm.start_servers und pm.max_requests verhindert, dass ein speicherhungriges Projekt die FPM-Worker eines anderen Projekts über PHP-interne Memory-Limits hinaus verdrängt. Nach jeder Pool-Änderung ist ein systemctl reload php8.3-fpm nötig, das laufende Requests zu Ende bedient, bevor die neue Konfiguration greift.
; /etc/php/8.3/fpm/pool.d/shop-kunde-a.conf
; Dedicated pool for a single Magento project on PHP 8.3
[shop-kunde-a]
user = shop-kunde-a
group = shop-kunde-a
; Unique socket path, referenced later in the Nginx vhost
listen = /run/php/php8.3-fpm-shop-kunde-a.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 12
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500
; Per-pool php.ini overrides, isolated from other pools
php_admin_value[memory_limit] = 2G
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_value[error_log] = /var/log/php/shop-kunde-a-error.log
php_flag[display_errors] = off
5. Nginx vhost pro Projekt an den richtigen Pool binden
Der letzte Baustein ist die Verbindung zwischen Nginx und dem passenden FPM-Socket. In der fastcgi_pass-Direktive des jeweiligen Server-Blocks trägt man den exakten Socket-Pfad des zugehörigen Pools ein, nicht den Default-Socket der Version. Dadurch entscheidet allein die Nginx-Konfiguration, welche PHP-Version und welcher Ressourcen-Pool ein bestimmtes Projekt bedient, unabhängig davon, welche Version gerade als globaler CLI-Standard über update-alternatives gesetzt ist.
Diese Trennung ist der entscheidende Vorteil gegenüber einer einzigen globalen PHP-Installation: Ein Update des CLI-Standards für Wartungsskripte auf dem Server beeinflusst keinen einzigen laufenden vhost, weil Nginx ausschließlich über den fest verdrahteten Socket-Pfad kommuniziert. Wer testen will, ob ein Projekt mit einer neuen PHP-Version kompatibel ist, kopiert einfach den vhost, ändert den Socket-Pfad auf den Test-Pool und testet über einen alternativen Hostnamen, ohne den produktiven vhost anzurühren.
# /etc/nginx/sites-available/shop-kunde-a.conf
server {
listen 443 ssl http2;
server_name shop-kunde-a.de;
root /var/www/shop-kunde-a/pub;
index index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# Points directly at the dedicated pool socket for PHP 8.3
fastcgi_pass unix:/run/php/php8.3-fpm-shop-kunde-a.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 300;
}
}
# Reload after every vhost or pool change
sudo nginx -t && sudo systemctl reload nginx
sudo systemctl reload php8.3-fpm
6. CLI-Version sicher pro Projekt umschalten
Für Deployment-Skripte, Composer-Aufrufe und bin/magento-Kommandos innerhalb eines einzelnen Projekts ist der globale update-alternatives-Standard oft zu grob, weil auf demselben Server mehrere Projekte mit unterschiedlichen CLI-Anforderungen gleichzeitig arbeiten müssen. Der sichere Weg führt über die direkte, versionsspezifische Binärdatei statt über den generischen php-Befehl: /usr/bin/php8.1 bin/magento setup:upgrade ruft garantiert PHP 8.1 auf, unabhängig davon, welche Version gerade als globaler Standard gesetzt ist.
Für Composer empfiehlt sich dieselbe Strategie über die Umgebungsvariable COMPOSER_PHP_PATH oder durch direkten Aufruf mit der gewünschten Binärdatei, etwa /usr/bin/php8.3 /usr/local/bin/composer.phar install. In Deploy-Skripten setzt man am Anfang eine Variable wie PHP_BIN="/usr/bin/php8.3" und referenziert diese konsequent für alle folgenden Aufrufe, statt sich auf den zum Ausführungszeitpunkt zufällig aktiven globalen Standard zu verlassen. Das macht das Skript reproduzierbar, unabhängig davon, wer es wann auf welchem Server startet.
#!/usr/bin/env bash
# deploy-shop-kunde-a.sh: pin PHP version explicitly for reproducible deploys
set -euo pipefail
readonly PHP_BIN="/usr/bin/php8.3"
readonly PROJECT_DIR="/var/www/shop-kunde-a"
readonly COMPOSER_BIN="/usr/local/bin/composer.phar"
cd "$PROJECT_DIR"
echo "[INFO] Using $($PHP_BIN -v | head -n 1)"
"$PHP_BIN" "$COMPOSER_BIN" install --no-dev --optimize-autoloader
"$PHP_BIN" bin/magento maintenance:enable
"$PHP_BIN" bin/magento setup:upgrade
"$PHP_BIN" bin/magento setup:di:compile
"$PHP_BIN" bin/magento setup:static-content:deploy de_DE en_US -f
"$PHP_BIN" bin/magento maintenance:disable
sudo systemctl reload php8.3-fpm
echo "[OK] Deploy finished with pinned PHP 8.3"
7. Extensions und php.ini pro Version pflegen
Jede parallel installierte PHP-Version besitzt ihr eigenes Konfigurationsverzeichnis, etwa /etc/php/8.1/ und /etc/php/8.3/, jeweils mit getrennten Unterordnern für cli, fpm und mods-available. Das Aktivieren einer Extension über phpenmod muss deshalb pro Version einzeln erfolgen, ein globales Aktivieren gibt es nicht. Der Befehl sudo phpenmod -v 8.3 redis aktiviert die Redis-Extension gezielt nur für PHP 8.3, während PHP 8.1 davon unberührt bleibt, bis derselbe Befehl auch dort ausgeführt wird.
Für projektspezifische php.ini-Werte wie memory_limit oder max_execution_time ist die Pool-Konfiguration aus Abschnitt 4 der bevorzugte Ort, weil sie ausschließlich das jeweilige Projekt betrifft und bei jedem Deploy versioniert im Repository mitgeführt werden kann. Globale Änderungen an der php.ini unter /etc/php/8.3/fpm/php.ini wirken sich dagegen auf alle Pools dieser Version aus und sollten nur für tatsächlich versionsweite Einstellungen wie opcache.enable oder Zeitzonenwerte verwendet werden.
Ein bewährter Kontrollmechanismus vor jedem größeren Rollout ist ein Diff-Vergleich der installierten Extension-Pakete zwischen zwei Versionen, etwa mit dpkg -l | grep php8.1- gegen dpkg -l | grep php8.3-. Ebenso hilfreich ist php8.3 --ini | grep "Loaded Configuration", das anzeigt, welche php.ini-Datei ein bestimmtes CLI-Binary tatsächlich lädt, denn gerade bei mehreren parallelen Versionen ist das keineswegs immer offensichtlich und eine häufige Fehlerquelle bei Support-Anfragen.
8. Magento-Projekte: Versionsanforderungen konkret zuordnen
Magento veröffentlicht mit jeder Minor-Version klar dokumentierte, unterstützte PHP-Bereiche, und diese Bereiche überschneiden sich zwischen aufeinanderfolgenden Magento-Versionen typischerweise nur teilweise. Magento 2.4.6 unterstützt PHP 8.1 und 8.2, Magento 2.4.7 erweitert das um PHP 8.3, und Magento 2.4.8 verlangt PHP 8.3 als Minimum bei gleichzeitiger Unterstützung neuerer Patch-Releases. Wer mehrere Magento-Stores auf unterschiedlichem Patch-Level betreut, landet damit fast zwangsläufig bei mindestens zwei parallel benötigten PHP-Versionen auf demselben Server.
Vor jedem Versionswechsel lohnt der Blick in die composer.json des jeweiligen Magento-Projekts, in der die Direktive require.php das exakt unterstützte Intervall angibt, etwa ">=8.3.0 <8.4.0-0". Ein Composer-Update mit einer nicht passenden aktiven CLI-Version bricht in solchen Fällen sofort mit einer klaren Fehlermeldung ab, statt inkompatible Pakete zu installieren, was eine der zuverlässigsten Absicherungen gegen versehentliche Versionsverwechslungen im Deploy-Prozess darstellt.
9. Umschalt-Methoden im direkten Vergleich
Die fünf häufigsten Stolperfallen beim parallelen Betrieb mehrerer PHP-Versionen lassen sich klar benennen und ebenso klar durch etablierte Praktiken vermeiden. Die folgende Tabelle stellt jeweils die unsichere Standardlösung dem empfohlenen, produktionssicheren Ansatz gegenüber.
| Aufgabe | Unsicher / Fehleranfällig | Empfohlener Ansatz | Vorteil |
|---|---|---|---|
| CLI-Aufruf in Deploy-Skripten | php bin/magento ... | /usr/bin/php8.3 bin/magento ... | Unabhängig vom globalen update-alternatives-Standard |
| FPM-Zuordnung im vhost | Default-Socket aller Projekte gemeinsam | Eigener Pool und Socket pro Projekt | Keine gegenseitige Ressourcen-Verdrängung |
| Extension-Verwaltung | Nur eine Version bestückt, zweite vergessen | phpenmod -v pro Version, Diff-Check | Fehlende Module fallen vor dem Deploy auf |
| CLI-Standard wechseln | Manuelles Umbiegen von Symlinks per Hand | update-alternatives --set php ... | Konsistent, dokumentiert, skriptfähig |
| Versionskompatibilität prüfen | Version raten oder Changelog überfliegen | require.php aus composer.json auswerten | Composer bricht bei Mismatch sofort sauber ab |
In der Praxis zeigt sich, dass die meisten Vorfälle mit falscher PHP-Version aus genau diesen fünf Stellen stammen: unklarer CLI-Aufruf, geteilter FPM-Socket, vergessene Extension, manuell verdrehte Symlinks und ignorierte Composer-Constraints. Wer diese fünf Punkte in Deploy-Skripten und Server-Dokumentation konsequent explizit macht, reduziert versionsbedingte Ausfälle auf ein Minimum, auch wenn gleichzeitig fünf oder mehr Magento-Stores mit unterschiedlichem Patch-Level auf demselben Server laufen.
Mironsoft
Server-Administration, PHP-Infrastruktur und Magento-Hosting
Mehrere Magento-Stores auf einem Server im Griff behalten?
Wir richten saubere Multi-PHP-Umgebungen mit dedizierten FPM-Pools pro Projekt ein und sorgen dafür, dass jede Magento-Version genau die PHP-Version bekommt, die sie tatsächlich braucht, ohne gegenseitige Beeinflussung.
Multi-PHP-Setup
Versionierte FPM-Pakete, saubere Pool-Trennung und dokumentierte CLI-Standards
Deploy-Automatisierung
Versionsgepinnte Deploy-Skripte, die keinen globalen PHP-Standard voraussetzen
Magento-Migration
PHP-Versionswechsel bei Magento-Upgrades ohne Downtime für andere Projekte
10. Zusammenfassung
Das parallele Betreiben mehrerer PHP-Versionen unter Linux ist mit den Bordmitteln von Debian und Ubuntu ohne Container-Overhead vollständig lösbar. Versionierte Pakete aus dem Ondrej-Sury-PPA installieren jede PHP-Version als eigenständigen FPM-Daemon mit eigenem Socket. update-alternatives steuert ausschließlich den globalen CLI-Standard und hat keinen Einfluss auf laufende Webprojekte. Dedizierte FPM-Pools pro vhost sorgen für saubere Ressourcentrennung, und die direkte, versionsspezifische Binärdatei in Deploy-Skripten macht jeden Ablauf unabhängig vom gerade aktiven globalen Standard.
Der größte praktische Hebel liegt darin, jede der fünf typischen Fehlerquellen, unklarer CLI-Aufruf, geteilter FPM-Socket, vergessene Extension, manuell verdrehte Symlinks und ignorierte Composer-Constraints, aktiv im Deploy-Prozess zu adressieren statt sie dem Zufall zu überlassen. Wer diese Struktur einmal sauber aufsetzt, kann beliebig viele Magento-Stores mit unterschiedlichen PHP-Anforderungen auf demselben Server betreiben, ohne dass ein Versionswechsel für ein Projekt jemals ein anderes gefährdet.
Mehrere PHP-Versionen parallel verwalten: Das Wichtigste auf einen Blick
Pakete pro Version
php8.1-fpm und php8.3-fpm laufen als komplett eigenständige Daemons, Extensions müssen pro Version einzeln installiert werden.
update-alternatives
Steuert nur den globalen CLI-Befehl php, hat keinerlei Auswirkung auf FPM-Pools oder laufende vhosts.
Ein Pool pro vhost
Eigener Socket, eigener Systembenutzer, eigene Ressourcenlimits pro Projekt verhindern gegenseitige Beeinflussung.
Deploy-Skripte pinnen
/usr/bin/php8.3 statt php in Deploy-Skripten macht Abläufe reproduzierbar und unabhängig vom globalen Standard.