PHP upload_max_filesize and post_max_size Explained

By Niks · Published · Updated

Tested on

  • PHP 8.2.12 (cli, ZTS, Visual C++ 2019 x64) with the built-in web server, run as php -n so the compiled defaults apply
  • curl 8.21.0, GNU coreutils 8.32 (Git Bash), Windows 11 (10.0.22631)
  • Run on 29 September 2026. PHP-FPM and Apache were not tested.

PHP upload_max_filesize is the largest a single uploaded file may be, and post_max_size is the largest the whole request body may be. The compiled defaults are 2M and 8M. A file must fit under the first, and the entire multipart request, with every file and form field, must fit under the second. When you raise one, raise the other with it, keeping post_max_size larger than upload_max_filesize.

The PHP manual says the same: upload_max_filesize is “the maximum size of an uploaded file” and “post_max_size must be larger than this value”. It adds that memory_limit should generally be larger than post_max_size.

PHP upload_max_filesize and post_max_size defaults

The manual’s core directives table lists these defaults, and we confirmed them against PHP 8.2.12 with no php.ini loaded (php -n):

$ php -n -r 'echo ini_get("upload_max_filesize"), "|", ini_get("post_max_size"), "|", ini_get("max_file_uploads"), "|", ini_get("memory_limit");'
2M|8M|20|128M
DirectiveDefaultWhat it limits
upload_max_filesize2MSize of each uploaded file
post_max_size8MSize of the whole request body
max_file_uploads20Number of files in one request
memory_limit128M hereMemory a script may use; keep it above post_max_size

The shorthand is binary. The manual states that 1M equals 1048576 bytes and 1K equals 1024 bytes, and that PHP’s notation differs from the IEC one (see KB vs KiB). We saw this in practice below: a file of exactly 2,097,152 bytes passes a 2M limit.

Your own machine may not show these values. Our XAMPP php.ini sets both to 40M, and php --ini shows which file is loaded:

Loaded Configuration File:         D:\xampp\php\php.ini

What happens when a file is over upload_max_filesize

We ran the built-in server with defaults and a script that prints $_FILES and the request size:

<?php
header('Content-Type: text/plain');
echo 'upload_max_filesize=', ini_get('upload_max_filesize'), ' post_max_size=', ini_get('post_max_size'), "\n";
echo 'CONTENT_LENGTH=', $_SERVER['CONTENT_LENGTH'] ?? 'none', "\n";
echo 'count($_FILES)=', count($_FILES), ' count($_POST)=', count($_POST), "\n";
if (isset($_FILES['file'])) {
    echo 'error=', $_FILES['file']['error'], ' size=', $_FILES['file']['size'], "\n";
}

Then two uploads with curl -F "file=@FILE" -F "note=hello", files of exactly 2 MiB and 2 MiB plus one byte:

== file 2097152 bytes
upload_max_filesize=2M post_max_size=8M
CONTENT_LENGTH=2097473
count($_FILES)=1 count($_POST)=1
error=0 size=2097152

== file 2097153 bytes
upload_max_filesize=2M post_max_size=8M
CONTENT_LENGTH=2097474
count($_FILES)=1 count($_POST)=1
error=1 size=0

Two facts stand out. The limit applies to the file’s own bytes: the request was 321 bytes larger than the 2 MiB file and still passed. And the failure is quiet in the way PHP designs it: the script still runs, $_POST still has the other fields, and the only sign is error=1 on the file. That value is UPLOAD_ERR_INI_SIZE, which the manual describes as “The uploaded file exceeds the upload_max_filesize directive in php.ini”. Always check $_FILES['file']['error'] === UPLOAD_ERR_OK before you use the file.

What happens when the request is over post_max_size

We raised upload_max_filesize to 20M (with php -n -d upload_max_filesize=20M -S ...), keeping post_max_size at 8M, and uploaded files close to 8 MiB:

== file 8388287 bytes
CONTENT_LENGTH=8388608
count($_FILES)=1 count($_POST)=1
error=0 size=8388287

== file 8388288 bytes
Warning:  POST Content-Length of 8388609 bytes exceeds the limit of 8388608 bytes in Unknown on line 0
CONTENT_LENGTH=8388609
count($_FILES)=0 count($_POST)=0

== file 8388608 bytes
Warning:  POST Content-Length of 8388929 bytes exceeds the limit of 8388608 bytes in Unknown on line 0
CONTENT_LENGTH=8388929
count($_FILES)=0 count($_POST)=0

This failure is louder and stranger. The limit is on the request: a Content-Length of exactly 8,388,608 bytes passed and 8,388,609 failed. Our request wrapped an 8,388,287-byte file in 321 bytes of multipart data, so a file of exactly 8 MiB fails even though upload_max_filesize is 20M. When it does fail, both $_FILES and $_POST are empty, and the HTTP status was still a normal 200 from the script. The manual documents this: if the size of post data is greater than post_max_size, the $_POST and $_FILES superglobals are empty.

We ran with no php.ini, which is why the warning appeared in the response. On a configured server, read your PHP error log instead. Since the script gets no input, detect the case in code: if $_SERVER['REQUEST_METHOD'] === 'POST', $_POST and $_FILES are empty and $_SERVER['CONTENT_LENGTH'] is greater than ini_get('post_max_size') (converted to bytes), tell the user the request was too big. The manual suggests another route: add a query parameter to the form’s action and check whether it arrived.

How to set them

Both directives are INI_PERDIR in the manual, which means they can be set in php.ini (and, depending on the SAPI, in per-directory files such as .user.ini) but not with ini_set() at runtime. The manual’s changing-configuration page states that an INI_PERDIR directive may not be set using ini_set().

; php.ini or .user.ini
upload_max_filesize = 20M
post_max_size = 24M
memory_limit = 256M

Keep post_max_size above upload_max_filesize by enough to cover several files, form fields and multipart overhead. Two more things from the manual:

  • .user.ini files are read by the CGI/FastCGI SAPI, and user_ini.cache_ttl controls how often they are re-read. The default is 300 seconds, so a change can take up to five minutes to show up.
  • post_max_size = 0 allows unlimited post size, with a caveat. The changelog says that since PHP 5.3.4 it does not disable the limit when the content type is application/x-www-form-urlencoded or is not registered with PHP. Prefer a real number.

To check the value your web server uses, do not rely on php -i on the command line: it shows what the CLI uses, and a web server may load a different configuration. Print the values from a page served through the web server (phpinfo() or ini_get() in a script). We did not test PHP-FPM or Apache, so this guide’s test setup is the built-in server only.

Test the limits with byte-exact files

Use the same three-file method on both limits (the general method):

  1. Per file: upload files of upload_max_filesize - 1, exactly upload_max_filesize and upload_max_filesize + 1 bytes. With 2M these are 2,097,151, 2,097,152 and 2,097,153. Expect error=0, error=0, error=1.
  2. Per request: raise upload_max_filesize above post_max_size, then upload files that bring the request to post_max_size - 1, exactly that and one over. Expect $_FILES to become empty at the first request over.
  3. Number of files: send more than max_file_uploads (20 by default) files.

Also test through the layer in front of PHP. A proxy limit rejects the request first: see fixing nginx 413.

Try it

Generate a spreadsheet at the exact size you need with the Dummy Excel file page, or a PDF with the Dummy PDF file page: for the 2M default, set the unit to B and enter 2097151, 2097152 and 2097153. The Dummy 5 MB PDF file page is a ready file for a mid-size test. Everything is built in your browser and nothing is uploaded.

Sources

Accessed 30 September 2026.

These pages build the file in your browser at the exact byte size you choose. Nothing is uploaded.

Related guides

All guides

Send feedback

Request a feature or tell us what is not working.

What is it about?

We store your message to read it and, if you add an email, to reply. Privacy policy