How to increase the maximum file upload size in WordPress

WordPress refuses an upload with "exceeds the upload_max_filesize directive" because the limit comes from PHP, not WordPress. Raise upload_max_filesize and post_max_size together in the right place for your stack, then verify in Site Health.

You try to upload a 12 MB image or a video into the WordPress media library and the upload dies with The uploaded file exceeds the upload_max_filesize directive in php.ini. or with a 413 response and no file. The ceiling is not in WordPress. It comes from PHP, and depending on your stack it can also come from the web server or from a second, separate Multisite setting. This article explains where the limit actually lives and walks through the five places you might need to change, with a verification at the end.

Prerequisites. You need one of the following: access to your host control panel (cPanel, Plesk, DirectAdmin, hPanel), SFTP access to the WordPress root, or SSH access to the server. Know which web server your site runs on. Site Health under Tools > Site Health > Info > Server reports the server software under Web server. If you are not sure which PHP handler your host uses (mod_php, PHP-FPM, or a managed abstraction), a quick look at Site Health > Info > Server > PHP SAPI will tell you.

Where the upload size limit comes from

WordPress core has no hard-coded upload cap. The effective limit is calculated at runtime by wp_max_upload_size(), which calls ini_get('upload_max_filesize') and ini_get('post_max_size') and returns the smaller of the two. That means raising one without raising the other has no effect. Whichever value is lower is the one WordPress enforces.

PHP's own defaults, documented in the core INI directive reference, are surprisingly small:

Directive PHP default Scope What it controls
upload_max_filesize 2M INI_PERDIR Max size of one uploaded file.
post_max_size 8M INI_PERDIR Max total POST body. Must be larger than upload_max_filesize.
memory_limit 128M INI_ALL PHP heap budget per request. Used heavily during thumbnail generation.

The INI_PERDIR scope matters. It means upload_max_filesize and post_max_size can only be set in php.ini, .htaccess (Apache with mod_php), .user.ini, or a PHP-FPM pool config. They cannot be changed from inside PHP code at runtime. I cover that trap in the wp-config section below, because it is the single most common mistake I see.

On top of PHP, the web server has its own cap on how large an HTTP request body may be. Apache calls it LimitRequestBody. Nginx calls it client_max_body_size, and its default is 1M, which is lower than PHP's default. Whichever layer sets the lowest cap wins, so in practice you may have to raise the value in two places even if you only think of it as "a PHP setting".

Check your current limit first

Before changing anything, read the three values and see which one is the active ceiling. That way you only raise the one that matters and you can confirm the change landed afterwards.

The fastest path inside WordPress is Tools > Site Health > Info > Media Handling. That section reports the Max size of a file upload directly, taken from wp_max_upload_size(). If it reads 8 MB, your post_max_size is the ceiling. If it reads 2 MB, upload_max_filesize is. Anything higher than both of those is a host that already raised the PHP defaults for you.

For the raw numbers, create a short diagnostic file in the WordPress root using the file manager in your hosting panel (or upload it via SFTP):

<?php
// upload-check.php, delete after use.
echo 'upload_max_filesize: ' . ini_get('upload_max_filesize') . PHP_EOL;
echo 'post_max_size:       ' . ini_get('post_max_size')       . PHP_EOL;
echo 'memory_limit:        ' . ini_get('memory_limit')        . PHP_EOL;
echo 'max_execution_time:  ' . ini_get('max_execution_time')  . PHP_EOL;
echo 'max_input_time:      ' . ini_get('max_input_time')      . PHP_EOL;

Visit https://yoursite.nl/upload-check.php. Expected output on a healthy WordPress host: upload_max_filesize: 64M, post_max_size: 64M, memory_limit: 256M, max_execution_time: 120, max_input_time: 120. Delete the file as soon as you have the numbers, because leaving a phpinfo()-style file in the document root is a security smell.

The post_max_size trap

Before you change anything, understand the relationship between the two file-related directives. post_max_size must stay larger than upload_max_filesize, always. If someone on a forum tells you to set upload_max_filesize to 64M but leaves post_max_size at the 8M default, the upload still fails at 8 MB and you will waste an afternoon wondering why. The PHP manual on common file upload pitfalls puts it plainly: when the POST body exceeds post_max_size, PHP silently empties both $_POST and $_FILES and hands the request to WordPress with nothing attached. WordPress then shows a confusing blank or empty-upload error that looks nothing like a size error.

