# Wie PHP Datei-Uploads verarbeitet

> Wie PHP Datei-Uploads intern verarbeitet: multipart/form-data, temporäre Dateien in upload_tmp_dir, das $_FILES-Array, Grenzwerte und Fehlercodes erklärt.

Quelle: https://phpsos.de/php/datei-uploads-intern/  
Stand: 25.10.2026  
Anbieter: PHP SOS, PHP-Notfallhilfe und Server-Sicherheit. Soforthilfe per WhatsApp: https://wa.me/491736494914?text=Hallo%20PHP%20SOS%2C%20wir%20brauchen%20Hilfe%20mit%20unserem%20PHP-Server.%20Firma%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kurz gesagt:** Wenn ein Browser eine Datei hochlädt, schickt er eine POST-Anfrage im Format `multipart/form-data`, in der Formularfelder und Dateiinhalte als einzelne Teile hintereinander stehen. PHP zerlegt diese Anfrage selbst, schreibt jeden Dateiteil in eine temporäre Datei im `upload_tmp_dir`, trägt die Angaben in `$_FILES` ein und startet erst dann Ihr Skript. Das Skript verschiebt die Datei mit `move_uploaded_file()`; alles, was nicht verschoben wird, löscht PHP am Ende der Anfrage.

## Der Weg einer Datei vom Browser bis ins Skript

Für Entscheider ist vor allem eines wichtig: Ein Upload ist kein Vorgang der Anwendung allein. Ein großer Teil der Arbeit passiert im PHP-Interpreter, bevor der Code von CMS oder Shop überhaupt beteiligt ist. Deshalb betreffen Fehler in diesem Teil von PHP jede Website, ob sie ein Upload-Formular hat oder nicht.

Der Ablauf im Überblick:

1. Das Formular mit `method="POST"` und `enctype="multipart/form-data"` wird abgeschickt.
2. Der Webserver nimmt die Anfrage an und reicht sie an PHP weiter, je nach Betriebsart über PHP-FPM, mod_php oder eine andere Schnittstelle.
3. PHP prüft Grenzwerte wie `post_max_size` und zerlegt den Inhalt anhand einer Trennzeichenfolge, der sogenannten Boundary.
4. Normale Felder landen in `$_POST`, Dateiteile werden in temporäre Dateien geschrieben und in `$_FILES` beschrieben.
5. Erst jetzt startet das Skript, prüft die Datei und verschiebt sie an ihren Zielort.
6. Am Ende der Anfrage löscht PHP alle temporären Dateien, die nicht verschoben wurden.

## multipart/form-data: der Aufbau einer Upload-Anfrage

Eine Upload-Anfrage ist reiner Text mit eingebetteten Dateidaten. Der Header `Content-Type` nennt die Boundary, und im Inhalt trennt diese Zeichenfolge die einzelnen Teile. Jeder Teil hat eigene Kopfzeilen mit Feldname und gegebenenfalls Dateiname. Ein stark gekürztes, harmloses Beispiel:

```http
POST /bewerbung HTTP/1.1
Content-Type: multipart/form-data; boundary=----trenner42

------trenner42
Content-Disposition: form-data; name="vorname"

Erika
------trenner42
Content-Disposition: form-data; name="lebenslauf"; filename="lebenslauf.pdf"
Content-Type: application/pdf

%PDF-1.7 ... Dateiinhalt ...
------trenner42--
```

Alle Angaben in diesem Text stammen vom Absender, auch Dateiname und `Content-Type` des Teils. PHP übernimmt sie in `$_FILES`, prüft sie aber nicht auf Plausibilität.

## Das $_FILES-Array

Für ein Feld mit dem Namen `lebenslauf` enthält `$_FILES['lebenslauf']` laut PHP-Handbuch diese Schlüssel:

