Skip to content
  • FG-63
  • 14 min read
  • Last checked November 8, 2026

How to Fix “The uploaded file exceeds the upload_max_filesize directive” and “413 Request Entity Too Large” in WordPress

Uploads fail with upload_max_filesize, “exceeds the maximum upload size” or 413 errors. Find which layer set the limit (PHP, web server, WAF or CDN) and raise the right one.

Run the health check

You try to upload an image, a video, a theme zip or a backup file and WordPress stops you with one of these messages: “The uploaded file exceeds the upload_max_filesize directive in php.ini.”, “FILENAME exceeds the maximum upload size for this site.”, “The link you followed has expired.”, or simply “HTTP error.” Sometimes the browser shows a plain white page that says “413 Request Entity Too Large”, with “nginx” or “cloudflare” printed underneath.

These all mean the same thing: something between your browser and WordPress refused a request body that was too big. The trick is that there are up to six separate limits in that chain, set in different places, and raising the wrong one changes nothing. This guide is for site owners, designers and developers who have already tried “just raise upload_max_filesize” and found that it did not help.

What is happening

When you upload a file, your browser sends it as the body of a POST request. That body passes through each layer below, in order. Any layer can refuse it, and each refuses it in its own way.

Layer Setting Common default What you see when it is exceeded
Cloudflare (if proxied) Maximum upload size per plan 100 MB on Free and Pro, 200 MB on Business, up to 5 GB on Enterprise 413 page branded by Cloudflare
Nginx client_max_body_size 1m (1 megabyte) “413 Request Entity Too Large” with “nginx” underneath
Apache LimitRequestBody 1 GiB on Apache 2.4.54 and later “Request Entity Too Large” page from Apache
LiteSpeed Max Request Body Size Set by the host 413 or a dropped upload
ModSecurity WAF SecRequestBodyLimit 13107200 bytes (12.5 MB) in the recommended config 413 or 403, logged in the ModSecurity audit log
PHP post_max_size 8M Empty request; WordPress says “The link you followed has expired.” or the uploader shows “HTTP error.”
PHP upload_max_filesize 2M “The uploaded file exceeds the upload_max_filesize directive in php.ini.”
WordPress wp_max_upload_size() The smaller of the two PHP values above “FILENAME exceeds the maximum upload size for this site.”

A few relationships matter more than the numbers:

  • post_max_size caps the whole request (file plus form fields), so it must be at least as big as upload_max_filesize, and a little bigger is safer. When the request is bigger than post_max_size, PHP throws the whole body away. WordPress then sees no file and no security nonce, which is why the theme and plugin uploaders say “The link you followed has expired.” instead of anything about size. That message has other causes too, such as expired nonces and cached admin pages, which our separate guide to it covers.
  • WordPress reads both PHP values and shows the smaller one as “Maximum upload file size” on Media > Add New. Its block editor and media library check the file size in the browser before sending, which produces the “exceeds the maximum upload size for this site” message.
  • On multisite, a network setting caps uploads too: Network Admin > Settings > Max upload file size, in kilobytes (1500 KB by default).
  • memory_limit, max_execution_time and max_input_time do not cap the file size directly, but a big upload can still fail on them: max_input_time limits how long PHP spends receiving the request, and generating image sizes for a huge photo needs memory and time after the upload succeeds.

Before you start

You need wp-admin access and either your hosting panel or SSH. Changes to PHP settings are low risk, but keep a copy of any file you edit (.user.ini, .htaccess, php.ini or a server config file) so you can put it back.

How to run the commands in this guide. Server commands run in an SSH terminal from your site folder, the one that contains wp-config.php (often public_html). WP-CLI is called wp on most servers; some hosts name it wp-cli, so swap the name if wp is not found. The curl tests run on your own computer: macOS and Linux have curl built in, and Windows 10 and 11 include curl.exe in PowerShell (type curl.exe, not curl), or you can use WSL.

Step 1: Read the error to find the layer

