← Back to blog

How to increase the upload size in WordPress, and why three of the top guides are wrong

The SandyWP team 8 min read

The number WordPress shows you under Media > Add New is one line of PHP: min( upload_max_filesize, post_max_size ), taken from the two PHP directives and then passed through a filter. Raise both of those in php.ini, .user.ini, or your PHP-FPM pool, and the limit moves.

That is the whole mechanism. Everything else that gets recommended for this problem either changes a different number or cannot change anything at all, and we will go through exactly why.

Where the number actually comes from

wp_max_upload_size() in wp-includes/media.php is five lines:

function wp_max_upload_size() {
	$u_bytes = wp_convert_hr_to_bytes( ini_get( 'upload_max_filesize' ) );
	$p_bytes = wp_convert_hr_to_bytes( ini_get( 'post_max_size' ) );

	return apply_filters( 'upload_size_limit', min( $u_bytes, $p_bytes ), $u_bytes, $p_bytes );
}

Two ini_get() calls, a min(), and a filter. There is no WordPress option, no constant, and no default of its own.

So the familiar claim that "WordPress defaults to 32MB" is not describing WordPress. PHP's own shipped defaults are upload_max_filesize = 2M and post_max_size = 8M (PHP manual, core directives), and whatever you see instead of those came from your host.

That matters for the fix. You are not configuring WordPress, you are configuring PHP, and the place PHP reads its configuration from is the thing you need to find.

Why editing functions.php cannot work

The most common suggestion after .htaccess is to drop ini_set() or @ini_set() into your theme's functions.php. It fails, and not because of a theme-update caveat.

Both directives are INI_PERDIR in the PHP manual's changeability column. That means they can be set in php.ini, in .user.ini, in an Apache php_value, or in a pool config, and not at runtime.

By the time functions.php loads, the request body has already been parsed. PHP read the limits before your code existed, so there is nothing left to change: ini_set() returns false and the value stays where it was.

WordPress core knows this and ships the check. wp_is_ini_value_changeable() in wp-includes/load.php reads ini_get_all() and returns true only for INI_ALL or INI_USER settings.

Run it against the two upload directives on your own install and you get the answer directly:

var_dump( wp_is_ini_value_changeable( 'upload_max_filesize' ) ); // false
var_dump( wp_is_ini_value_changeable( 'memory_limit' ) );        // true

One guide on the first page recommends @ini_set( 'upload_max_size', '64M' ). That is doubly broken: upload_max_size is not a PHP directive at all, and the real one would not accept a runtime write anyway.

WP_MEMORY_LIMIT does nothing to your upload size

The top-ranking result for this phrase tells you to put define( 'WP_MEMORY_LIMIT', '96M' ); in wp-config.php.

Look at wp_max_upload_size() again. It reads upload_max_filesize and post_max_size.

It never reads memory_limit, and no filter in core connects the two.

WP_MEMORY_LIMIT is a real and useful constant, and we wrote about what it does and does not do. Raising it will not move the number on the Media screen by a single byte.

Memory does matter downstream, after the file has arrived: generating the intermediate image sizes for a large photo is where PHP runs out of memory. That is a separate failure with a separate symptom, and conflating the two is why people raise memory, see no change, and conclude their host is lying to them.

post_max_size is the one that fails confusingly

upload_max_filesize caps one file. post_max_size caps the entire request body, which includes the file plus every other form field.

When a request exceeds post_max_size, PHP discards the whole body before your script runs. $_POST and $_FILES both come back empty, so WordPress has no file to complain about and no nonce to verify.

What you see is not "file too large". It is a generic failure, a page that reloads to nothing, or a nonce error, which sends people hunting for a plugin conflict that does not exist.

Keep post_max_size meaningfully above upload_max_filesize, not equal to it. The remaining form fields and the multipart boundaries are small but not zero.

Setting post_max_size to 0 gives you a limit of zero

PHP treats post_max_size = 0 as unlimited. WordPress does not.

wp_convert_hr_to_bytes() casts the string to an integer and multiplies by the suffix, so "0" becomes 0 bytes. Then min( $u_bytes, 0 ) is 0.

The Media screen reports a maximum upload size of 0 B, and the JavaScript uploader refuses every file before it sends anything. PHP would have accepted all of them.