| Schlüssel | Inhalt | Vertrauenswürdig? |
|---|---|---|
| `name` | ursprünglicher Dateiname beim Absender | nein |
| `full_path` | vom Browser gemeldeter Pfad, seit PHP 8.1.0 | nein |
| `type` | MIME-Typ laut Browser, von PHP nicht geprüft | nein |
| `size` | Größe in Bytes | von PHP ermittelt |
| `tmp_name` | Pfad der temporären Datei auf dem Server | von PHP vergeben |
| `error` | Fehlercode der Upload-Verarbeitung | von PHP gesetzt |

Bei Feldern mit eckigen Klammern im Namen, etwa `bilder[]`, enthält jeder Schlüssel ein Array mit einem Eintrag je Datei. Die Anwendung durchläuft dann zum Beispiel `$_FILES['bilder']['error']` und greift über denselben Index auf `tmp_name` und `name` zu.

Ein Formular kann zusätzlich ein verstecktes Feld `MAX_FILE_SIZE` vor dem Dateifeld senden. Laut Handbuch ist das nur ein Hinweis an den Browser, der sich leicht umgehen lässt. Verlassen können Sie sich ausschließlich auf die Grenzen, die der Server durchsetzt: in PHP, im Webserver und in der Anwendung selbst.

## Grenzwerte: die php.ini-Direktiven für Uploads

Diese Direktiven steuern die Upload-Verarbeitung. Die Standardwerte stammen aus dem PHP-Handbuch und gelten, wenn keine php.ini sie überschreibt.

| Direktive | Standard | Änderbar in |
|---|---|---|
| `file_uploads` | `1` | nur Systemkonfiguration |
| `upload_tmp_dir` | leer (System-Temp) | nur Systemkonfiguration |
| `upload_max_filesize` | `2M` | php.ini, Pool, Verzeichniskonfiguration |
| `post_max_size` | `8M` | php.ini, Pool, Verzeichniskonfiguration |
| `max_file_uploads` | `20` | php.ini, Pool, Verzeichniskonfiguration |
| `enable_post_data_reading` | `1` | php.ini, Pool, Verzeichniskonfiguration |