The message itself tells you where to look:

  • A WordPress message (styled like wp-admin, or a red notice in the media library) means the request reached PHP. Go to Step 2.
  • A bare 413 page means a layer in front of PHP refused it. Open your browser’s developer tools (F12), go to the Network tab, retry the upload, click the failed request and look at the server response header. cloudflare means Step 5. nginx, Apache or LiteSpeed means Step 4.
  • “HTTP error.” in the media uploader is vague. It can be post_max_size, a 413 from the web server, a timeout, or a memory error while WordPress generates image sizes. Check the Network tab for the status code, then the PHP error log.

Step 2: Check the effective PHP values

“Effective” matters. The value in a file you edited is not necessarily the value PHP is using.

Site Health (the most reliable from wp-admin)

Go to Tools > Site Health > Info and open Server. You will see “Upload max filesize”, “Max size of post data”, “Max effective file size”, “PHP memory limit”, “PHP time limit” and “Max input time”. These come from the PHP process that serves your website, so they are the real numbers. Good: both size limits comfortably above your file. Bad: “Max effective file size” still at 2 MB after you edited a file, which means your edit is not being read.

WP-CLI, with a caution

wp eval 'echo ini_get("upload_max_filesize"), " / ", ini_get("post_max_size"), " / ", size_format( wp_max_upload_size() ), "\n";'
php --ini

Run these over SSH from the site folder. There are no placeholders. The first prints upload_max_filesize, post_max_size and WordPress’s computed maximum. The second lists which php.ini and extra .ini files the command line PHP loaded.

The caution: WP-CLI runs under command line PHP, which reads its own configuration and ignores .user.ini and .htaccess. Output like 512M / 512M / 512 MB from WP-CLI while Site Health says 2 MB is normal and tells you nothing about the website. Use WP-CLI to check the command line, and Site Health or phpinfo to check the website.

A temporary phpinfo page

<?php phpinfo( INFO_CONFIGURATION );

Save this as a file with a random name, such as RANDOM_NAME.php, in your site folder (it is a file you create, not a command), then open https://example.com/RANDOM_NAME.php. Replace example.com with your domain. INFO_CONFIGURATION limits the output to configuration directives. Search the page for upload_max_filesize: the “Local Value” column is what this site uses, and the “Master Value” column is the server default. Near the top of a full phpinfo page, “Loaded Configuration File” and “Additional .ini files parsed” show which files were read, and “Server API” tells you the handler (FPM/FastCGI, Apache 2.0 Handler or LiteSpeed). Delete the file as soon as you are done, because it reveals server details.

Step 3: Raise the PHP limits in the right place

Where you set PHP values depends on how PHP runs. Setting them in the wrong place does nothing at best and causes a 500 error at worst.