If you want a very high ceiling, set a very high number. Do not set zero.

The web server has its own limit, in front of PHP

If PHP is configured correctly and large files still fail, the request is being rejected before PHP sees it.

nginx defaults client_max_body_size to 1m and returns 413 when a request exceeds it (nginx core module docs). One megabyte, regardless of what PHP allows.

http {
    client_max_body_size 256m;
}

The 413 is the tell. A PHP-side limit produces a WordPress error page or an empty $_FILES, whereas a server-side limit produces a bare status code from the server with no WordPress involvement at all.

On Apache, LimitRequestBody does the same job. And this is also why .htaccess advice is unreliable: php_value directives in .htaccess only work when PHP runs as an Apache module, and under PHP-FPM, which is how most hosts run PHP now, Apache ignores them silently.

Multisite adds two more caps, and they are easy to miss

On a network, upload_size_limit_filter() hooks upload_size_limit and narrows it further:

$fileupload_maxk         = (int) get_site_option( 'fileupload_maxk', 1500 );
$max_fileupload_in_bytes = KB_IN_BYTES * $fileupload_maxk;

return min( $size, $max_fileupload_in_bytes, get_upload_space_available() );

So the real limit on a multisite install is the smallest of four numbers: the two PHP directives, the network's Max upload file size setting in kilobytes, and the storage the site has left in its quota.

The quota one is nasty. get_upload_space_available() returns 0 once a site has used its allowance, and a 0-byte limit looks identical to a misconfigured php.ini.

There is also a discrepancy worth knowing about if the option row is missing. The filter above falls back to 1500, but wp-admin/network/settings.php renders the field with get_site_option( 'fileupload_maxk', 300 ).

So the screen can show 300 while the enforced value is 1500. A normal network install writes 1500 into the option at setup, so you only hit this where the row was never written or got deleted.

If you are working through network-level behaviour, our notes on configuring multisite cover the rest of the network settings screen.

Where to actually change it

In order of how likely it is to work:

  1. php.ini, if you control it. Set both upload_max_filesize and post_max_size, then restart PHP-FPM.
  2. .user.ini in the web root, if your host runs PHP-FPM or CGI and allows it. Same two directives. It is cached, so changes can take a few minutes.
  3. Your PHP-FPM pool config, with php_admin_value[upload_max_filesize]. This overrides .user.ini, which is sometimes why a correct .user.ini appears to do nothing.
  4. Your host's control panel, which writes one of the above for you.
  5. .htaccess with php_value, only on mod_php. Check php_sapi_name() before you trust it.

Confirm the result rather than assuming it. php -i | grep upload_max_filesize gives you the CLI value, which is often a different php.ini from the web one, so prefer a one-line script served over HTTP:

<?php echo ini_get( 'upload_max_filesize' ), ' / ', ini_get( 'post_max_size' );

WordPress will also tell you. Tools > Site Health > Info > Media Handling lists both directives as PHP sees them in a web request, which is the number that counts.

Change it on a copy, not on the live site

Raising an upload limit is usually safe. Finding out which of the five places above your host actually reads is not, because the process is a sequence of edits and PHP restarts against a site people are using.

This is the kind of question a disposable install answers in a couple of minutes. On a SandyWP sandbox the limits are editable from the PHP configuration panel: upload_max_filesize and post_max_size are both adjustable, with a default of 256 MB each.

Saving restarts the sandbox for a few seconds. The panel also enforces the same relationship core relies on, refusing an upload limit above the post limit.

If the problem is specific to your own site's content or plugins rather than to PHP, bring a copy of the real site across and reproduce it there. For the file-level work, SSH and SFTP give you the install directly, with wp-cli already on the box.

Then take the one change that worked to production, instead of five that might have.

The short version

  • The limit is min( upload_max_filesize, post_max_size ), filtered. Nothing else in core sets it.
  • functions.php and ini_set() cannot change either directive. They are INI_PERDIR.
  • WP_MEMORY_LIMIT is unrelated to the upload size.
  • post_max_size = 0 means unlimited to PHP and zero bytes to WordPress.
  • A 413 means the web server rejected it, not PHP.
  • On multisite, the network setting and the site's storage quota both cap it further.