A PHP job form with upload_max_filesize = 10M accepts a 5 MB CV, but a 9 MB CV arrives with $_POST and $_FILES both empty; why?
answer
- two limits: per file and per body
- post_max_size defaults to 8M
- "POST Content-Length ... exceeds the limit"
- whole body discarded, no error code
- compare CONTENT_LENGTH to detect it
basics
~20 spost_max_size (default 8M) caps the whole request body, and a 9 MB CV plus fields exceeds it. PHP then logs a warning and parses nothing, so $_POST and $_FILES are empty. Set post_max_size above upload_max_filesize times the files per request.
solid answer
~40 sTwo directives apply. `upload_max_filesize` (default `2M`) limits **each file**; `post_max_size` (default `8M`) limits the **entire body**: every file, every field and the multipart overhead. Raising only the first to `10M` leaves the body capped at 8 MB. When `Content-Length` exceeds `post_max_size`, PHP emits an `E_WARNING` ("POST Content-Length of N bytes exceeds the limit of M bytes") during request startup and parses **nothing**: `$_POST` and `$_FILES` are both empty, so there is no `UPLOAD_ERR_*` code, and any hidden token check also fails. A file that only exceeds `upload_max_filesize` behaves differently: its entry gets `UPLOAD_ERR_INI_SIZE` and the other fields still arrive. Fix it by setting `post_max_size` comfortably above `upload_max_filesize` x the files per request, both in `INI_PERDIR` config, and detect the empty-body case by comparing `$_SERVER['CONTENT_LENGTH']` with the limit.
code
ini · 4 lines; job-application site, FPM pool or .user.ini
upload_max_filesize = 10M
post_max_size = 24M ; two 10M files plus form fields
max_file_uploads = 5go deeper
Recall that there are two limits: upload_max_filesize per file and post_max_size for the whole request, and that the second must be larger.
Explain the two failure modes: an UPLOAD_ERR_INI_SIZE entry versus completely empty $_POST and $_FILES, and why neither can be fixed with ini_set().
Diagnose the empty-POST symptom from the log warning and Content-Length, size all three directives together, and keep a business size check in code.
Decide whether large uploads belong on the application at all, or go straight to storage, and set limits per route rather than per server.
## Two limits, two different failures PHP applies two size directives to uploads, and they fail in very different ways: | Directive | Default | Limits | When exceeded | |---|---|---|---| | `upload_max_filesize` | `2M` | each uploaded file | that file's entry gets `UPLOAD_ERR_INI_SIZE`; everything else arrives | | `post_max_size` | `8M` | the whole request body | nothing is parsed; `$_POST` and `$_FILES` are empty | | `max_file_uploads` | `20` | number of files per request | extra files are skipped with a warning | Both size directives are `INI_PERDIR`: they can be set in `php.ini`, `.htaccess`, `.user.ini` or an FPM pool's `php_value` / `php_admin_value`, but not with `ini_set()`, because the body is parsed before the script starts. ## What happened to the 9 MB CV The server raised `upload_max_filesize` to `10M` and left `post_max_size` at `8M`. The 5 MB CV fits under both. The 9 MB CV passes the per-file limit but pushes the body over 8 MB, so: 1. PHP compares the request's `Content-Length` with `post_max_size` before reading the body. 2. It emits an `E_WARNING`: "POST Content-Length of 9441230 bytes exceeds the limit of 8388608 bytes". 3. It skips parsing entirely: no fields, no files, no temp files. 4. The script runs with `$_SERVER['REQUEST_METHOD'] === 'POST'` and empty `$_POST` and `$_FILES`. The handler therefore sees what looks like an empty submission. Typical downstream symptoms are "please fill in your name" errors on a form the user completed, or a failed hidden-token check that looks like a security event. The warning goes to the error log and, with `display_errors` on, to the output. ## Detecting it in the handler Because there is no error code, detect it from the request itself: - the method is POST; - `$_POST` and `$_FILES` are both empty; - `$_SERVER['CONTENT_LENGTH']` is larger than `ini_parse_quantity(ini_get('post_max_size'))`. `ini_parse_quantity()` (PHP 8.2+) turns shorthand like `8M` into bytes, so you do not have to parse the suffix yourself. When the condition holds, answer with a clear "the upload is too large, the maximum is N MB" message instead of running validation on an empty form. ## Choosing the values - Set `upload_max_filesize` to the largest single file you accept (say `10M` for CVs). - Set `post_max_size` to that value times the files allowed per request, plus headroom for fields and multipart boundaries: one CV plus a cover letter at 10 MB each suggests about `24M`. - Keep `max_file_uploads` at or above the number of file inputs the form can send. - Enforce your **business** limit in code too, by checking `$_FILES['cv']['size']`, so a later config change does not silently widen what you accept. - Remember that a web server in front of PHP may enforce its own body limit and reject the request before PHP sees it; that limit must be at least `post_max_size`. ## Checking the values actually in force A frequent reason the "fix" does not work is editing the wrong configuration: - The CLI and the web server API often read **different** `php.ini` files, so `php -i` on the command line can show `10M` while the web requests still use `8M`. - Check from inside a web request: `ini_get('post_max_size')` in a temporary diagnostic page, or `phpinfo()` on a protected route, shows the values that apply to that directory. - Per-directory files (`.user.ini`, `.htaccess`) and FPM pool settings override `php.ini`, and `.user.ini` changes are picked up only after its cache period expires. - After changing a pool or `php.ini`, the PHP-FPM service must reload before new values apply. ## Why the limits are separate The per-file limit lets PHP stop writing one oversized file and still process the rest of the form. The body limit protects the process from reading an arbitrarily large request at all, which is why exceeding it discards everything rather than trimming it.
- What changes if the 9 MB CV only exceeds upload_max_filesize and the body fits under post_max_size?PHP parses the body normally. The CV's `$_FILES` entry gets `UPLOAD_ERR_INI_SIZE` (1), with `size` 0 and an empty `tmp_name`, while every other field and file arrives. The handler can show a precise "file too large" message and keep the rest of the user's input.
- Why can't the handler raise post_max_size with ini_set() for one large-upload route?The directive is `INI_PERDIR`, so `ini_set()` cannot change it, and the body has already been parsed or discarded before the script's first line. Use a per-directory setting (`.user.ini`, `.htaccess`) or a separate FPM pool for the upload endpoint if only that route needs a higher limit.
saying these in an interview costs you the question
- Raising upload_max_filesize alone lets larger files through.
- A body over post_max_size gives the file UPLOAD_ERR_INI_SIZE.
- post_max_size limits each file, not the whole body.
- PHP keeps the fields that fit under post_max_size and drops the rest.
- ini_set('post_max_size', '32M') in the handler fixes the limit.