Large uploads can fail before the application code ever handles the request, which breaks media libraries, backup restores, plugin installers, and bulk imports that depend on normal multipart form submissions. Raising the effective PHP upload limit lets the application accept the larger request instead of rejecting it at the runtime boundary.
The main pair is upload_max_filesize for each file and post_max_size for the full request body. The PHP manual notes that post_max_size must stay larger than upload_max_filesize, and memory_limit should generally stay larger than post_max_size. When the body exceeds post_max_size, $_POST and $_FILES arrive empty instead of containing the submitted upload data.
The example flow uses PHP-FPM on Ubuntu or Debian because that layout is common, but the same limit chain applies elsewhere once the active web-facing runtime is identified. A PHP-FPM pool override, per-directory .user.ini file, Apache php_value override, or smaller request-body limit in Apache, Nginx, or a reverse proxy can still win over the base php.ini, so the final check must come through the same URL path that handles uploads.
Related: How to limit request body size in Apache
Related: How to limit request sizes in Nginx
$ sudo tee /var/www/app.example.test/public/upload-check.php >/dev/null <<'PHP'
<?php
header('Content-Type: text/plain');
printf("SAPI=%s\n", PHP_SAPI);
printf("Loaded php.ini=%s\n", php_ini_loaded_file() ?: 'none');
printf("upload_max_filesize=%s\n", ini_get('upload_max_filesize'));
printf("post_max_size=%s\n", ini_get('post_max_size'));
printf("memory_limit=%s\n", ini_get('memory_limit'));
printf("max_input_time=%s\n", ini_get('max_input_time'));
printf("max_file_uploads=%s\n", ini_get('max_file_uploads'));
PHP
Keep the diagnostic file temporary because it exposes runtime configuration details to anyone who can reach the URL.
$ curl -s https://app.example.test/upload-check.php SAPI=fpm-fcgi Loaded php.ini=/etc/php/8.3/fpm/php.ini upload_max_filesize=2M post_max_size=8M memory_limit=128M max_input_time=60 max_file_uploads=20
If the response shows apache2handler, inspect Apache overrides before changing the global php.ini. If it shows fpm-fcgi, inspect the matching PHP-FPM pool and any nearby .user.ini files before assuming the main file is the last active layer.
Related: How to find PHP configuration files
$ sudo grep -REn '^[[:space:]]*php(_admin)?_value\[(upload_max_filesize|post_max_size|memory_limit|max_input_time|max_file_uploads)\]' /etc/php/8.3/fpm/pool.d /etc/php-fpm.d 2>/dev/null
No output means the pool files are not currently overriding these directives. If the command returns a matching line, change that pool file instead because the pool value wins for requests handled by that pool.
Related: How to find PHP configuration files
$ sudo grep -REn 'php_(admin_)?value (upload_max_filesize|post_max_size|memory_limit|max_input_time|max_file_uploads)' /etc/apache2 /etc/httpd 2>/dev/null
Change the matching virtual host or directory context instead of the global php.ini when this command returns an active override.
$ find /var/www/app.example.test -name .user.ini -print /var/www/app.example.test/public/.user.ini
The default per-directory filename is .user.ini, and PHP re-reads it on the default user_ini.cache_ttl interval of 300 seconds instead of on every request. Apache mod_php ignores this file.
$ sudo cp /etc/php/8.3/fpm/php.ini /etc/php/8.3/fpm/php.ini.bak-$(date +%Y%m%d%H%M%S)
Back up the discovered pool file or the existing .user.ini file instead when an earlier step shows that layer is setting the live value.
A timestamped backup shortens rollback time if a malformed edit prevents new PHP-FPM workers from loading.
$ sudoedit /etc/php/8.3/fpm/php.ini
Open the discovered PHP-FPM pool file or .user.ini file instead when an earlier step shows that layer owns the active value.
Related: How to find PHP configuration files
; php.ini or .user.ini upload_max_filesize = 128M post_max_size = 160M ; PHP-FPM pool file php_admin_value[upload_max_filesize] = 128M php_admin_value[post_max_size] = 160M
post_max_size must stay larger than upload_max_filesize so the full multipart request can reach PHP.
Keep the existing pool syntax when a pool file already uses php_value[] rather than php_admin_value[]. These directives are INI_PERDIR, so raising them with ini_set() inside the application does not lift the upload ceiling for the current request.
memory_limit = 256M
The PHP manual recommends keeping memory_limit larger than post_max_size. Image processing, archive extraction, and large import jobs often need more headroom than the upload size alone suggests.
Related: How to increase PHP memory limit
max_input_time = 300
max_input_time covers input parsing and file uploads. max_execution_time applies after script execution begins.
max_file_uploads = 50
PHP counts only populated upload fields toward this limit, so blank upload inputs do not consume the quota.
$ sudo php-fpm8.3 -t NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful
Use the matching binary name on the host, such as php-fpm or php-fpm8.4. Skip this syntax test when the only change lives in .user.ini or an Apache override.
Do not reload the runtime until the configuration test succeeds.
$ sudo systemctl reload php8.3-fpm
Use the versioned unit that matches the host package, or php-fpm on systems that ship the unversioned service. Reload Apache instead when PHP runs as an Apache module, and wait for user_ini.cache_ttl when only .user.ini changed.
$ curl -s https://app.example.test/upload-check.php SAPI=fpm-fcgi Loaded php.ini=/etc/php/8.3/fpm/php.ini upload_max_filesize=128M post_max_size=160M memory_limit=256M max_input_time=300 max_file_uploads=50
If smaller values still appear, check for a later PHP-FPM pool override, a nearer .user.ini file, an Apache php_value or php_admin_value override, or a smaller request-body limit in Apache, Nginx, or the reverse proxy.
Related: How to find PHP configuration files
$ sudo rm /var/www/app.example.test/public/upload-check.php
Leaving the file reachable keeps the active SAPI, loaded configuration path, and runtime limits exposed to anyone who can request the URL.