PHP-Uploadgröße erhöhen und ablehnendes Limit ermitteln
Um eine Datei mit 20 MiB anzunehmen, setzen Sie upload_max_filesize = 20M, geben Sie der gesamten
Multipart-Anfrage mit post_max_size = 25M mehr Spielraum und stellen Sie sicher, dass auch
Ihre Anwendung und Ihr Webserver sie zulassen. Änderungen an den PHP-Einstellungen können ein
kleineres Limit in Ihrem Upload-Handler nicht außer Kraft setzen. Diese Anleitung zeigt, wie Sie
die aktiven Einstellungen finden und jeden Fehler über HTTP mit einem kleinen lokalen Endpunkt
reproduzieren.
Maximale Dateigröße für Uploads in PHP prüfen
Prüfen Sie den PHP-Prozess, der die Upload-URL bedient. php --ini zeigt die
Konfiguration für die Kommandozeile an, nicht die von FPM oder Apache geladene Konfiguration.
PHP kann
unterschiedliche Konfigurationsdateien für verschiedene Serverschnittstellen
verwenden.
Speichern Sie dies als check_upload_size.php neben Ihrem Upload-Endpunkt und rufen Sie es
über dieselbe Website ab. Beschränken Sie den Zugriff auf einer bestehenden Website auf
Administratoren und entfernen Sie die Datei nach der Diagnose: Die Dateipfade sind für Sie
nützlich, sollten aber nicht öffentlich sein.
<?php
header('Content-Type: application/json');
echo json_encode([
'version' => PHP_VERSION,
'sapi' => PHP_SAPI,
'loaded_ini' => php_ini_loaded_file(),
'scanned_ini' => php_ini_scanned_files(),
'file_uploads' => ini_get('file_uploads'),
'upload_max_filesize' => ini_get('upload_max_filesize'),
'post_max_size' => ini_get('post_max_size'),
'max_file_uploads' => ini_get('max_file_uploads'),
], JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR);
PHP dokumentiert die Standardwerte 2M für upload_max_filesize,
8M für post_max_size und 20 für
max_file_uploads; Installationen können sie überschreiben. Die ersten beiden begrenzen
die Anzahl der Bytes, während max_file_uploads die Anzahl der Dateien begrenzt.
In der PHP-Größennotation bedeutet 20M 20 × 1.024 × 1.024 Bytes,
also 20 MiB. Siehe die Kerndirektiven und den
Größenparser.
| Ebene | Was sie begrenzt | Worauf Sie achten sollten |
|---|---|---|
| Webserver oder Proxy | Den eingehenden Anfragebody | Ablehnung vor Ausführung des PHP-Handlers; prüfen Sie die Logs dieses Servers |
post_max_size | Den gesamten POST-Body einschließlich Feldern und Multipart-Overhead | Sowohl $_POST als auch $_FILES können leer sein |
upload_max_filesize | Jede hochgeladene Datei | UPLOAD_ERR_INI_SIZE im Feld error der Datei |
| Anwendungsvalidierung | Die Datei, die Ihre Anwendung annimmt | Eine Handler-Konstante oder Framework-Regel, deren Limit kleiner sein kann als das von PHP |
PHP dokumentiert das Verhalten leerer Superglobals
und die Upload-Fehlercodes.
Ein leeres $_FILES allein beweist keine zu große Anfrage:
Auch ein fehlendes Dateifeld kann die Ursache sein.
Dateigröße für Uploads in PHP erhöhen
Bearbeiten Sie die Konfiguration, die für die Webanfrage verwendet wird. Den Speicherort finden
Sie in der Diagnoseausgabe oben. Unter Ubuntu verwendet eine PHP-8.3-Installation aus Paketen
üblicherweise /etc/php/8.3/fpm/php.ini für FPM oder /etc/php/8.3/apache2/php.ini für mod_php.
Maßgeblich sind der gemeldete Pfad und die tatsächlich wirksamen Werte, nicht diese Beispiele.
file_uploads = On
upload_max_filesize = 20M
post_max_size = 25M
display_errors = Off
log_errors = On
Laden Sie nach einer Konfigurationsänderung den Dienst neu, der PHP ausführt. Bei FPM ist das der
zuständige FPM-Dienst, bei mod_php Apache. Rufen Sie die Diagnose-URL erneut ab und prüfen Sie die
wirksamen Werte. Diese beiden Größendirektiven sind Einstellungen vom Typ
INI_PERDIR; daher kann ini_set() innerhalb von
upload.php sie nicht erhöhen. Wenn Sie die Fehleranzeige in der Konfiguration
deaktivieren, gelangen auch Warnungen beim Parsen der Anfrage nicht in die JSON-Antwort.
PHP-FPM-Konfiguration verwenden
Ein Pool kann php.ini überschreiben. Prüfen Sie den Pool für diese Website,
wenn die Werte weiterhin abweichen. Für ein Limit von 20 MiB lauten die entsprechenden
Pool-Einstellungen php_admin_value[upload_max_filesize] = 20M und php_admin_value[post_max_size] = 25M.
Die FPM-Konfigurationsreferenz
erklärt diese Überschreibungen durch Pools. Laden Sie den betroffenen FPM-Dienst neu und prüfen
Sie die Werte erneut über HTTP.
.htaccess verwenden (für Apache 2.4+ mit mod_php)
Wenn PHP als Apache-Modul läuft, können php_value upload_max_filesize 20M und
php_value post_max_size 25M in .htaccess stehen, sofern der Server diese
Überschreibungen erlaubt. Es sind keine PHP-FPM-Einstellungen; fügen Sie sie nicht allein deshalb
hinzu, weil Apache der Website vorgeschaltet ist.
Siehe die Anleitung zur Apache-Konfiguration von PHP.
Wenn die Anfrage PHP nie erreicht, prüfen Sie das vorgelagerte Limit für den Anfragebody.
Die Nginx-Direktive client_max_body_size
hat den Standardwert 1m und gibt bei einer zu großen Anfrage 413 zurück.
In diesem Beispiel erlaubt client_max_body_size 25m; die vorgesehene Multipart-Anfrage.
Apache bietet LimitRequestBody, und ein
Hosting-Proxy kann ein weiteres Limit setzen. Planen Sie auf jeder Ebene Platz für die gesamte
Anfrage ein. Ein höheres PHP-Limit ändert keine dieser Einstellungen.
Datei-Uploads in PHP implementieren
Verwenden Sie eine lokale Linux-Umgebung mit 64-Bit-PHP ab Version 8.3, der Fileinfo-Erweiterung und cURL. Das Beispiel wurde mit PHP 8.3.30 und 8.5.10 getestet. Der integrierte Server von PHP ist für die lokale Entwicklung gedacht. Er bildet keine Bereitstellung mit FPM, Apache oder Proxy nach.
Erstellen Sie ein neues Verzeichnis. Die verketteten Befehle stoppen, wenn das Verzeichnis bereits existiert oder der Verzeichniswechsel fehlschlägt. Wählen Sie einen anderen Namen, statt ein bestehendes Projekt zu überschreiben.
mkdir php-upload-demo &&
cd php-upload-demo &&
mkdir public private &&
chmod 700 private
Fahren Sie erst fort, wenn dies erfolgreich war. Speichern Sie den INI-Block oben als
php-upload.ini in diesem Verzeichnis.
Speichern Sie das Diagnoseskript als public/check_upload_size.php.
PHP-Uploadskript
Speichern Sie diesen vollständigen Handler als public/upload.php.
APP_MAX_BYTES ist das unabhängige Limit der Anwendung; hier entspricht es dem
PHP-Limit von 20 MiB pro Datei. Der Handler nimmt JPEG-, PNG- und PDF-Dateien an, gleicht den
erkannten MIME-Typ mit der Dateiendung ab und speichert die angenommenen Bytes unter
private/, außerhalb des Dokumentstammverzeichnisses.
<?php
declare(strict_types=1);
const APP_MAX_BYTES = 20 * 1024 * 1024;
header('Content-Type: application/json');
function rejectUpload(int $status, string $code, string $message): never {
http_response_code($status);
echo json_encode([
'success' => false,
'code' => $code,
'message' => $message,
], JSON_THROW_ON_ERROR);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Allow: POST');
rejectUpload(405, 'method', 'Send a multipart POST request.');
}
$postLimit = ini_parse_quantity(ini_get('post_max_size'));
$contentLength = $_SERVER['CONTENT_LENGTH'] ?? null;
if ($postLimit > 0 && $contentLength !== null && (int) $contentLength > $postLimit) {
rejectUpload(413, 'post_max_size', 'The complete request exceeds PHP post_max_size.');
}
$file = $_FILES['file'] ?? null;
if (!is_array($file) || array_keys($_FILES) !== ['file'] ||
!isset($file['error'], $file['size'], $file['name'], $file['tmp_name']) ||
!is_int($file['error']) || !is_int($file['size']) ||
!is_string($file['name']) || !is_string($file['tmp_name'])) {
rejectUpload(400, 'invalid_upload', 'Send one file in the file field, without array brackets.');
}
if ($file['error'] !== UPLOAD_ERR_OK) {
[$status, $code, $message] = match ($file['error']) {
UPLOAD_ERR_INI_SIZE => [413, 'upload_max_filesize', 'The file exceeds PHP upload_max_filesize.'],
UPLOAD_ERR_FORM_SIZE => [413, 'form_limit', 'The file exceeds the submitted MAX_FILE_SIZE.'],
UPLOAD_ERR_PARTIAL => [400, 'partial_upload', 'The file arrived incomplete. Retry the upload.'],
UPLOAD_ERR_NO_FILE => [400, 'missing_file', 'Choose a file to upload.'],
default => [500, 'upload_unavailable', 'The upload service is unavailable.'],
};
rejectUpload($status, $code, $message);
}
if (!is_uploaded_file($file['tmp_name'])) {
rejectUpload(400, 'invalid_upload', 'The file is not a valid HTTP upload.');
}
$directory = null;
$destination = null;
try {
$size = filesize($file['tmp_name']);
if ($size === false) {
throw new RuntimeException('Cannot measure upload');
}
if ($size === 0) {
rejectUpload(400, 'empty_file', 'The file is empty.');
}
if ($size > APP_MAX_BYTES) {
rejectUpload(413, 'application_limit', 'The file exceeds the application size limit.');
}
$allowed = [
'image/jpeg' => ['jpg', 'jpeg'],
'image/png' => ['png'],
'application/pdf' => ['pdf'],
];
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($file['tmp_name']);
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!isset($allowed[$mime]) || !in_array($extension, $allowed[$mime], true)) {
rejectUpload(415, 'file_type', 'Send a JPEG, PNG, or PDF with a matching extension.');
}
$hash = hash_file('sha256', $file['tmp_name']);
if ($hash === false) {
throw new RuntimeException('Cannot hash upload');
}
$id = bin2hex(random_bytes(16));
$candidate = dirname(__DIR__) . '/private/' . $id;
// Creating a new directory reserves this ID without overwriting an earlier upload.
if (!@mkdir($candidate, 0700)) {
throw new RuntimeException('Cannot reserve storage');
}
$directory = $candidate;
$destination = $directory . '/file.' . $allowed[$mime][0];
if (!@move_uploaded_file($file['tmp_name'], $destination) || !@chmod($destination, 0600)) {
throw new RuntimeException('Cannot store upload');
}
http_response_code(201);
echo json_encode([
'success' => true,
'id' => $id,
'type' => $mime,
'size' => $size,
'sha256' => $hash,
], JSON_THROW_ON_ERROR);
} catch (Throwable $error) {
if ($destination !== null) {
@unlink($destination);
}
if ($directory !== null) {
@rmdir($directory);
}
error_log('Upload failed: ' . get_class($error));
rejectUpload(500, 'upload_unavailable', 'The upload service is unavailable.');
}
Die Prüfung der Anfragegröße verwendet Content-Length, um einen nachweislich zu
großen Body von einer fehlenden Datei zu unterscheiden. Die folgenden cURL-Anfragen liefern diese
Längenangabe. Ohne sie kann der Handler eine Überschreitung von post_max_size
nicht auf diese Weise diagnostizieren. Erzwingen Sie für Clients mit gestreamten Anfragen ein
Body-Limit im Webserver. ini_parse_quantity() verarbeitet die Größensuffixe von PHP,
einschließlich eines Limits von null, das die Größenbegrenzung für Multipart-POST-Anfragen aufhebt.
Starten Sie den Server aus php-upload-demo. Wählen Sie einen verfügbaren Port und
verwenden Sie ihn auch in den folgenden Anfragen. Dieser Befehl liefert nur
public/ aus:
php -c php-upload.ini -S 127.0.0.1:8080 -t public
Lassen Sie dieses Terminal weiterlaufen. Öffnen Sie in einem zweiten Terminal dasselbe Projektverzeichnis und prüfen Sie:
curl -fsS http://127.0.0.1:8080/check_upload_size.php
Erwartet werden für sapi der Wert cli-server, für
upload_max_filesize der Wert 20M und für
post_max_size der Wert 25M.
Der Pfad zur geladenen INI-Datei sollte auf dieses Projekt verweisen. Stoppen Sie den Server nach
Abschluss mit Strg+C.
Echte Multipart-Anfragen senden
Speichern Sie dies für einen reproduzierbaren Größentest als make-probe.php im
Projektstammverzeichnis. Das Skript erstellt eine gültige PNG-Datei mit einem Pixel und füllt sie
bis zur gewünschten Byte-Größe auf. Das Auffüllen ist beabsichtigt: So lassen sich Byte-Limits
ohne großes Foto testen. Zugleich zeigt es, warum eine MIME-Erkennung kein Sicherheitsurteil ist.
Der exklusive Erstellungsmodus verhindert das Überschreiben einer vorhandenen Testdatei.
<?php
declare(strict_types=1);
$bytes = filter_var($argv[2] ?? '', FILTER_VALIDATE_INT);
if ($argc !== 3 || $bytes === false || $bytes < 1024 || $bytes > 30 * 1024 * 1024) {
fwrite(STDERR, "Usage: php make-probe.php OUTPUT BYTES (1024 to 31457280)\n");
exit(1);
}
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg==', true);
$output = @fopen($argv[1], 'xb');
if ($output === false) {
fwrite(STDERR, "Cannot create probe; choose a new output filename.\n");
exit(1);
}
if (fwrite($output, $png) !== strlen($png) || !ftruncate($output, $bytes) || !fclose($output)) {
fwrite(STDERR, "Cannot finish probe.\n");
exit(1);
}
Erstellen und senden Sie eine Datei mit 6 MiB. -F liefert die
Multipart-Codierung und den Grenzmarker; setzen Sie den Header Content-Type
nicht selbst. -i zeigt den Antwortstatus. Diese Diagnoseaufrufe
verzichten auf die cURL-Option --fail, damit Sie den JSON-Body erwarteter
Fehlerantworten lesen können.
php make-probe.php probe-6.png 6291456 &&
curl -sS -i -F 'file=@probe-6.png' http://127.0.0.1:8080/upload.php
Erwartet wird HTTP 201 mit success: true, type: "image/png",
size: 6291456, einem zufälligen Wert für id und einem
Wert für sha256. Die Bytes bleiben in private/<id>/file.png;
es gibt keine öffentliche Download-URL. Vergleichen Sie den Hash aus der Antwort mit einem lokal
berechneten Hash der Quelldatei:
php -r 'echo hash_file("sha256", "probe-6.png"), PHP_EOL;'
Wiederholen Sie den Test exakt an der Grenze von 20 MiB und dann mit Dateien, die groß genug sind, um jedes PHP-Limit auszulösen:
php make-probe.php probe-20.png 20971520 &&
curl -sS -i -F 'file=@probe-20.png' http://127.0.0.1:8080/upload.php
php make-probe.php probe-21.png 22020096 &&
curl -sS -i -F 'file=@probe-21.png' http://127.0.0.1:8080/upload.php
php make-probe.php probe-26.png 27262976 &&
curl -sS -i -F 'file=@probe-26.png' http://127.0.0.1:8080/upload.php
| Datei | Erwartetes Ergebnis mit den dokumentierten Einstellungen |
|---|---|
| PNG mit 6 MiB | 201; mit übereinstimmenden Bytes und übereinstimmendem Hash gespeichert |
| PNG mit 20 MiB | 201; die Obergrenze pro Datei ist eingeschlossen |
| PNG mit 21 MiB | 413 mit code: "upload_max_filesize" |
| PNG mit 26 MiB | 413 mit code: "post_max_size"; die gesamte Anfrage ist zu groß |
Um das Anwendungslimit isoliert zu testen, stoppen Sie den Server und starten Sie ihn mit dieser vorübergehenden Überschreibung neu:
php -c php-upload.ini -d upload_max_filesize=24M -S 127.0.0.1:8080 -t public
Senden Sie die vorhandene Testdatei mit 21 MiB erneut:
curl -sS -i -F 'file=@probe-21.png' http://127.0.0.1:8080/upload.php
Jetzt wird 413 mit code: "application_limit" erwartet: PHP lässt die Datei zu, aber
APP_MAX_BYTES lehnt sie ab. Verwenden Sie danach wieder den ursprünglichen
Serverbefehl. Wenn eine bestehende Anwendung nach dem Erhöhen der PHP-Limits weiterhin eine Datei
mit 6 MiB ablehnt, suchen Sie nach einem kleineren Anwendungslimit wie
5242880 Bytes oder einer Validierungsregel des Frameworks.
Eine Änderung von php.ini aktualisiert diese Regel nicht.
Die Testbefehle ersetzen niemals eine vorhandene Testdatei. Um sie erneut zu senden, führen Sie nur ihren cURL-Befehl aus. Jede erfolgreiche Anfrage erstellt einen neuen privaten Upload, selbst bei identischem Inhalt; bei Fehlern bleibt kein Upload gespeichert. Die Demo bewahrt erfolgreiche Uploads auf, bis Sie ihre privaten Daten entfernen.
Bewährte Verfahren für sichere Datei-Uploads
Verzeichnissicherheit
Halten Sie private/ außerhalb des ausgelieferten Verzeichnisses und stellen
Sie sicher, dass es dem PHP-Konto gehört. Jedes reservierte Upload-Verzeichnis hat den Modus
0700; gespeicherte Dateien haben den Modus 0600.
Der ursprüngliche Dateiname wird nie zum Speicherpfad. Die PHP-Funktion
move_uploaded_file()
prüft, ob die Quelle über PHP hochgeladen wurde, kann aber ein vorhandenes Ziel überschreiben.
In diesem Beispiel verhindert die Reservierung eines neuen zufälligen Verzeichnisses vor dem
Verschieben der Datei dieses Überschreiben.
Bevor Sie einen Upload-Endpunkt öffentlich zugänglich machen
Dieses lokale Beispiel bietet keine Authentifizierung, Benutzerkontingente, Malware-Prüfung oder öffentliche Dateiauslieferung. Fileinfo ermittelt anhand der Bytes einen wahrscheinlichen Typ; weder dieses Ergebnis noch ein SHA-256-Hash belegt, dass eine Datei harmlos ist. Legen Sie für eine bereitgestellte Anwendung fest, wer hochladen darf, begrenzen Sie den Speicherverbrauch dieser Personen und validieren oder scannen Sie Inhalte vor ihrer Freigabe. Für Frameworks und Anzeigen des Upload-Fortschritts gelten weiterhin dieselben Anfrage- und Dateilimits; sie heben diese nicht auf.