My rule of thumb: set post_max_size equal to upload_max_filesize, or one step higher. 64M and 64M is fine. 64M and 80M is fine. 64M and 8M is a trap.

Method 1: the host control panel (try this first)

Most managed and shared hosts expose a panel-level PHP settings editor. This is the cleanest way to change the values, because it writes them to the right file for you and the change applies without a restart.

  1. Log into your host control panel.
  2. Open the PHP settings editor. On cPanel it is Software > Select PHP Version > PHP Options, or on older cPanels MultiPHP INI Editor. On Plesk it is PHP Settings on the domain's Hosting panel. On DirectAdmin it is PHP Selector. On hPanel (Hostinger) it is PHP Configuration.
  3. Find upload_max_filesize and raise it to 64M. If your use case is larger, pick a real ceiling like 128M or 256M. Do not jump to 1024M because you feel like it. A finite cap catches runaway uploads before they take out the server.
  4. Find post_max_size and raise it to match, or one step higher.
  5. Find memory_limit and raise it to at least 256M. Image processing during upload can easily need this much, and a low memory limit produces a silent failure during thumbnail generation even when the upload itself succeeded.
  6. Save. Most panels apply the change to the next request with no restart.

Expected output after the change: reload your diagnostic file. You will know the change landed when the three values read what you set.

Verify the upload works. Go to Media > Add New and upload a file that was previously failing. You will know the fix held when the thumbnail appears in the library and you do not see an HTTP error in the uploader.

If the panel does not expose these knobs, or the values appear set but do not take effect, the host has locked them at the server level. Go to method 2, 3, or 4 depending on your stack.

Method 2: .htaccess (Apache with mod_php only)

.htaccess is the classic advice and it is the one that most often fails silently. The directives below only work on Apache when PHP runs as an Apache module (mod_php). They do not work on Nginx, because Nginx ignores .htaccess entirely. They also do not work on Apache when PHP runs through PHP-FPM, because php_value is an Apache-to-PHP bridge that only the mod_php handler honours. On those stacks, the directives are silently ignored and nothing tells you why your upload still fails.

If you have confirmed Apache with mod_php (Site Health shows Apache as the web server and apache2handler as the PHP SAPI), edit .htaccess in your WordPress root using the file manager in your hosting panel or via SFTP:

php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value memory_limit 256M
php_value max_execution_time 300
php_value max_input_time 300

Save the file. The change applies on the next request. If you see a 500 Internal Server Error right after adding these lines, one of three things is true: the server is not running mod_php, your AllowOverride directive is too restrictive, or the host has disabled php_value in .htaccess. Remove the lines and fall back to method 4 (.user.ini) or the host panel.

Verify. Reload the diagnostic file. You will know the fix held when the values now read what you set.

Method 3: Nginx and PHP-FPM (two files, not one)

This method requires SSH access to your server. If you are on managed hosting without shell access, use Method 1 (the host panel) instead. The panel applies the PHP-side changes for you, and on managed Nginx stacks the client_max_body_size is typically pre-configured at a reasonable value.

On the modern default stack (Nginx fronting PHP-FPM), you have to change the limit in two different places. One sets the HTTP body cap at the Nginx layer. The other sets the PHP directives inside the FPM pool. Missing either one leaves the upload broken.

In the Nginx server block (e.g. /etc/nginx/sites-available/yoursite.conf), at the server level, add or raise client_max_body_size:

server {
    server_name yoursite.nl;
    root /var/www/yoursite/current;

    client_max_body_size 80m;

    # ... rest of your config
}

The default is 1M. A 413 Request Entity Too Large response that comes directly from Nginx (no WordPress HTML around it) is the tell that this setting was not raised. Put the directive at the server level rather than inside a location block. WordPress relies on URL rewriting via try_files, and a client_max_body_size set inside a location may not apply to the rewritten request that actually processes the upload. Reload Nginx with sudo systemctl reload nginx after the change.

