Ein Skript entsteht lokal unter Windows und arbeitet später auf einem Linux-Server, und irgendwann trifft es auf eine Stelle, an der beide Systeme sich eben doch unterscheiden. PHP php_uname beantwortet die erste Frage dazu: auf welchem System läuft dieser Code gerade? Die Funktion nennt Systemname, Rechnername, Kernel-Version, Build-Angabe und Maschinentyp, wahlweise einzeln oder in einer Zeile. Dieses Tutorial zeigt alle sechs Modi mit echter Ausgabe von zwei Systemen, die richtige Verzweigung nach Betriebssystem und die Grenze, an der diese Angaben nichts mehr zu suchen haben.
Das Bild ordnet die fünf Bestandteile der Ausgabe den beiden Plattformen zu. Im Code beginnt das mit einem einzigen Aufruf ohne Parameter.
Was PHP php_uname() zurückgibt
Jede Ausgabe in diesem Tutorial stammt aus einem echten Lauf auf zwei Installationen: PHP 8.4.8 unter Windows 11 und PHP 8.4.17 im Docker-Container php-space_web unter Linux. Im Kommentar steht jeweils, welches System die Zeile geliefert hat. Der einfachste Aufruf von PHP php_uname kommt ohne Argument aus.
<?php
echo php_uname();
/* Windows 11, PHP 8.4.8: */
/* Windows NT WIN-ENTWICKLUNG 10.0 build 26200 (Windows 11) AMD64 */
/* Linux-Container, PHP 8.4.17: */
/* Linux d88f31d6e21d 6.6.87.2-microsoft-standard-WSL2 */
/* #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025 x86_64 */
Die Signatur lautet php_uname(string $mode = "a"): string. Ohne Argument setzt PHP php_uname alle Angaben zu einer Zeile zusammen, in der Reihenfolge Systemname, Hostname, Release, Version und Maschinentyp. Eine Kleinigkeit wird dabei ständig verwechselt: beschrieben wird der Rechner, auf dem PHP arbeitet, nicht der des Besuchers. Wer wissen will, mit welchem Gerät jemand die Seite aufruft, liest den User-Agent aus.
Die Suche nach Serverangaben landet regelmäßig bei zwei benachbarten Werkzeugen, deshalb gehört die Abgrenzung gleich an den Anfang. Das Tutorial zu phpinfo() in PHP zeigt die PHP-Konfiguration mit ihren Extensions und ini-Werten, das Tutorial zu PHP phpversion() zeigt die PHP-Version, und PHP php_uname zeigt das Betriebssystem darunter. Drei Ebenen, drei Werkzeuge, und keines ersetzt eines der beiden anderen.
Die sechs Modi von PHP php_uname()
Die Funktion erwartet als einziges Argument einen einzelnen Buchstaben. Neben dem Sammelmodus a gibt es fünf Einzelabfragen, und eine Schleife zeigt sie am schnellsten alle nebeneinander.
<?php
foreach (['a', 's', 'n', 'r', 'v', 'm'] as $modus) {
echo $modus . ' => ' . php_uname($modus) . PHP_EOL;
}
/* Windows 11, PHP 8.4.8 (Modus a hier weggelassen): */
/* s => Windows NT */
/* n => WIN-ENTWICKLUNG */
/* r => 10.0 */
/* v => build 26200 (Windows 11) */
/* m => AMD64 */
/* Linux-Container, PHP 8.4.17 (Modus a weggelassen): */
/* s => Linux */
/* n => d88f31d6e21d */
/* r => 6.6.87.2-microsoft-standard-WSL2 */
/* v => #1 SMP PREEMPT_DYNAMIC Thu Jun 5 ... */
/* m => x86_64 */
Die Tabelle stellt beide Läufe gegenüber. Erst so wird sichtbar, warum sich zu einem Buchstaben keine feste Bedeutung merken lässt: r trägt unter Windows eine zweistellige Versionsnummer und unter Linux die vollständige Kernel-Version.
| Modus | Bedeutung | Windows 11, PHP 8.4.8 | Linux, PHP 8.4.17 |
a | alles in einer Zeile, Vorgabe | Windows NT WIN-ENTWICKLUNG 10.0 build 26200 (Windows 11) AMD64 | Linux d88f31d6e21d 6.6.87.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025 x86_64 |
s | Systemname | Windows NT | Linux |
n | Hostname, also der Rechnername | WIN-ENTWICKLUNG | d88f31d6e21d |
r | Release, unter Linux die Kernel-Version | 10.0 | 6.6.87.2-microsoft-standard-WSL2 |
v | Version, also die Build-Angabe | build 26200 (Windows 11) | #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025 |
m | Maschinentyp | AMD64 | x86_64 |
Ein Buchstabe, den es nicht gibt, wird von PHP php_uname in aktuellen Versionen nicht mehr stillschweigend als a behandelt. Wer sich vertippt, bekommt eine deutliche Ansage, und das ist eine Verbesserung: ältere Fassungen lieferten bei einem Tippfehler einfach die Sammelzeile, und niemand merkte etwas davon.
<?php
try {
echo php_uname('x');
} catch (ValueError $fehler) {
echo $fehler->getMessage();
}
/* Wortgleich unter Windows 11 und im Linux-Container: */
/* php_uname(): Argument #1 ($mode) must be one of */
/* "a", "m", "n", "r", "s", or "v" */
Windows und Linux nebeneinander gelesen
Der Vergleich der beiden Spalten ist mehr als eine Kuriosität, er ist der Grund für den ganzen nächsten Abschnitt. Beim Maschinentyp stehen AMD64 und x86_64 nebeneinander, obwohl beide dieselbe Hardware meinen. Die Sammelzeile aus dem Linux-Container enthält sogar ein Datum mit Uhrzeit, die aus Windows 11 dagegen nicht.
Daraus folgt die wichtigste Regel im Umgang mit PHP php_uname: der Rückgabewert ist Diagnosetext für Menschen. Er hat kein garantiertes Format, seine Bestandteile stehen nicht an festen Positionen, und ihre Zahl ist auch nicht festgelegt. Alles, was daraus per Textsuche eine Entscheidung ableiten will, steht auf Sand.
Die Plattform-Weiche gehört auf PHP_OS_FAMILY
Für eine Verzweigung nach Betriebssystem gibt es genau ein richtiges Werkzeug, und das ist nicht PHP php_uname, sondern die Konstante PHP_OS_FAMILY. Sie liefert einen von sechs Werten: Windows, BSD, Darwin, Solaris, Linux oder Unknown. Eine kleine, feste Menge also, mit der sich ein match vollständig abdecken lässt.
<?php
$zweig = match (PHP_OS_FAMILY) {
'Windows' => 'Windows-Zweig',
'Darwin' => 'macOS-Zweig',
'Linux', 'BSD' => 'Unix-Zweig',
default => 'Unbekanntes System',
};
echo $zweig;
/* Windows 11, PHP 8.4.8: Windows-Zweig */
/* Linux-Container, PHP 8.4.17: Unix-Zweig */
Der Wert Darwin verdient besondere Aufmerksamkeit, denn macOS meldet sich genau so. Eine Weiche, die nur Windows und "alles andere" kennt, schickt jeden Mac in den Linux-Zweig und setzt dort Befehle ab, die es nicht gibt. Wer plattformabhängige Aufrufe an die Systemumgebung durchreicht, findet die Werkzeuge dafür im Tutorial zum Ausführen von Kommandos auf der Kommandozeile.
In gewachsenem Code stehen daneben meist zwei ältere Muster. Beide funktionieren irgendwie, und beide sollten weichen.
<?php
/* Nicht so: der Rueckgabestring hat kein festes Format */
if (strpos(php_uname(), 'Windows') !== false) {
/* bricht, sobald sich die Zusammensetzung aendert */
}
/* Auch nicht so: Bastelwerk aus der Zeit vor PHP 7.2 */
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
/* liefert unter Windows 11 tatsaechlich true, sagt */
/* aber nichts ueber macOS, BSD oder Solaris */
}
/* Sondern so: */
if (PHP_OS_FAMILY === 'Windows') {
/* eindeutig, lesbar, seit PHP 7.2 vorhanden */
}
Der erste Block zerlegt genau den Text, den PHP php_uname als Diagnoseangabe ausgibt, und hängt damit an einer Zeichenkette ohne Zusage. Der zweite Block liest die Konstante PHP_OS, und die ist noch einmal etwas anderes: sie nennt das System, auf dem dieser PHP-Build entstanden ist, und liefert unter Windows WINNT statt Windows NT. Das gemessene Ergebnis stimmt, die Aussage bleibt aber auf Windows beschränkt. Seit PHP 7.2 gibt es dafür PHP_OS_FAMILY, und wie sich eine Mindestversion sauber absichern lässt, steht im Tutorial zu PHP phpversion() und dem Versionsvergleich.
Pfade, Zeilenenden und Pfadlisten
Sobald die Weiche steht, verschwinden die meisten Plattformunterschiede von selbst, denn PHP bringt für sie fertige Konstanten mit. Genau hier scheitern die Skripte, die lokal einwandfrei arbeiten und auf dem Server keine einzige Datei mehr finden.
<?php
$pfad = 'var' . DIRECTORY_SEPARATOR . 'log'
. DIRECTORY_SEPARATOR . 'app.log';
echo $pfad;
/* Windows 11: var\log\app.log */
/* Linux-Container: var/log/app.log */
echo PATH_SEPARATOR;
/* Windows 11: ; Linux-Container: : */
/* PHP_EOL ist unter Windows \r\n und unter Linux \n. */
/* Gemessen als Bytefolge: 0d0a gegen 0a. */
file_put_contents('bericht.txt', 'Zeile 1' . PHP_EOL, FILE_APPEND);
/* Fuer das Temporaerverzeichnis braucht es keine eigene Weiche: */
echo sys_get_temp_dir();
/* Windows 11: C:\Users\benutzer\AppData\Local\Temp */
/* Linux-Container: /tmp */
Der Backslash im Windows-Pfad ist der Klassiker unter den Umzugsschmerzen. PHP nimmt unter Windows in Dateifunktionen zwar auch den Schrägstrich entgegen, doch ausgegebene und protokollierte Pfade sollten zur Plattform passen. PATH_SEPARATOR trennt Einträge in Pfadlisten, unter Windows als Semikolon und unter Linux als Doppelpunkt; wer solche Listen aus der Umgebung liest, findet den Weg dahin im Tutorial zu PHP getenv() und Umgebungsvariablen. Und PHP_EOL entscheidet darüber, ob eine erzeugte CSV-Datei im Zielsystem sauber ankommt oder als eine einzige lange Zeile. Keine dieser vier Konstanten braucht einen Aufruf von PHP php_uname, und keine von ihnen kann gesperrt werden.
Maschinentyp und Rechenbreite sind zwei Fragen
Der Modus m von PHP php_uname nennt den Maschinentyp, also die Bezeichnung der Prozessorarchitektur. Daraus wird schnell der falsche Schluss, PHP rechne deshalb auch 64 Bit breit. Die Zeichenkette sagt darüber nichts: derselbe Prozessor heißt unter Windows AMD64 und unter Linux x86_64, und auf einem 64-Bit-System kann sehr wohl ein 32-Bit-Build von PHP laufen.
<?php
/* Der Maschinentyp ist eine Information, keine Entscheidung */
echo php_uname('m');
/* Windows 11: AMD64 Linux-Container: x86_64 */
/* Wie breit PHP selbst rechnet, sagt allein PHP_INT_SIZE */
echo PHP_INT_SIZE * 8 . ' Bit';
/* auf beiden Systemen gemessen: 64 Bit */
echo PHP_INT_MAX;
/* auf beiden Systemen: 9223372036854775807 */
/* auf einem 32-Bit-Build waeren es 2147483647 */
if (PHP_INT_SIZE === 4) {
/* 32 Bit: Zeitstempel jenseits 2038 und sehr grosse */
/* Ganzzahlen brauchen hier einen anderen Weg */
}
Die Trennung ist damit einfach zu merken: der Maschinentyp beschreibt die Hardware, PHP_INT_SIZE beschreibt den Interpreter. Wer Messwerte, Zeitstempel oder Beträge in Cent verarbeitet, interessiert sich ausschließlich für den zweiten Wert.
Der Hostname ist nicht die aufgerufene Domain
Der Modus n liefert den Rechnernamen des Servers. Im Linux-Container ist das die Container-Kennung d88f31d6e21d, auf einem Hosting-Paket oft etwas wie server9. Mit der Adresse, die der Besucher eingetippt hat, hat der Name nichts zu tun. Wer ihn in absolute Links einsetzt, erzeugt Verweise ins Leere.
<?php
/* Der Rechnername des Servers */
echo php_uname('n'); /* Linux-Container: d88f31d6e21d */
echo gethostname(); /* derselbe Wert, ohne php_uname */
/* Die aufgerufene Domain steht woanders und kommt vom Client, */
/* gehoert also gegen eine Liste erlaubter Werte geprueft */
$erlaubt = ['www.php-space.info', 'php-space.info'];
$host = $_SERVER['HTTP_HOST'] ?? '';
$domain = in_array($host, $erlaubt, true)
? $host
: 'www.php-space.info';
echo 'https://' . $domain . '/kontakt.php';
Bemerkenswert ist die zweite Zeile: gethostname() gibt denselben Rechnernamen aus, kommt aber ohne PHP php_uname aus. Das wird gleich noch wichtig. Der Wert aus $_SERVER['HTTP_HOST'] stammt dagegen vom Client, lässt sich frei setzen und darf deshalb nie ungeprüft weiterverwendet werden.
Diese Ausgabe gehört ins Protokoll, nicht ins Frontend
Jetzt zum wichtigsten Punkt des ganzen Tutorials. Die Sammelzeile von PHP php_uname nennt die genaue Kernel-Version und den Hostnamen des Servers. Wer beides auf einer öffentlich erreichbaren Seite ausgibt, ob in einer Fußzeile, einem Statusbereich oder einer Fehlermeldung, liefert einem Angreifer die halbe Vorarbeit frei Haus. Statt zu raten, welche Angriffe hier überhaupt greifen, liest er die Version ab und wählt gezielt aus, was zu dieser Kernel-Fassung passt. Dass in diesem Tutorial trotzdem vollständige Ausgaben abgedruckt sind, hat einen einfachen Grund: sie stehen hier zum Mitlesen, und der Rechnername darin ist bewusst ein generischer.
Die Regel dazu ist kurz und kennt keine Ausnahme: Diagnoseangaben gehören ins Protokoll oder hinter die Anmeldung, niemals in die Antwort an den Besucher. Dasselbe Argument gilt für die vollständige Konfigurationsseite, wie das Tutorial zu phpinfo() und dessen Absicherung ausführt. Der Unterschied zwischen richtig und falsch ist im Code oft nur eine Zeile.
<?php
try {
verarbeiteBestellung();
} catch (Throwable $fehler) {
/* Falsch: verraet Kernel-Version und Hostname an jeden Besucher */
/* echo 'Fehler auf ' . php_uname() . ': ' . $fehler->getMessage(); */
/* Richtig: Details ins Protokoll, dem Besucher nur ein Hinweis */
error_log(sprintf(
'%s | %s | %s',
php_uname(),
$fehler->getMessage(),
$fehler->getFile() . ':' . $fehler->getLine()
));
http_response_code(500);
echo 'Die Bestellung konnte nicht verarbeitet werden.';
}
Im Protokoll dagegen sind diese Angaben ausgesprochen wertvoll. Laufen nächtliche Importe auf mehreren Maschinen in dieselbe Datei, sehen alle Einträge gleich aus, und jede Fehlersuche wird zur Raterei. Eine kompakte Systemkennzeile vor jedem Laufbeginn beendet das.
<?php
function systemkennung(): string
{
if (!function_exists('php_uname')) {
/* Vom Hoster in disable_functions eingetragen */
return PHP_OS_FAMILY . ' auf ' . gethostname();
}
return sprintf(
'%s %s auf %s',
php_uname('s'),
php_uname('r'),
php_uname('m')
);
}
$zeile = sprintf(
'[%s] %s | PHP %s | %s',
date('Y-m-d H:i:s'),
'Import beendet',
PHP_VERSION,
systemkennung()
);
error_log($zeile);
/* Gemessen unter Windows 11: */
/* [2026-08-10 08:09:09] Import beendet | PHP 8.4.8 */
/* | Windows NT 10.0 auf AMD64 */
/* Gemessen im Linux-Container: */
/* [2026-08-10 10:09:05] Import beendet | PHP 8.4.17 */
/* | Linux 6.6.87.2-microsoft-standard-WSL2 auf x86_64 */
Wohin diese Zeilen geschrieben werden und wie sich das Ziel einstellen lässt, behandelt das Tutorial zu PHP error_log(). Wer einem Fehler danach im laufenden Betrieb nachgeht, findet die Werkzeuge dafür unter PHP Debugging mit Xdebug.
Wenn PHP php_uname gesperrt ist
Weil die Ausgabe so aufschlussreich ist, tragen viele Anbieter von geteiltem Webspace PHP php_uname in disable_functions ein. Das ist dort kein Sonderfall, sondern eher die Regel. Der Aufruf endet dann mit einem schweren Fehler und reißt das Skript mit, und ausgerechnet ein Diagnoseskript bricht damit vor der Diagnose ab. Die Prüfung mit function_exists('php_uname') aus dem letzten Beispiel kostet eine Zeile und hält den Rest am Leben.
Der Ersatzweg kommt dabei erstaunlich weit. PHP_OS_FAMILY und PHP_OS sind Konstanten und lassen sich gar nicht sperren, und gethostname() liefert denselben Rechnernamen wie der Modus n. Es fehlt allein die Kernel-Version, und die ist für die meisten Zwecke verzichtbar. Was sich an der Konfiguration überhaupt einstellen lässt, zeigt das Tutorial zu ini_set() und der PHP-Konfiguration.
Für eine Weiche im Code genügt eine Konstante. php_uname() kommt erst ins Spiel, wenn ein Mensch die Angabe lesen soll, und dann zählt, welcher Mensch das ist.
flowchart TD
A[Systemangabe gebraucht] --> B{Wozu?}
B -->|Weiche nach OS| C[PHP_OS_FAMILY]
B -->|Pfad, Zeilenende| D[Konstanten nutzen]
B -->|Rechenbreite| E[PHP_INT_SIZE]
B -->|Diagnose| F{php_uname erlaubt?}
F -->|Nein| G[Ersatz OS_FAMILY]
F -->|Ja| H[php_uname mit Modus]
H --> I{Wohin damit?}
I -->|Log oder Admin| J[In Ordnung]
I -->|Frontend| K[Niemals ausgeben]
Stolperfallen, die keinen Fehler melden
Der Rückgabewert von PHP php_uname ist Diagnosetext, kein Datensatz. Wer ihn wie einen Datensatz behandelt, bekommt trotzdem ein Ergebnis, nur eben ein falsches. Drei Fälle davon begegnen einem auf Shared Hosting und in Containern immer wieder.
Den Rückgabestring zerlegen Ein strpos() auf der Sammelzeile liefert heute das erwartete Ergebnis und morgen ein anderes. Schon ein Kernel-Wechsel ändert die Zeile.
Nur Windows und "alles andere" unterscheiden macOS meldet sich als Darwin und fällt in einer zweiteiligen Weiche in den Linux-Zweig. Die dort abgesetzten Befehle gibt es auf dem Mac oft gar nicht.
Aus dem Maschinentyp auf 32 oder 64 Bit schließen Der Modus m beschreibt die Hardware. Ob der Interpreter mit 8 oder mit 4 Byte breiten Ganzzahlen rechnet, beantwortet ausschließlich PHP_INT_SIZE.
Häufige Fragen zu PHP php_uname()
Diese vier Fragen tauchen in Code-Reviews und in Supportanfragen am häufigsten auf.
Wie erkenne ich, ob mein Skript unter Windows oder unter Linux arbeitet? Über PHP_OS_FAMILY, verglichen mit den festen Werten Windows und Linux. Weder Textsuche noch Großschreibung sind dabei nötig.
Was ist der Unterschied zu PHP_OS? Die Konstante beschreibt die Bauumgebung dieses PHP-Builds, die Funktion das laufende System. Praktisch gilt: PHP_OS_FAMILY für Weichen, PHP php_uname für Diagnose, PHP_OS nur noch für sehr alte Installationen.
Darf ich die Ausgabe im Adminbereich anzeigen? Hinter einer Anmeldung ja, im öffentlichen Teil der Seite nein. Was PHP php_uname ausgibt, bleibt inhaltlich dasselbe; entscheidend ist allein, wer es zu sehen bekommt.
Warum meldet mein Server "has been disabled for security reasons"? Der Hoster hat die Funktion in disable_functions gesperrt. Eine Prüfung mit function_exists() und der Ersatzweg über PHP_OS_FAMILY und gethostname() halten das Skript am Leben.
Fazit
PHP php_uname beantwortet genau eine Frage, nämlich die nach dem System, auf dem dieser Code gerade arbeitet. Die sechs Modi liefern Systemname, Hostname, Release, Version und Maschinentyp, und die Gegenüberstellung von Windows 11 und dem Linux-Container zeigt, wie weit dieselben Buchstaben auseinanderliegen. Der Rückgabewert ist damit Text für Menschen und keine Entscheidungsgrundlage für Programme.
Verzweigungen nach Betriebssystem laufen deshalb über PHP_OS_FAMILY mit seinen sechs festen Werten, Pfade über DIRECTORY_SEPARATOR, Zeilenenden über PHP_EOL, Pfadlisten über PATH_SEPARATOR und die Rechenbreite über PHP_INT_SIZE. Zwei Dinge machen am Ende den Unterschied: ein function_exists() davor, weil die Funktion auf geteiltem Webspace oft gesperrt ist, und die Ausgabe konsequent im Protokoll statt im Frontend. So eingesetzt ist PHP php_uname ein präzises Diagnosewerkzeug und keine offene Auskunftsstelle.