How to increase the PHP file upload limit

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.

Steps to increase the PHP file upload limit:

  1. Create a temporary diagnostic script in the same site path that handles uploads.
    $ 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.

  2. Request the diagnostic script through the same site, virtual host, or pool that handles uploads.
    $ 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.

  3. Search PHP-FPM pool files for upload-related overrides when the diagnostic request reports fpm-fcgi.
    $ 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.

  4. Search Apache configuration for upload-related overrides when the diagnostic request reports apache2handler.
    $ 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.

  5. Search the application tree for per-directory .user.ini files when the site runs through CGI or FastCGI.
    $ 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.

  6. Back up the file that currently controls the effective upload limit.
    $ 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.

  7. Open the file that controls the effective limit in a text editor.
    $ 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.

  8. Set the per-file and full-request limits high enough for the largest legitimate upload.
    ; 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.

  9. Increase memory_limit only when the application must parse, unpack, or transform the larger upload after it arrives.
    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.

  10. Increase max_input_time only when large or slow uploads time out while PHP is still receiving the request body.
    max_input_time = 300

    max_input_time covers input parsing and file uploads. max_execution_time applies after script execution begins.

  11. Raise max_file_uploads only when one submission legitimately carries many files.
    max_file_uploads = 50

    PHP counts only populated upload fields toward this limit, so blank upload inputs do not consume the quota.

  12. Test the PHP-FPM configuration before reloading the service when the change lives in php.ini or a PHP-FPM pool file.
    $ 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.

  13. Reload the runtime that serves the application, or wait for the .user.ini cache interval when that is the only layer that changed.
    $ 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.

  14. Request the same diagnostic script again and confirm the higher live values from the web-facing runtime.
    $ 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.

  15. Remove the temporary diagnostic script after the check.
    $ 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.