In the PHP-FPM pool config (e.g. /etc/php/8.3/fpm/pool.d/www.conf), add or raise the three PHP directives:

php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 80M
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 300
php_admin_value[max_input_time] = 300

Use php_admin_value rather than php_value so these cannot be overridden from a .user.ini or from PHP code. Reload PHP-FPM with sudo systemctl reload php8.3-fpm (adjust the version).

Verify. Reload the diagnostic file. You will know the FPM side of the change stuck when upload_max_filesize and post_max_size report the new values. Then retry the upload. You will know the Nginx side is correct when a file larger than the old client_max_body_size completes without a 413.

Method 4: .user.ini (PHP-FPM without pool access)

If you are on PHP-FPM but cannot edit the pool config, PHP supports a per-directory .user.ini file that applies the same settings in the scope of that directory. Create it in the WordPress root (next to wp-config.php) using the file manager in your hosting panel or via SFTP:

upload_max_filesize = 64M
post_max_size = 64M
memory_limit = 256M

PHP rescans .user.ini files on an interval controlled by user_ini.cache_ttl, which defaults to 300 seconds. Changes can take up to five minutes to apply, which is the single most common reason people think .user.ini did not work. Wait out the cache, then reload the diagnostic file.

.user.ini respects the INI_PERDIR scope of upload_max_filesize and post_max_size, which wp-config.php does not, so this is the right tool when you only have filesystem access and no panel.

Method 5: wp-config.php (why ini_set does not work here)

This is where most forum advice goes wrong. A popular snippet looks like this:

// This does not work. Do not use.
@ini_set( 'upload_max_filesize' , '64M' );
@ini_set( 'post_max_size',   '64M');

It looks plausible. It runs without error. The ini_set() call even returns cleanly. The value never takes effect. The reason is the INI_PERDIR scope. PHP's own ini_set() documentation is explicit: INI_PERDIR directives can only be set in php.ini, .htaccess, .user.ini, or a PHP-FPM pool config. At runtime they are read-only. By the time the upload has landed in PHP at all, the file has already been accepted or rejected based on whatever value was configured before the script ran. Calling ini_set() on it is cosmetic.

What wp-config.php can do legitimately is raise memory_limit, because memory_limit is INI_ALL (not INI_PERDIR). That lets you work around a low memory ceiling during image processing even when the raw upload limit is out of reach:

// Raise WordPress memory limits. Safe in wp-config.php.
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );

The first line applies on front-end requests. The second applies in wp-admin. WordPress translates these into an ini_set('memory_limit', ...) call during bootstrap. These are documented in my reference on wp-config.php settings, along with the other constants that do and do not belong in that file.

If you find yourself reaching for ini_set('upload_max_filesize', ...) in wp-config.php or a must-use plugin, stop and use one of the other four methods instead. There is no shortcut through PHP code for this one.

Method 6: the upload_size_limit filter (lowering only)

WordPress exposes an upload_size_limit filter that lets you adjust the effective limit from code. This is useful when you want a smaller cap for certain users (a limit for editors, no limit for administrators), or when you want to surface a clearer error before the upload even begins.

One important constraint: the filter can only lower the limit, never raise it. If PHP says 64M and the filter returns 128M, the server still rejects the upload at 64 MB because PHP has already run. Returning a smaller value makes WordPress pre-check the upload and refuse it in JavaScript with a clearer message.

A per-role example, in a mu-plugin at wp-content/mu-plugins/upload-limits.php:

<?php
// Cap non-admin uploads at 10 MB.
add_filter( 'upload_size_limit', function( $size, $u_bytes, $p_bytes ) {
    if ( current_user_can( 'manage_options' ) ) {
        return $size;
    }
    return 10 * 1024 * 1024;
}, 10, 3 );

The filter receives three arguments: the calculated effective limit in bytes, upload_max_filesize in bytes, and post_max_size in bytes. Return an integer in bytes to change the effective limit.

WordPress Multisite has a second cap

This is the step that catches people out on Multisite. Even after you raise PHP and the web server to allow 64M uploads, Multisite enforces a separate network-wide cap that defaults to 1,500 KB (roughly 1.5 MB). It is stored in the fileupload_maxk site option and applied by upload_size_limit_filter(), which hooks into upload_size_limit automatically on every Multisite install.

