Previous Article
WooCommerce Shortcodes: Complete Reference (and What Blocks Replaced)
3 May 2018
"Increase the upload limit" sounds like a one-line change and frequently takes an afternoon, because the setting lives in a different place depending on how PHP is running, and several directives have to agree before anything works.
This guide covers which method applies to your setup, and why the advice you have probably already tried did nothing.
This snippet appears in countless articles, including the earlier version of this one:
// Does nothing. At all. @ini_set( 'upload_max_size' , '64M' ); @ini_set( 'post_max_size', '64M' );
Two separate bugs:
upload_max_size. It is upload_max_filesize. The line sets a nonexistent setting and returns silently.PHP_INI_PERDIR. That means they can only be set in php.ini, .htaccess, or the server config — never at runtime. By the time your PHP script executes, the upload has already been received or rejected. Changing the limit mid-request is too late by definition.The @ suppresses the warning that would have told you. Remove error suppression and you would have seen the problem immediately.
This determines which method works. Create a file and load it in a browser:
<?php phpinfo();
Look at Server API near the top:
| Server API | Use | .htaccess works? |
|---|---|---|
| Apache 2.0 Handler | php.ini or .htaccess | Yes |
| FPM/FastCGI | php.ini or pool config | No — causes a 500 error |
| CGI/FastCGI | php.ini in the directory | No |
| LiteSpeed | php.ini or .htaccess | Usually |
Also note Loaded Configuration File — that is the php.ini actually in use. Editing a different one, which is easy to do when several exist, is the most common reason a change appears to have no effect. Delete the file when you are finished; phpinfo() exposes a great deal about your server.
From the command line:
php -i | grep -E "Loaded Configuration|upload_max_filesize|post_max_size"
Note that CLI often uses a different php.ini from the web server, so the command line reporting 64M tells you nothing about what your site is doing.
Raising only upload_max_filesize is the second most common mistake. Five settings interact:
; The largest single uploaded file. upload_max_filesize = 64M ; The largest total POST body. MUST exceed upload_max_filesize — ; it has to hold the file plus every other form field plus encoding overhead. post_max_size = 68M ; Memory available to the script. memory_limit = 128M ; Seconds the script may run. Large uploads on slow connections need headroom. max_execution_time = 300 ; Seconds allowed to PARSE input, including receiving the upload. ; This is the one people forget — the upload can time out here ; even with a generous max_execution_time. max_input_time = 300 ; Only if you accept several files at once. max_file_uploads = 20
The ordering rule: memory_limit ≥ post_max_size > upload_max_filesize.
If post_max_size is smaller than upload_max_filesize, PHP discards the request body and $_POST and $_FILES arrive completely empty — with no error. A form that silently submits nothing is almost always this.
Edit the file named in Loaded Configuration File, set the values above, and restart:
# mod_php sudo systemctl restart apache2 # PHP-FPM — restart the FPM service, not just the web server sudo systemctl restart php8.3-fpm
Forgetting to restart FPM is a classic. The config is read once at startup, so your edit sits there having no effect until the process reloads.
php_value upload_max_filesize 64M php_value post_max_size 68M php_value max_execution_time 300 php_value max_input_time 300
Under PHP-FPM this produces a 500 Internal Server Error — Apache does not recognise php_value when PHP is not loaded as a module. If your site breaks the moment you save this file, that is your answer: you are on FPM, use php.ini instead.
Wrap it defensively if you are unsure:
<IfModule mod_php.c> php_value upload_max_filesize 64M php_value post_max_size 68M </IfModule>
On shared hosting running PHP as CGI, a php.ini placed in your web root applies to that directory:
upload_max_filesize = 64M post_max_size = 68M memory_limit = 128M
Two caveats. It usually does not cascade into subdirectories — some hosts need a copy in each, or a suPHP_ConfigPath directive in .htaccess. And it does nothing at all under mod_php.
The correct place on a modern VPS. Edit your pool file, typically /etc/php/8.3/fpm/pool.d/www.conf:
php_admin_value[upload_max_filesize] = 64M php_admin_value[post_max_size] = 68M php_admin_value[memory_limit] = 128M
php_admin_value cannot be overridden by ini_set() at runtime; plain php_value can. Use php_admin_value for limits you want enforced.
If you run nginx, PHP is not your only limit. nginx rejects large bodies before PHP ever sees them, and the default is a mere 1 MB:
http {
client_max_body_size 64M;
client_body_timeout 300s;
}
The symptom is distinctive: 413 Request Entity Too Large, served by nginx, with nothing in your PHP logs. If you have raised every PHP directive and still cannot upload, this is almost certainly why.
sudo nginx -t && sudo systemctl reload nginx
Cloudflare imposes its own cap too — 100 MB on the free plan — and returns a 413 of its own regardless of your server settings.
<?php
printf(
"upload_max_filesize: %s\npost_max_size: %s\nmemory_limit: %s\nmax_execution_time: %s\n",
ini_get('upload_max_filesize'),
ini_get('post_max_size'),
ini_get('memory_limit'),
ini_get('max_execution_time')
);
A helper that reports the real ceiling — the smaller of the two limits — is worth having, since that is what users actually experience:
function maxUploadBytes(): int
{
$toBytes = static function (string $value): int {
$value = trim($value);
if ($value === '' || $value === '-1') return PHP_INT_MAX;
$unit = strtolower($value[strlen($value) - 1]);
$num = (int) $value;
return match ($unit) {
'g' => $num * 1024 ** 3,
'm' => $num * 1024 ** 2,
'k' => $num * 1024,
default => $num,
};
};
return min(
$toBytes(ini_get('upload_max_filesize')),
$toBytes(ini_get('post_max_size'))
);
}
When an upload exceeds post_max_size, PHP empties $_POST and $_FILES entirely — so your usual "no file submitted" branch fires and gives the user a misleading message. Detect it explicitly:
if ($_SERVER['REQUEST_METHOD'] === 'POST'
&& empty($_POST)
&& empty($_FILES)
&& (int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
$limit = ini_get('post_max_size');
exit("Your upload was too large. The maximum is {$limit}.");
}
// And check the per-file error code.
match ($_FILES['doc']['error'] ?? UPLOAD_ERR_NO_FILE) {
UPLOAD_ERR_OK => handleUpload($_FILES['doc']),
UPLOAD_ERR_INI_SIZE => fail('File exceeds the server limit.'),
UPLOAD_ERR_FORM_SIZE => fail('File exceeds the form limit.'),
UPLOAD_ERR_PARTIAL => fail('Upload was interrupted. Please retry.'),
UPLOAD_ERR_NO_FILE => fail('No file was selected.'),
UPLOAD_ERR_NO_TMP_DIR,
UPLOAD_ERR_CANT_WRITE => fail('Server storage error. Contact support.'),
default => fail('Upload failed.'),
};
Tell the user the limit in the interface as well, so they find out before spending five minutes uploading.
A large post_max_size means any visitor can make your server buffer that much data per request — a cheap way to exhaust resources. For genuinely large files, consider chunked uploads (splitting client-side and reassembling) or presigned direct-to-storage uploads to S3 or similar, which bypass your server entirely.
php.ini?post_max_size larger than upload_max_filesize?memory_limit at least as large as post_max_size?client_max_body_size?max_input_time long enough for a slow connection?Advertisement
Written by
Amit VermaFounder / Senior Software Engineer
Amit leads engineering at Being Idea. With 15+ years building scalable software products across global markets, he drives architecture decisions and engineering culture across every engagement.
More articles by AmitYou might also like
We ship software that scales. Let's work together.