We may earn a referral fee at no additional cost to you when you shop through the links on this page.
Upload a large image or a plugin ZIP and PHP throws it back: The uploaded file exceeds the upload_max_filesize directive in php.ini. PHP’s default ceiling is a stingy 2MB, and three separate layers can each block a bigger file on its own.
Quick Fix
Raise three directives in the php.ini your site actually loads, then restart that service. Which file that is depends on how PHP runs, and the two are not interchangeable:
- Apache with
mod_php:/etc/php/8.3/apache2/php.ini, then restartapache2. - Nginx, or Apache with PHP-FPM:
/etc/php/8.3/fpm/php.ini, then restartphp8.3-fpm. - No shell access at all: skip to No Shell Access, where the file you edit lives in your web root instead.
Substitute your own PHP version for 8.3. The three values are the same either way:
upload_max_filesize = 64M
post_max_size = 66M
memory_limit = 256M
Then restart the service that serves your site, and confirm the new value in a browser rather than a terminal, for the reason covered in Verify the Change.
sudo systemctl restart apache2 # Apache with mod_php
sudo systemctl restart php8.3-fpm # Nginx, or Apache with PHP-FPM
Before You Start
This guide has two paths and you only need the one that matches your access:
- With a shell.
sudoon the server, and PHP already serving pages behind Apache or Nginx. Starting from a bare server, build it first with the LAMP stack guide or LEMP stack guide. You editphp.inidirectly. - Without one. Shared or managed hosting, where
php.iniis not yours to edit. You need write access to your web root through SFTP, a file manager, or the hosting panel. - Either way, knowing whether your site runs PHP-FPM or Apache
mod_php, since it decides which file applies.
With a shell, ask systemd for a running FPM service. A line naming a php<version>-fpm.service means PHP-FPM. No output is a hint rather than proof, since a stopped pool or one running on another host also prints nothing:
systemctl list-units --type=service --state=running --no-legend 'php*-fpm.service'
Without a shell, the answer is on the phpinfo() page described in Verify the Change: the Server API row reads FPM/FastCGI (or CGI/FastCGI on plain CGI hosts, which take .user.ini the same way) or Apache 2.0 Handler. That row is the authority on both paths, so it is worth checking even when you do have a shell.
No Shell Access? Start Here
On shared or managed hosting the main php.ini belongs to the host, but PHP still reads per-directory overrides. Which one works depends on the same SAPI split as everything else, and picking the wrong one is worse than doing nothing:
.user.iniin your web root. The route for PHP-FPM and CGI, which covers most modern shared hosting. This is a plain PHP mechanism, not a WordPress one, so it works for any PHP application.php_valuein.htaccess. Only when PHP runs as an Apache module. Withoutmod_phploaded Apache does not recognise the directive at all, so on a PHP-FPM server the usual result is a500 Internal Server Erroron every page.
For the .user.ini route, create the file beside your site’s index file and add the same three values:
upload_max_filesize = 64M
post_max_size = 66M
memory_limit = 256M
A .user.ini edit is not instant. PHP caches these files for user_ini.cache_ttl, which defaults to 300 seconds, so give it five minutes before deciding it failed.
For the .htaccess route on Apache with mod_php, add the same three values as php_value lines in the .htaccess in your web root, creating the file if it does not exist:
php_value upload_max_filesize 64M
php_value post_max_size 66M
php_value memory_limit 256M
If neither file takes effect, your host has locked the setting. Most control panels expose it directly, usually under a PHP configuration or PHP options screen, so check there before opening a ticket. Some hosts genuinely cap it and will not raise it, which is the one case in this guide with no fix from your side: managed hosting with root-level PHP control buys you the setting directly instead of a support ticket.
Understand the Three Size Limits
Bumping one number rarely fixes the problem, because PHP checks a chain of limits and the smallest one wins. Three directives do the work:
upload_max_filesize: the maximum size of a single uploaded file. Default: 2M.post_max_size: the maximum size of the entire POST body. Default: 8M. It must be larger thanupload_max_filesize, since the request carries the file plus the rest of the form.memory_limit: the maximum memory a script may use. It should be at least as large aspost_max_size.
So the safe ordering is memory_limit >= post_max_size > upload_max_filesize. Because a POST request carries the uploaded file plus the rest of the form data, post_max_size needs headroom above the file size alone, not just an equal value. A fourth directive, max_file_uploads, caps how many files one request accepts (default 20); past that, $_FILES quietly stops adding them.
post_max_size, PHP empties $_POST and $_FILES completely. Your script then sees no file and no obvious error, just blank data, which is why this is the most confusing upload failure to debug. Edit the Right php.ini
On Debian and Ubuntu, the PHP packages install a separate configuration directory for every SAPI under /etc/php/<version>/. The command line, PHP-FPM and Apache’s module each read their own copy, and editing one has no effect on the others. That single fact causes more failed upload-limit changes than any other:
| Your setup | Edit this file | Then restart |
|---|---|---|
| Nginx with PHP-FPM | /etc/php/8.3/fpm/php.ini | php8.3-fpm |
| Apache with PHP-FPM | /etc/php/8.3/fpm/php.ini | php8.3-fpm |
| Apache with mod_php | /etc/php/8.3/apache2/php.ini | apache2 |
| Scripts run from a shell | /etc/php/8.3/cli/php.ini | nothing |
The layout differs on RHEL-family systems, where the service names are php-fpm and httpd rather than php8.3-fpm and apache2. For that case, or any non-standard install, our guide to finding and editing php.ini walks through locating the active file, and the PHP-FPM configuration file locations covers the FPM side in more detail.
Set all three values in whichever file the table pointed you at:
sudo nano /etc/php/8.3/fpm/php.ini
upload_max_filesize = 64M
post_max_size = 66M
memory_limit = 256M
Save and exit (press CTRL + X, press Y, then press ENTER)
post_max_size sits just above upload_max_filesize to leave room for the rest of the form, and memory_limit clears both. Note that upload_max_filesize and post_max_size are PHP_INI_PERDIR settings, so you cannot change them at runtime with ini_set(); they have to live in php.ini, .htaccess, httpd.conf or a .user.ini file.
Pick numbers that match what you actually upload: 64M handles large images and most plugin ZIPs comfortably, while a site accepting video might want 256M or more. There is no benefit to setting them needlessly high, since a single oversized request is then allowed to consume that much server memory.
The edit does nothing until PHP re-reads its configuration. Under PHP-FPM that means restarting the service, and the service name carries your PHP version, so list it if you are unsure. Our guide to restarting PHP-FPM and reloading its config covers the reload-versus-restart difference:
sudo systemctl restart php8.3-fpm # PHP-FPM, Debian/Ubuntu (RHEL: php-fpm)
# Or, with mod_php, restart Apache instead:
sudo systemctl restart apache2 # Debian/Ubuntu (RHEL: httpd)
Verify the Change (Your Terminal Will Lie)
This is where the change usually looks like it failed. The obvious move is to check from the shell, but php -r and php -i are CLI-only options that report the command line’s own configuration. They read /etc/php/8.3/cli/php.ini, which you did not edit, so they keep printing the old value no matter how correct your FPM change was.
Check what your site sees instead. Write a one-line PHP file into your web root. /var/www/html is the default document root on Debian and Ubuntu; substitute your own if you changed it:
sudo nano /var/www/html/info.php # use your site's document root if it differs
<?php
phpinfo();
Save and exit (press CTRL + X, press Y, then press ENTER)
Load it in a browser, then read two rows near the top: Loaded Configuration File confirms which php.ini the web server actually used, and upload_max_filesize shows the value now in force. If you edited php.ini directly and the loaded file is not the one you edited, that mismatch is your whole problem. If you used .user.ini or .htaccess, this row will still name the host’s main php.ini, and that is expected: those files are read in addition to it, never instead of it, so success shows in the upload_max_filesize value rather than in the path.
info.php as soon as you have read it. A phpinfo() page exposes your full PHP configuration, extension list, absolute paths and environment variables to anyone who finds the URL. To see the split for yourself, run the CLI check on the same server immediately after a successful FPM change. It reports the command line’s untouched default, not the 64M the browser just showed:
php -r "echo ini_get('upload_max_filesize'), PHP_EOL;"
2M
There is a terminal route to the web value too. php-fpm8.3 -i runs the FPM binary’s own info dump, so it reads /etc/php/8.3/fpm/php.ini rather than the CLI file. It lives in /usr/sbin, which a Debian user’s PATH does not include, so run it with sudo:
sudo php-fpm8.3 -i | grep upload_max_filesize
phpinfo() page fetched over HTTP answers what the running workers actually loaded, which is the question when you are checking whether a restart took effect. Not every distribution splits the file this way, and where a single php.ini serves every SAPI the two answers agree. That is exactly why the Loaded Configuration File row is the check rather than a guess: it names the file your setup actually used. On the per-SAPI layout above you get two different answers on one machine, both correct for the SAPI that produced them. If you want the command line raised as well, for a CLI import script or a WP-CLI media command, edit the cli/php.ini too. Do not edit it expecting the website to change. You can see which file the shell is reading with php --ini.
Raise the Web Server Limit (Apache and Nginx)
PHP is only half the story. The web server in front of it enforces its own request-body cap, and if that cap is smaller than your upload, the request is rejected before PHP ever sees it, almost always as a 413 Request Entity Too Large.
Nginx limits the body to 1MB by default through client_max_body_size. Raise it to at least your post_max_size in the http, server, or location context, then reload nginx:
client_max_body_size 66M;
Test the config and reload nginx so the change applies without dropping connections. If the 413 survives the change, our guide to fixing the Nginx 413 error covers the contexts people most often miss:
sudo nginx -t && sudo systemctl reload nginx
Apache caps the body with LimitRequestBody. From Apache 2.4.54 onward the default is 1073741824 bytes, which is 1 GB and generous enough that it rarely blocks anything; on 2.4.53 and earlier the default was 0, meaning unlimited. Either way a host may have set it lower, so check before ruling it out. To raise or confirm it, the directive takes a byte count and is valid in the server config, a virtual host, a directory block or .htaccess: set LimitRequestBody 69206016 (66M) in your vhost. Run sudo apachectl configtest before restarting, the same habit as nginx -t: a syntax error in a vhost leaves Apache refusing to start rather than merely unchanged.
Increase the Upload Limit in WordPress
On a single site, WordPress imposes no ceiling of its own. The Maximum upload file size figure on the media screen comes from PHP, and specifically from the lower of upload_max_filesize and post_max_size. Raising only one of them leaves the figure pinned to the other, which is why a change sometimes appears to do nothing. Multisite is the exception: a network imposes its own upload-size cap on top of the PHP values, so on a network install check the network settings as well or the PHP change alone will not lift the limit.
Without shell access the fix is the same one described in No Shell Access. To confirm what WordPress currently sees without touching a terminal, check Tools → Site Health → Info → Media Handling.
Still Hitting the Limit?
- The number never changes. You almost certainly edited a different SAPI’s
php.ini. Load thephpinfo()page and compare Loaded Configuration File against the path you opened in your editor. - The file is right but nothing applied. Most often PHP-FPM was not restarted, since
php.iniis read only at startup. If it was, look for a per-poolphp_admin_valueinpool.d/*.conf, or a.user.inihigher up the tree, overriding you. - 413 Request Entity Too Large. The web server rejected the body before PHP saw it. Raise
client_max_body_sizeon Nginx, and check any reverse proxy or CDN in front of the origin, which enforces its own request-size cap independently of your server. - Large files fail, small ones succeed, and the failure takes a while. This is a timeout, not a size cap.
max_input_timelimits the seconds PHP spends receiving and parsing POST data, uploads included, so a slow connection can exceed it while every size limit is still comfortable. - No error at all, just an empty form. The POST body exceeded
post_max_size, so PHP emptied$_POSTand$_FILES. Turn on error display while you debug, using our guide to enabling PHP error reporting, and turn it back off afterwards: on a public site it leaks absolute paths and stack traces to anyone who triggers an error. - WordPress still shows an HTTP error on upload. Once the limits are right, the remaining causes are usually memory or the image library, covered in fixing the WordPress HTTP error on image upload.
One case has no fix from your side. On shared hosting you may now know the exact directive to change and still have no way to change it, because the host owns php.ini and ignores .user.ini. WordPress’s own support guidance for that situation is to ask the host, and to move if they refuse. If you reach that point, managed hosting with root-level PHP control buys you the setting directly instead of a support ticket.
Summary
Raise upload_max_filesize, post_max_size and memory_limit together, in the php.ini for the SAPI that serves your site if you have a shell, or in .user.ini, .htaccess or your hosting panel if you do not, restart the service where one applies, and confirm the result through a phpinfo() page rather than the command line. If the file still bounces after that, work outwards: the web server’s own body cap, then any proxy or CDN in front of it, then the application, and finally PHP’s timeouts rather than its sizes.
thank u sm, I was missing the restarting part.
Apache LimitRequestBody directive is equally as important, if already set to a value
This is a really fantastic explanation. Many thanks!
you are the man. helped me so much. Thank you so much
thanks so much ..you made may day to be good for this helpness
You are excellent. Top quality work.
Thanks!!!!
Works like magic
🙂
Thanks For Sharing