To raise it, go to Network Admin > Settings > Network Settings > Upload Settings > Max upload file size and change the value. The unit on the form is kilobytes, so 64M equals 65536 (64 × 1024).

Three rules to remember:

  • The Multisite cap can never exceed the PHP cap. If you set the Network Admin value to 65536 but PHP is still at 2M, the PHP cap wins.
  • Individual sites in the network cannot exceed the network-wide setting.
  • If you raised PHP but uploads still fail at the old 1.5 MB ceiling, the Multisite filter is the cause. That is not a bug. It is upload_size_limit_filter() doing its job.

The Big Image Threshold is not an upload limit

This is the most common point of confusion after the PHP trap. Starting with WordPress 5.3, core introduced the big image size threshold, which scales down images with dimensions above 2560 pixels after they have been uploaded. People sometimes think this is what limits uploads. It is not. The full original file is uploaded first and stored on disk. WordPress then generates a scaled copy (saved as yourfile-scaled.jpg) and uses it as the primary media in the library. The original is preserved and accessible via wp_get_original_image_path().

Two practical implications:

  • This does not prevent uploads. A 20 MB JPEG is uploaded in full regardless of the threshold.
  • It does affect memory. Thumbnail and scaled-copy generation loads the image into memory uncompressed. A 6000 × 4000 pixel 24-bit image needs roughly 72 MB of raw bitmap plus PHP overhead. That is why raising memory_limit matters for large uploads: the upload itself succeeds, but the post-upload scaling step can fail with Allowed memory size exhausted. The dedicated article on allowed memory size exhausted walks through that scenario in detail.
  • PNG files are excluded from scaling. WordPress deliberately skips the scaling step for PNG files to avoid a known issue where an indexed-color PNG-8 gets upsampled to PNG-24/32 during the resize and ends up larger than the original. If you upload a large PNG, expect the original to be used as-is in the media library with no -scaled copy generated.

If you want to disable the threshold entirely, or raise it to 4000 pixels, the one-line filter is:

// Raise the big image threshold to 4000 pixels.
add_filter( 'big_image_size_threshold', function() {
    return 4000;
});

Return false from the same filter to disable scaling entirely. This belongs in a mu-plugin, not in functions.php, because you want it to survive theme switches.

Verify the final result

Regardless of which method you used, run the same three checks at the end:

  1. Reload the diagnostic file at the WordPress root and confirm upload_max_filesize, post_max_size, and memory_limit read what you set.
  2. Go to Tools > Site Health > Info > Media Handling and confirm Max size of a file upload shows the new value.
  3. Upload the original failing file from Media > Add New. You will know the fix held when the thumbnail appears, the status code in the browser's network tab on async-upload.php is 200, and the media library shows the file without an HTTP error badge.

Delete the diagnostic file once you are done.

If it is still broken

If you have raised the limit in all the right places and the upload still fails, the problem is almost certainly not the size cap. The most common follow-ons are:

  • A generic HTTP error in the media library with no obvious cause. The dedicated article on HTTP errors during media upload walks through diagnosing by status code, reading the network tab, and narrowing down the five things that actually produce it.
  • A Fatal error: Maximum execution time of N seconds exceeded during a slow upload over a slow connection. This is a separate timer. The fix is in the article on maximum execution time exceeded, which also explains the difference between max_execution_time and max_input_time (the latter is the timer that most often trips during big uploads and gets missed).
  • A PHP fatal during thumbnail generation. Follow the allowed memory size exhausted article to raise the memory side cleanly.

For a recurring upload workflow, also consider the option that is not a PHP setting at all: upload the file over SFTP into wp-content/uploads/ and register it as a media attachment afterwards with wp media import or the "Add from Server" plugin. That route bypasses every size limit described above, at the cost of a second step. It is the right tool when you are importing a one-off archive of large videos or print-ready PDFs and do not want to touch server configuration.

Want WordPress hosting that stays stable?

I handle updates, backups, and security, and keep performance steady—so outages and slowdowns don't keep coming back.

Explore WordPress maintenance

Search this site

Start typing to search, or browse the knowledge base and blog.