On a PHP photo-contest site on a shared host, why does ini_set('upload_max_filesize', '20M') fail to raise the upload limit?
answer
- changeable modes: USER, PERDIR, SYSTEM, ALL
- upload_max_filesize is PERDIR
- ini_set returns false, no warning
- body already parsed before your code
- set it in .user.ini or .htaccess
basics
~20 supload_max_filesize is changeable only at INI_PERDIR or INI_SYSTEM level, so ini_set() from a script refuses it and returns false. The upload is also parsed before the script runs. Set it in php.ini, .user.ini or .htaccess instead.
solid answer
~40 sEvery directive has a **changeable mode**: `INI_USER` (scripts may change it), `INI_PERDIR` (php.ini, `.htaccess`, `httpd.conf` or `.user.ini`), `INI_SYSTEM` (php.ini or `httpd.conf`) and `INI_ALL` (anywhere). `upload_max_filesize` and `post_max_size` are `PERDIR`, so `ini_set()` refuses them: it returns `false` without a warning, and the value stays at its default `2M`. Even if it could change them, it would be too late, because PHP parses the request body before the first line of the script runs. On a shared host you set them per directory: in a `.user.ini` when PHP runs as FastCGI, including PHP-FPM, or with `php_value` in `.htaccess` when PHP runs as Apache's module, assuming the host allows it. `ini_get_all()` shows each directive's mode.
code
php · 8 lines<?php
declare(strict_types=1);
var_dump(ini_set('upload_max_filesize', '20M')); // bool(false): INI_PERDIR
var_dump(ini_get('upload_max_filesize')); // string(2) "2M"
var_dump(ini_set('memory_limit', '256M')); // old value, e.g. string(4) "128M"
var_dump(ini_get('memory_limit')); // string(4) "256M"go deeper
Remember that some settings, such as the upload size limit, cannot be changed from inside a script with ini_set().
Name the four changeable modes, explain ini_set()'s silent false return, and why upload limits must be set before the script runs.
Choose the right per-directory mechanism for the SAPI in use, check ini_set() results, and treat PERDIR and SYSTEM modes as the host's delegation boundary.
Decide which limits tenants or teams may change themselves and which stay with operators, and make those settings visible in reviewed files.
## The symptom A photo-contest site lets entrants upload a 12 MB photo. Uploads fail, and the entrant's `$_FILES` entry reports that the file exceeded the size limit. A developer adds this to the top of the upload handler: ```php ini_set('upload_max_filesize', '20M'); ``` Nothing changes. `var_dump(ini_get('upload_max_filesize'))` still prints `2M`. ## Changeable modes Every ini directive is registered with a **changeable mode** that says where it may be set. The manual defines four, also exposed as PHP constants: | Constant | Where the directive may be set | |---|---| | `INI_USER` | in user scripts with `ini_set()`, the Windows registry, or `.user.ini` | | `INI_PERDIR` | php.ini, `.htaccess`, `httpd.conf` or `.user.ini` | | `INI_SYSTEM` | php.ini or `httpd.conf` only | | `INI_ALL` | anywhere, including `ini_set()` | `ini_set()` changes a directive only if its mode includes `INI_USER`, which `INI_ALL` does. Examples: - `memory_limit`, `max_execution_time`: `INI_ALL`, so `ini_set()` works; - `upload_max_filesize`, `post_max_size`, `max_file_uploads`, `output_buffering`: `INI_PERDIR`, so it does not; - `disable_functions`, `extension_dir`, `file_uploads`: `INI_SYSTEM`, so only server-level configuration can set them. ## What ini_set() returns `ini_set(string $option, string|int|float|bool|null $value): string|false` returns the **old value** as a string on success and `false` on failure. Refusing a `PERDIR` directive is a failure, but it emits **no warning**, so the call looks harmless unless you check the return value: ```php if (ini_set('upload_max_filesize', '20M') === false) { // not changeable from a script } ``` ## Too late anyway Even for a directive that `ini_set()` could change, timing matters. For a `multipart/form-data` POST, PHP reads and parses the request body, enforcing the upload and body limits, **before** your script's first line runs. By the time `ini_set()` executes, the oversized file has already been rejected. That is exactly why these directives are not script-changeable. ## Where to set them on a shared host Without access to php.ini, use the per-directory mechanism that matches how PHP runs: 1. **PHP as FastCGI** (PHP-FPM or `php-cgi`): put the values in a **`.user.ini`** file in the document root or the upload handler's directory. 2. **PHP as Apache's module** (`apache2handler`): use `php_value upload_max_filesize 20M` in `.htaccess`, if the host's Apache configuration allows overrides. 3. **Anywhere you control the server**: php.ini, or a file in the scan directory. Raise both limits together: the file limit and the whole-body limit, which must be at least as large. How the two interact is part of receiving uploads, a separate topic. ## A decision table | Directive mode | `ini_set()` | `.user.ini` | `.htaccess` (`php_value`) | php.ini | |---|---|---|---|---| | `INI_ALL` | yes | yes | yes | yes | | `INI_PERDIR` | no | yes | yes | yes | | `INI_SYSTEM` | no | no | no | yes | The `.user.ini` column applies only when PHP runs as CGI or FastCGI, and the `.htaccess` column only under Apache's module. Reading the table for the photo-contest site: the limits are `PERDIR`, the host runs PHP-FPM, so `.user.ini` is the tool. ## Common mistakes - Assuming `ini_set()` worked because no warning appeared. - Raising the per-file limit but not the whole-body limit, so large uploads still fail. - Testing the new value from the shell, where neither `.user.ini` nor `.htaccess` applies. - Using `php_value` in `.htaccess` on a FastCGI setup, where no PHP module inside Apache exists to apply it. ## How to check a directive's mode `ini_get_all('core')` returns, for each directive, its `global_value`, `local_value` and `access`, where `access` is the bitmask of the modes above. The manual's list of php.ini directives also gives each one's mode. ## Why the modes exist Modes let a host delegate safely. Letting any script raise upload or body limits would let one tenant's code accept huge requests that tie up the server; letting it change `disable_functions` would defeat hardening. `PERDIR` puts such knobs in files an operator can see and audit, and `SYSTEM` keeps the dangerous ones with the server's owner.
- Why doesn't ini_set() warn when it refuses a directive?It reports failure only through its return value: false instead of the old value. Code that ignores the return value never learns the change was refused, so check it with === false, or confirm with ini_get().
- Which directives can a script change with ini_set()?Those whose changeable mode includes INI_USER, which INI_ALL does. memory_limit and max_execution_time qualify; PERDIR ones such as upload_max_filesize and SYSTEM ones such as disable_functions do not. ini_get_all() reports each directive's access bitmask.
- The host runs PHP as an Apache module. Will a .user.ini work?No. .user.ini files are processed only by the CGI and FastCGI SAPIs, including PHP-FPM. Under Apache's module, per-directory settings go in .htaccess with php_value or php_flag, which the Apache configuration must allow.
saying these in an interview costs you the question
- ini_set() can change any directive for the current request.
- ini_set() throws an exception when a directive is not changeable.
- Calling ini_set() before move_uploaded_file() raises the upload limit in time.
- upload_max_filesize is an INI_SYSTEM directive only php.ini can set.
- .user.ini works the same under Apache's module as under PHP-FPM.