`post_max_size` muss größer sein als `upload_max_filesize`, und `memory_limit` sollte laut Handbuch in der Regel größer sein als `post_max_size`. Ist `upload_tmp_dir` nicht beschreibbar, weicht PHP auf das Temp-Verzeichnis des Systems aus. Mit `open_basedir` muss das Temp-Verzeichnis erlaubt sein, sonst schlagen Uploads fehl. Praxiswerte und deren Folgen beschreibt der Beitrag [post_max_size und upload_max_filesize sinnvoll begrenzen](https://phpsos.de/blog/post-max-size-upload-limits/).

## Fehlercodes richtig auswerten

Das Feld `error` sagt, was bei der Verarbeitung passiert ist. Die Konstanten reichen von `UPLOAD_ERR_OK` (0) über `UPLOAD_ERR_INI_SIZE` (1), `UPLOAD_ERR_FORM_SIZE` (2), `UPLOAD_ERR_PARTIAL` (3) und `UPLOAD_ERR_NO_FILE` (4) bis `UPLOAD_ERR_NO_TMP_DIR` (6), `UPLOAD_ERR_CANT_WRITE` (7) und `UPLOAD_ERR_EXTENSION` (8).

```php
<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');
}

$meldung = match ($datei['error']) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Datei zu groß.',
    UPLOAD_ERR_NO_FILE => 'Keine Datei gewählt.',
    default => 'Upload fehlgeschlagen.',
};

if ($meldung === null && is_uploaded_file($datei['tmp_name'])) {
    $ziel = '/srv/app/uploads/' . bin2hex(random_bytes(16)) . '.pdf';
    move_uploaded_file($datei['tmp_name'], $ziel);
}
```

Das Beispiel zeigt nur die Mechanik. Prüfung des Dateityps, Ablage außerhalb des Webroots und das Sperren der Skriptausführung beschreibt der Ratgeber [Datei-Uploads in PHP sicher umsetzen](https://phpsos.de/ratgeber/datei-upload-sicher/).

## Uploads und PHP2Shell: Was die Übergangsmaßnahmen bedeuten

Die PHP2Shell-Lücken CVE-2026-109618 und CVE-2026-109619 betreffen die Verarbeitung rund um die Anfrage im PHP-Interpreter. Deshalb nennt php.net als Übergangsmaßnahmen unter anderem `post_max_size = 8M` (ohne Uploads) oder `32M` (mit Uploads), `upload_max_filesize` darunter, sowie `session.upload_progress.enabled=0`. Diese Werte gehören in php.ini, FPM-Pool oder Serverkonfiguration, danach müssen alle Worker neu starten.

Fachautoren schlagen zusätzlich vor, `multipart/form-data`-Anfragen außerhalb echter Upload-Pfade am Proxy oder in der WAF zu blockieren. Das ist eine inoffizielle Maßnahme mit dem Risiko, dass Formulare brechen; die Abwägung beschreibt der Beitrag [Upload-Anfragen am Rand filtern](https://phpsos.de/blog/upload-anfragen-am-rand-filtern/). Keine dieser Maßnahmen ersetzt das Update auf PHP 8.5.12, 8.4.27, 8.3.36 oder 8.2.35.

## Wie hilft PHP SOS konkret?

- Wir ermitteln, welche Ihrer Websites tatsächlich Uploads brauchen und auf welchen Pfaden.
- Wir setzen die Grenzwerte und Übergangsmaßnahmen je Pool, starten alle Worker neu und testen die Formulare.
- Wir prüfen Upload-Verzeichnisse auf ausführbare Dateien und sperren die Skriptausführung im Rahmen der [Server-Härtung](https://phpsos.de/leistungen/server-haertung/).
- Wir spielen das PHP-Update ein und dokumentieren den Patchstand je Website.


## Häufige Fragen

**Wo speichert PHP hochgeladene Dateien zuerst?**  
In einem temporären Verzeichnis. Standardmäßig ist das das temporäre Verzeichnis des Systems; mit upload_tmp_dir lässt es sich ändern. Wird die Datei im Skript nicht verschoben, löscht PHP sie am Ende der Anfrage.

**Kann ich dem Feld type in $_FILES vertrauen?**  
Nein. Den MIME-Typ meldet der Browser, PHP prüft ihn laut Handbuch nicht. Auch name und full_path stammen vom Absender. Den tatsächlichen Dateityp ermitteln Sie serverseitig anhand des Inhalts.

**Warum ist $_FILES leer, obwohl eine Datei gesendet wurde?**  
Meist war die gesamte Anfrage größer als post_max_size. Dann leert PHP $_POST und $_FILES vollständig, ohne Fehlercode im Array. Weitere Ursachen sind ein fehlendes enctype im Formular oder abgeschaltete file_uploads.

**Hilft es gegen PHP2Shell, Uploads in der Anwendung zu verbieten?**  
Nein. Die Verarbeitung der Anfragedaten passiert in PHP, bevor Anwendungscode läuft. Wirksam sind das PHP-Update und der Neustart aller Worker; engere Grenzwerte sind nur eine Übergangsmaßnahme.

**Was ist der Unterschied zwischen upload_max_filesize und post_max_size?**  
upload_max_filesize begrenzt jede einzelne Datei, post_max_size den gesamten Inhalt der Anfrage einschließlich aller Dateien und Felder. post_max_size muss deshalb größer sein als upload_max_filesize.

## Quellen

- [PHP-Handbuch: Uploads mit POST](https://www.php.net/manual/de/features.file-upload.post-method.php)
- [PHP-Handbuch: Fehlermeldungen bei Uploads](https://www.php.net/manual/de/features.file-upload.errors.php)
- [PHP-Handbuch: php.ini-Direktiven](https://www.php.net/manual/de/ini.core.php)
- [php.net: Sicherheitsmeldungen](https://www.php.net/)
