The media library says Maximum upload file size: 8 MB. The file on your desktop is 12 MB. You have read three tutorials, pasted a line into wp-config.php, added a filter to functions.php, cleared the cache, and the number still says 8 MB.
It says 8 MB because that is what PHP told WordPress. The WordPress maximum upload size is not a WordPress setting. Core asks PHP two questions, takes the smaller answer, and prints it.
There are three ceilings involved and the smallest one always wins. The one most people raise is usually not the one stopping them.
Three ceilings, and the smallest one wins
upload_max_filesize is a PHP directive. It caps the size of any single file in a multipart form submission. PHP ships with this set to 2M in its own configuration templates, and hosts commonly raise it to somewhere between 8M and 64M.
post_max_size is also a PHP directive, and it caps the entire request body: every file, plus every form field, plus the multipart boundaries and headers that wrap them. PHP’s own default is 8M. This is the one that quietly ruins things.
The WordPress side is, on a single site, not a limit at all. It is a report. On multisite it becomes a real limit, and that is a genuine exception covered further down.
There is a fourth ceiling on some stacks that has nothing to do with PHP: the web server itself. Nginx enforces client_max_body_size, which defaults to 1 MB, and rejects the request with a 413 before PHP is ever started. If your uploads fail at almost exactly 1 MB and the PHP values look generous, that is your answer, and it lives in the nginx configuration rather than anywhere you can reach from WordPress.
Where the number in the media library comes from
The function is wp_max_upload_size(), it lives in wp-includes/media.php, and it has been in core since WordPress 2.5. It is four lines long. It reads upload_max_filesize, reads post_max_size, converts both from strings like 8M into bytes with wp_convert_hr_to_bytes(), and returns min() of the two through the upload_size_limit filter.
The docblock above it is unusually blunt about what it does: “Determines the maximum upload size allowed in php.ini.” Not allowed by WordPress. Allowed in php.ini.
The string you are staring at is printed in wp-admin/includes/media.php, where the result is passed through size_format() so that 8388608 bytes becomes “8 MB”. The same value is handed to the uploader’s JavaScript as the max_file_size filter in wp_plupload_default_settings(), which is why the browser can refuse a file instantly, before any of it goes over the wire.
So the number is a measurement of your server, taken fresh on every page load. Editing WordPress to change a measurement is like repainting the thermometer. That is the whole reason the settings pages contain no upload size field: there would be nothing for it to write to.
The post_max_size mistake
This is the single most common way a correct-looking fix does nothing.
Someone sets upload_max_filesize = 64M and stops there. post_max_size is still at its default of 8M. Because core takes the minimum of the two, the media library still says 8 MB. Nothing improved, and the setting that was changed is now completely meaningless, because a 64 MB file can never arrive inside an 8 MB request.
The rule is simple: post_max_size must be larger than upload_max_filesize, not equal to it. The request carries the file plus everything else in the form, so give it real headroom. If you want a 64 MB file to upload, set post_max_size to 80M or 96M. Setting both to 64M works for a bare single file and then fails the moment the request carries anything extra, which is a horrible bug to chase because it looks intermittent.
There is a second, nastier failure mode. When a request body exceeds post_max_size, PHP does not raise a normal upload error. It discards the body and hands the script an empty $_POST and an empty $_FILES. WordPress then has no file and no error code to report, so you get a blank response, a spinner that never finishes, or the generic “HTTP error” in the uploader. That symptom is almost always post_max_size, and it is not what people go looking for, because the message says nothing about size.
The multisite exception
On a network install there really is a WordPress level limit, and it is enforced in PHP rather than merely displayed.
Multisite hooks upload_size_limit_filter() onto the upload_size_limit filter. That function reads the network option fileupload_maxk, which defaults to 1500, multiplies it by KB_IN_BYTES, and returns the minimum of the PHP derived value, that network cap and, unless the network has its space check disabled, the remaining space in the site’s storage quota. A default network therefore caps every upload at about 1.5 MB regardless of how generous the server is.
You change it in Network Admin under Settings, Network Settings, in the Upload Settings block. The field is labelled “Max upload file size” and it is in kilobytes, so 64 MB is 65536, not 64. The neighbouring “Site upload space” field controls the per site quota, and a site that is filling that quota will show a shrinking maximum upload size as the space runs out, which looks like a bug and is not.
None of this applies to a single site install. If you are not running multisite, there is no WordPress setting to find, and every tutorial that tells you to look for one is describing a different kind of install.
Seeing the real numbers instead of guessing
WordPress already shows you all of this. Go to Tools, Site Health, Info, and open the Media Handling section. Core lists the values under labels that are almost readable enough: “Max size of an uploaded file” is upload_max_filesize, “Max size of post data allowed” is post_max_size, and “Max effective file size” is the min() of the two, which is the number the media library prints. The Server section on the same page carries “PHP memory limit” and “PHP time limit”.
If you want the values in front of you while you test uploads, drop this into wp-content/mu-plugins/ as a file called show-upload-limits.php. Create the folder if it does not exist. Must use plugins load automatically and this one only reads and prints, so delete the file when you are done.
<?php
/**
* Plugin Name: Show upload limits
* Description: Prints the real PHP limits on the Media screen. Delete when done.
*/
add_action( 'admin_notices', function () {
$screen = get_current_screen();
if ( ! $screen || 'upload' !== $screen->id ) {
return;
}
printf(
'<div class="notice notice-info"><p>upload_max_filesize: %1$s, post_max_size: %2$s, effective: %3$s, memory_limit: %4$s, max_execution_time: %5$s</p></div>',
esc_html( ini_get( 'upload_max_filesize' ) ),
esc_html( ini_get( 'post_max_size' ) ),
esc_html( size_format( wp_max_upload_size() ) ),
esc_html( ini_get( 'memory_limit' ) ),
esc_html( ini_get( 'max_execution_time' ) )
);
} );
One warning about checking these on the command line. Running php -i over SSH, or reading the values through WP-CLI, tells you about the CLI configuration, which is frequently a different php.ini with far more generous limits. Uploads happen in the web SAPI. Trust the browser, not the shell.
Before you raise anything, it is worth knowing which of the three ceilings is the one stopping you, and whether the file will even survive the processing afterwards.
Drop the file below and enter the three values from Site Health. The planner tells you which limit fails first, works out how much memory the image will actually need from its pixel dimensions rather than its file size, and writes the exact lines for php.ini, .user.ini, .htaccess and wp-config.php, with an honest note about which of those your host will ignore.
Upload and memory limit planner
Four separate limits decide whether a file makes it into the media library: upload_max_filesize, post_max_size, memory_limit and max_execution_time. Give this planner the file you want to upload and the four values your server reports, and it works out which limit stops you first and writes the exact lines that fix it. A file you drop is only measured, its size and, for an image, its width and height. Nothing is uploaded and nothing leaves this browser tab.
or press Enter to pick one. Only the size is read, and the width and height when it is an image. The file itself stays on your disk.
All four values sit in Tools, Site Health, Info, under Server. The shorthand is the one PHP uses: 64M means 64 times 1024 KB, and -1 means no limit.
| Limit | This upload needs | The server allows | Verdict |
|---|
A JPEG does not take up its file size while it is being resized, it takes up its pixel area. GD holds the full bitmap at four bytes per pixel, and it holds the source and the result at the same time while it scales. ImageMagick counts differently and can stream parts of the work, so treat this figure as a rough floor rather than an exact demand.
A rough estimate at 0.35 seconds per megapixel and per generated size, plus two seconds for the request itself. A fast server beats it, a busy shared host does not. Some hosts also cut a request off at the web server or the proxy, which max_execution_time cannot do anything about.
Raise one limit at a time and check Site Health again after each change. If a value refuses to move, the host is setting it somewhere further up, and only support can change it there.
Raising the limits, ranked by what actually works
First, the hosting control panel. Almost every managed host exposes these values in a PHP settings screen: cPanel has MultiPHP INI Editor, Plesk has PHP Settings per domain, and most WordPress specific hosts have a plain form or a support macro. This is the correct answer most of the time. It writes to the right file, it survives PHP version upgrades, and it does not leave a stray directive that breaks the site two years later.
Second, php.ini or a user ini file. If you have real server access, edit the php.ini that the web SAPI loads, then restart PHP-FPM. Site Health will not tell you which file that is, so confirm the loaded path with a temporary phpinfo() page or your host’s documentation rather than assuming. If you do not have that access but PHP runs as FastCGI or FPM, you can usually place a .user.ini file in the WordPress root:
upload_max_filesize = 64M
post_max_size = 96M
memory_limit = 256M
max_execution_time = 300
Two things to know about .user.ini. PHP caches it, by default for 300 seconds, so a change may take five minutes to show up and refreshing harder will not help. And it only exists when PHP runs as CGI, FastCGI or FPM. Under mod_php the file is ignored silently, which makes it look like the values are wrong when they were never read.
Third, .htaccess, and only on Apache with mod_php. Above the # BEGIN WordPress block:
php_value upload_max_filesize 64M
php_value post_max_size 96M
php_value memory_limit 256M
php_value max_execution_time 300
If PHP is running through FPM, which is the normal modern arrangement, php_value is not a directive Apache understands and the whole site returns a 500 Internal Server Error. On nginx there is no .htaccess at all and the file is simply never read. Try this one last, keep a way to undo it, and check the front end immediately.
The snippets that circulate and do nothing
Search this problem and you will be handed the same three or four pieces of code. Here is why they fail, because the reason matters more than the list.
@ini_set( 'upload_max_filesize', '64M' ); in wp-config.php or in a theme’s functions.php cannot work, and not because of where it is placed. Both upload_max_filesize and post_max_size are marked PHP_INI_PERDIR, which means they can only be set in php.ini, .htaccess, .user.ini or the web server configuration. A running script cannot change them. The leading @ suppresses the warning that would have told you so. This is the purest cargo cult in the genre: it looks like configuration, it is syntactically valid, and it has no effect whatsoever. If you are wondering where such code would belong if it did work, the answer is still not functions.php, but in this case there is no correct location, because the mechanism does not exist.
Filtering upload_size_limit to return a bigger number is worse than useless, because it appears to work. The media library dutifully prints your new figure and the JavaScript uploader stops blocking large files, so you get further than before. Then PHP throws the file away at the door exactly as it did previously, and now you have a screen that lies to everyone who uses it. On single sites, leave that filter alone.
Setting define( 'WP_MEMORY_LIMIT', '256M' ); is real code that really works, but it has nothing to do with upload size. It raises how much memory WordPress will ask PHP for, which matters after the upload succeeds, not during it. People add it while chasing an upload limit, see nothing change, and conclude the fix was wrong when it was simply aimed elsewhere.
Finally, a plugin that “increases the maximum upload size” is usually doing one of the above on your behalf, most often the upload_size_limit filter. A few genuinely attempt to write a .user.ini or .htaccess for you, which is a real mechanism, but you can write those two files yourself in less time than the plugin takes to install.
What breaks once the big file gets in
Raising the ceiling only moves the failure. A 40 MB JPEG that uploads successfully now has to be processed, and that is where the next two limits bite.
memory_limit is the one that kills large photographs. GD decompresses an image into raw pixels before it can touch it, and the file size on disk tells you almost nothing about how much RAM that takes. An 8000 by 6000 pixel photograph is 48 million pixels, and at four bytes per pixel that is roughly 190 MB of raw bitmap for the source alone, before the resized copy exists alongside it. This is why the upload bar reaches 100 percent and then the screen returns a blank error: the transfer worked and the resize ran out of memory. There are three separate memory ceilings in a WordPress install, and it is worth knowing which one applies during a media upload.
max_execution_time, commonly 30 seconds, is the other one. WordPress does not make one copy of your upload. It makes every registered image size, which on a typical theme is several files, plus the -scaled version core creates automatically once an image exceeds the big_image_size_threshold of 2560 pixels on its longest side. One upload becomes a lot of files, and each one is a separate encode. Big source images can genuinely exceed half a minute of processing.
Raise both alongside the upload limits, in the same place, at the same time. A 64 MB upload limit sitting next to a 128 MB memory limit is a trap you built yourself.
The 12 MB photograph is the real problem
Everything above assumes you are right to want the bigger limit. Sometimes you are. Video, PDFs, a theme zip, a client’s press pack: those need headroom, and the fix is a genuinely higher ceiling.
For photographs, it is nearly always the wrong fix. A 12 MB phone photo is 12 MB because it is several thousand pixels wide and encoded at near maximum quality for editing, not for the web. Your content area is perhaps 800 pixels wide, and on a high density screen you serve maybe 1600. Everything above that is thrown away by the browser after being decoded. Core’s -scaled behaviour already caps the version it serves at 2560 pixels, so the original is stored, backed up, migrated and never delivered to a single visitor.
Resize before uploading, to something like 2000 or 2560 pixels on the long edge, and re-encode at a sane quality. The same picture typically lands in the hundreds of kilobytes, uploads instantly under an 8 MB limit, never troubles memory_limit, and looks identical on the page. Choosing the right format compounds that: WebP at quality 80 will usually beat a JPEG of matching visual quality by a comfortable margin, and WordPress has supported WebP in core since 5.8.
If the oversized originals are already sitting in your media library, resizing them one at a time is not realistic, which is exactly the case WunderPaint’s batch processor exists for.
Symptom and cause
The number in the media library never changes, whatever you edit in WordPress. It is min( upload_max_filesize, post_max_size ) read from PHP on every page load. WordPress has no stored value to update. Change PHP, not WordPress.
You raised upload_max_filesize to 64M and the number still says 8 MB. post_max_size is still 8M and core takes the smaller of the two. Raise post_max_size above it, with headroom.
The uploader shows a blank or generic “HTTP error” with no size mentioned. The request body exceeded post_max_size, so PHP discarded it and handed WordPress an empty $_FILES. There is no error for core to report, which is why the message is unhelpful.
The progress bar reaches 100 percent, then it fails. The transfer finished and the processing did not. That is memory_limit during the resize, or max_execution_time while generating every registered size.
Uploads fail at roughly 1 MB no matter what PHP says. Nginx’s client_max_body_size is still at its 1 MB default and returns a 413 before PHP runs. It has to be fixed in the server configuration.
The maximum is about 1.5 MB and this is a network install. That is the fileupload_maxk network option at its default of 1500 kilobytes. Network Admin, Settings, Upload Settings.
The limit dropped on its own, on multisite. The site is near its upload space quota, and core reports the remaining space as the maximum upload size.
Adding php_value lines to .htaccess took the whole site down. PHP is running as FPM or CGI, where Apache does not recognise php_value. Remove the lines and use .user.ini instead.
What to take away
The reason this problem is so frustrating is that it presents itself inside WordPress, in the WordPress admin, in WordPress’s own wording, while being entirely a property of the server underneath. Four lines of core, in place since WordPress 2.5, read two PHP directives and print the smaller one. Every hour spent editing WordPress files is spent on the wrong side of that boundary.
Once you accept that, the procedure is short. Read the real values in Site Health. Raise both directives together in the control panel if you have one, in php.ini or .user.ini if you do not, and treat .htaccess as a last resort you can undo quickly. Raise memory_limit and max_execution_time in the same edit, because a file that gets in and then dies during resizing is not progress. Then check the media library again, because that number is the only honest confirmation that any of it worked.
And before you raise anything, look at what you are uploading. If it is a photograph, the limit is not the problem. A 12 MB image on a web page is a mistake whether or not it fits through the door, and the version your visitors receive was going to be a fraction of that size anyway. The best outcome of this whole investigation is usually that you stop needing a bigger limit.