Method Works when Notes
Hosting panel PHP settings Almost always on shared hosting Examples: cPanel MultiPHP INI Editor, or Select PHP Version > Options on CloudLinux; Plesk’s PHP settings for the domain. Preferred, because the panel writes the right file for your handler.
.user.ini in the site folder PHP runs as CGI or FastCGI, including PHP-FPM Read per directory. Changes can take up to user_ini.cache_ttl seconds to apply (300 by default).
php_value in .htaccess Apache with mod_php, or LiteSpeed Enterprise On PHP-FPM behind Apache this causes a 500 error (“Invalid command ‘php_value'”). Nginx ignores .htaccess completely.
php.ini or a PHP-FPM pool file You manage the server (VPS) Restart or reload PHP-FPM afterward.
ini_set() in wp-config.php Never for these two settings upload_max_filesize and post_max_size are per-directory settings that cannot be changed at runtime. WP_MEMORY_LIMIT only affects memory.

.user.ini on PHP-FPM

upload_max_filesize = UPLOAD_SIZE
post_max_size = POST_SIZE
memory_limit = MEMORY_SIZE
max_execution_time = SECONDS
max_input_time = SECONDS

This is a file you create or edit, named .user.ini, in the folder that contains wp-config.php. Replace UPLOAD_SIZE with the largest file you need plus some margin, written with an M suffix (for example 64M). Make POST_SIZE a little larger (for example 72M). MEMORY_SIZE should be at least 256M if you upload large images, since resizing needs memory. SECONDS around 120 to 300 gives slow connections time to finish. Wait five minutes (the cache TTL), then recheck Site Health. Good: the new values appear. Bad: nothing changes, which usually means PHP is not running as FastCGI or the host disables .user.ini; use the panel instead.

.htaccess on Apache with mod_php or LiteSpeed

php_value upload_max_filesize UPLOAD_SIZE
php_value post_max_size POST_SIZE
php_value memory_limit MEMORY_SIZE
php_value max_execution_time SECONDS
php_value max_input_time SECONDS

Add these lines to the .htaccess file in your site folder, outside the # BEGIN WordPress block. The placeholders mean the same as above. Good: the site still loads and Site Health shows the new values. Bad: an immediate 500 error means your PHP does not run as an Apache module; remove the lines straight away and use .user.ini or the panel.

php.ini on your own server

php --ini
sudo systemctl reload PHP_FPM_SERVICE

Run these over SSH on a server you manage. The first shows where the CLI’s php.ini lives; the FPM one is usually in a parallel folder (for example /etc/php/VERSION/fpm/php.ini on Debian and Ubuntu, where VERSION is like 8.3). Set the same five directives there, or in a pool file with php_admin_value[upload_max_filesize] = UPLOAD_SIZE. PHP_FPM_SERVICE is the service name, such as php8.3-fpm; list them with systemctl list-units | grep fpm. A reload applies the change without dropping requests. Good: no output, and Site Health shows the new values.

Step 4: Raise the web server and WAF limits

Nginx: client_max_body_size

Confirm it in the Nginx error log (often /var/log/nginx/error.log, or the panel’s error log view). The tell-tale line is:

client intended to send too large body: BODY_BYTES bytes, client: CLIENT_IP, server: example.com, request: "POST /wp-admin/async-upload.php HTTP/2.0"

This is example log output. BODY_BYTES is the size of the rejected request and CLIENT_IP is the uploader’s address. If you see it, raise the limit:

client_max_body_size SIZE;
sudo nginx -t && sudo systemctl reload nginx

The first line goes in a file you edit: the server { } block for your site, or the http { } block to apply it to all sites. SIZE uses Nginx units, for example 64m; match it to your PHP post_max_size. The second line runs over SSH: nginx -t checks the configuration and reload applies it only if the check passed. Good output is “syntax is ok” and “test is successful”. On shared hosting you cannot edit this; open a ticket. If your server runs Nginx in front of Apache, both layers have their own limit.

Apache: LimitRequestBody

Since Apache 2.4.54, LimitRequestBody defaults to 1 GiB, so it rarely bites on its own, but hosts sometimes set it lower. It can be set in the server or virtual host configuration and, where overrides are allowed, in .htaccess, as LimitRequestBody BYTES (a number of bytes, where 0 means unlimited). The Apache error log names the configured limit when it refuses a request.

LiteSpeed: Max Request Body Size

On LiteSpeed and OpenLiteSpeed, the request body ceiling is the “Max Request Body Size” setting under Server > Tuning in the WebAdmin console (also settable per virtual host). On shared LiteSpeed hosting, this is a host setting; open a ticket if it is lower than your PHP limit.

ModSecurity: SecRequestBodyLimit

If your server runs ModSecurity, its SecRequestBodyLimit applies to every request body. The recommended configuration sets it to about 12.5 MB, which is below many PHP limits. Look in the ModSecurity audit log, or your panel’s ModSecurity hits list, for a message saying the request body is larger than the configured limit. On a VPS you can raise SecRequestBodyLimit in the ModSecurity configuration; on shared hosting, send the log line to your host.

Step 5: Cloudflare’s upload limit

If your domain is proxied through Cloudflare (orange cloud), every upload passes through Cloudflare’s edge first. According to Cloudflare’s documentation, the maximum upload size is 100 MB on Free and Pro plans, 200 MB on Business, and up to 5 GB on Enterprise. You can lower the limit, or raise it within your plan, under the zone’s Network settings (“Maximum Upload Size”). A request over the limit gets a Cloudflare-branded 413 before it ever reaches your server, so no server change can fix it.

Your options are to upload files smaller than the limit, to use a plugin or tool that sends large files in chunks, or to send the upload to a hostname that is not proxied. A DNS-only (gray cloud) subdomain pointed at the same server, used just for wp-admin uploads or SFTP, is a common pattern. Very long uploads also run into Cloudflare’s proxy read timeout, which shows up as a 524 error; our Cloudflare 5xx errors guide covers that.

Step 6: Upload big files another way

For files over a few hundred megabytes, raising every web limit is the wrong fix. Long uploads through a browser fail on flaky connections, tie up a PHP worker for minutes and are easy to abuse. Send big files over SFTP and register them with WordPress instead.

wp media import ~/incoming/FILENAME --title="MEDIA_TITLE"
wp plugin install ~/incoming/PLUGIN_ZIP.zip --activate
wp theme install ~/incoming/THEME_ZIP.zip

Run these over SSH from the site folder after uploading the file by SFTP to a folder outside your web root, here ~/incoming. FILENAME is the file you uploaded, MEDIA_TITLE is the title it gets in the media library, and PLUGIN_ZIP and THEME_ZIP are the zip names. wp media import copies the file into wp-content/uploads, creates the attachment and generates image sizes; --activate turns the plugin on after install. Good output ends with “Success: Imported 1 of 1 items.” or “Plugin installed successfully.” A memory error during import means the image is too large for your memory_limit, which you raise in the CLI’s php.ini or with php -d memory_limit=MEMORY_SIZE $(which wp) media import ....

For database files, use wp db import FILE.sql rather than a web importer. For video, a dedicated video host or streaming service is usually a better home than your media library.

Advanced: Find the refusing layer with curl

When you cannot tell which layer is refusing, send a test body of known size and read the response headers. Do this on your own computer.

head -c SIZE_IN_BYTES /dev/zero > test-upload.bin
curl -sS -o /dev/null -D - -X POST --data-binary @test-upload.bin https://example.com/wp-login.php
curl -sS -o /dev/null -D - -X POST --data-binary @test-upload.bin --resolve example.com:443:ORIGIN_IP https://example.com/wp-login.php
  • SIZE_IN_BYTES is the test size, for example the size of the file that failed. On macOS, head -c works the same way.
  • example.com is your domain and ORIGIN_IP is your server’s real IP address, shown in your hosting panel. --resolve makes curl skip DNS and connect straight to the origin, bypassing Cloudflare.
  • -D - prints the response headers, -o /dev/null throws away the body, and --data-binary sends the file as the request body without alteration.

How to read it: a first line of HTTP/2 413 with server: cloudflare means Cloudflare refused it. The same through --resolve with server: nginx or Apache means your web server refused it. A normal response (200 or 302) means the body got through every web layer to PHP, and if it was larger than post_max_size, the PHP error log now contains a line like “PHP Warning: POST Content-Length of BODY_BYTES bytes exceeds the limit of LIMIT_BYTES bytes”. That pins the problem on PHP. Delete test-upload.bin when you are done.

A sensible target for most sites: upload_max_filesize 64M, post_max_size slightly higher, web server and WAF limits matching or a little above, and anything larger moved by SFTP. Huge web limits invite abuse without helping real users much.

When to contact your host

Open a ticket if your panel will not let you raise the PHP values, if Site Health still shows the old values after you changed them, or if the 413 comes from Nginx, Apache, LiteSpeed or ModSecurity on a server you do not control. Send:

  • the domain and the URL that failed (for example /wp-admin/async-upload.php);
  • the file size you need to upload;
  • the exact error text and the UTC time of a failed attempt;
  • the server response header and status code from the Network tab or curl;
  • the Site Health values for upload max filesize, max size of post data and max effective file size;
  • any matching log line (“client intended to send too large body”, a ModSecurity message, or the PHP “POST Content-Length” warning).

Our diagnostic tools can show you response headers quickly, and if more than uploads are broken, start at the Emergency Room.

Still stuck?

Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.

Get the free checklists

Keep reading

All field guides

Search the field manual

What is going wrong?

Press / to search from any page, Esc to close