Large uploads can fail before the application code ever sees the request, which blocks media libraries, backup restores, theme or plugin installers, and bulk imports that depend on normal multipart uploads. Raising the effective PHP upload size limit lets legitimate requests reach the application instead of being discarded at the runtime boundary.
The main upload ceiling comes from more than one directive. upload_max_filesize limits each uploaded file, post_max_size limits the full request body, and larger uploads can still require extra memory_limit headroom while the application parses archives, images, or other heavy payloads. When the request body exceeds post_max_size, the upload never reaches normal application handling and the submitted body can arrive without the expected $_POST or $_FILES data.
Change the runtime that actually serves the site, not only the CLI binary in PATH. PHP-FPM pool directives, per-directory .user.ini files on CGI/FastCGI, or smaller request-body limits in Apache, Nginx, or a reverse proxy can still win over the base php.ini file. Per-directory .user.ini changes are cached rather than applied immediately, so verification needs to happen against the same site path that handles the real upload.
Related: How to increase PHP memory limit
Related: How to limit request body size in Apache
Related: How to limit request sizes in Nginx
$ php-fpm8.3 -i | grep -E 'Loaded Configuration File|^(upload_max_filesize|post_max_size|memory_limit|max_input_time) =>' Loaded Configuration File => /etc/php/8.3/fpm/php.ini max_input_time => 60 => 60 memory_limit => 128M => 128M post_max_size => 8M => 8M upload_max_filesize => 2M => 2M
Use the binary that matches the runtime serving the site, such as php-fpm or php-fpm8.4. If the site runs through the Apache module instead, confirm the active values from a temporary request-side probe because php --ini only reports the CLI configuration tree.
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)\\]' /etc/php/8.3/fpm/pool.d /etc/php-fpm.d 2>/dev/null
No output means no current pool file is overriding these directives. If this command returns a matching line, change that pool file instead because the pool value wins for requests handled by that pool.
$ 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 pool file instead when the previous step finds an active PHP-FPM override.
Keep the timestamped backup until the new limit is confirmed from the same site path that handles uploads.
$ sudoedit /etc/php/8.3/fpm/php.ini
Open the discovered pool file instead of php.ini when the active values come from /etc/php/8.3/fpm/pool.d/*.conf or /etc/php-fpm.d/*.conf.
Related: How to find PHP configuration files
; Keep the full request body larger than the per-file limit upload_max_filesize = 128M post_max_size = 160M memory_limit = 256M
post_max_size must stay larger than upload_max_filesize so the multipart body can reach PHP. Keep memory_limit above the amount of memory the application needs while parsing or processing the uploaded data.
On CGI or FastCGI deployments without main configuration access, place the same directives in .user.ini. When PHP runs as an Apache module, use an allowed php_value override point instead. Current PHP documentation still lists the default user_ini.cache_ttl as 300 seconds, so a .user.ini change can take a few minutes to appear.
max_input_time = 300
max_input_time covers request parsing time, including file uploads. max_execution_time applies after script execution begins.
$ sudo php-fpm8.3 -t [25-Mar-2026 23:02:43] 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 on many RHEL-family systems. Skip this syntax test only when the change was made in .user.ini rather than the main PHP-FPM configuration.
Do not reload the runtime until the configuration test succeeds.
$ sudo systemctl reload php8.3-fpm
Reload Apache instead when PHP runs as an Apache module, and use the unversioned php-fpm unit on hosts that package it that way. A .user.ini change is picked up after the user_ini.cache_ttl interval instead of a service reload.
$ 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'));
PHP
Keep the probe under the same virtual host, document root, or PHP-FPM pool that receives the real uploads so the result reflects the effective request-side configuration instead of a different SAPI or pool.
Remove the diagnostic file after testing because it exposes runtime 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=128M post_max_size=160M memory_limit=256M max_input_time=300
If lower values still appear, inspect later PHP-FPM pool overrides, per-directory .user.ini files, and any 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 script in place exposes the site's runtime configuration to anyone who can request the URL.