Troubleshooting

413 Request Entity Too Large: find the layer that says no and raise its limit

A 413 means an upload is bigger than a limit on the way in: nginx allows 1 MB by default, Cloudflare caps by plan, PHP and Apache have their own. Fix each one.

Published ~10 min read

Short answer: 413 Request Entity Too Large means the request body, usually a file upload or a large form or JSON payload, is bigger than a limit set somewhere between the browser and your app, and that layer refused it before your code ever saw it. On a VPS the usual culprit is nginx, whose client_max_body_size defaults to just 1 MB. After that come Cloudflare’s upload cap, Apache’s LimitRequestBody, ModSecurity and your framework’s own body limit. Find the layer that answered, raise its limit to the largest upload you really expect, and make sure every layer behind it allows at least as much.

The current HTTP specification, RFC 9110, renamed the status to 413 Content Too Large. Older software still prints Request Entity Too Large or Payload Too Large; all three are the same error.

Which layer sent the 413?

Reproduce the upload with curl, once through the normal hostname and once straight to your server, then check the server’s log. Do not go by the headers alone: while a site is proxied by Cloudflare, every response carries server: cloudflare and a cf-ray header, including a 413 that your own nginx sent. What tells them apart is the origin. If the direct request is refused too and nginx logged it, nginx is the limit. If the direct request goes through and nothing reached your logs, Cloudflare refused it.

Debian · Ubuntu
# a 20 MB test file, posted as a form upload
# (pick a size just above the upload that fails for you)
head -c 20M /dev/zero > /tmp/test.bin
curl -sS -o /dev/null -D - -F "file=@/tmp/test.bin" https://example.com/upload

# skip Cloudflare and ask the origin directly (203.0.113.10 = your server)
curl -sS -o /dev/null -D - -F "file=@/tmp/test.bin" \
  --resolve example.com:443:203.0.113.10 https://example.com/upload

# on the server: did nginx refuse it?
sudo grep 'too large body' /var/log/nginx/error.log | tail -5
rm /tmp/test.bin

The table lists every common source, its default and how it shows itself:

LayerSettingDefaultWhat you see
nginxclient_max_body_size1 MB413 page that names nginx; error log: client intended to send too large body
Cloudflare proxyyour Cloudflare plan100 MB on Free and Pro, 200 MB on Business, more on Enterprise413 from Cloudflare; nothing in your server logs
ApacheLimitRequestBody1 GiB since 2.4.54 (unlimited before)413; logged as AH01588 only at LogLevel info
ModSecuritySecRequestBodyLimit12.5 MiB with files, 128 KiB without, in the recommended config413; a request body line in the error log
PHPupload_max_filesize, post_max_size2M and 8MUsually no 413: an upload error, or empty $_POST and $_FILES
Express (Node)express.json limit100kb413 with error type entity.too.large
IIS (Windows)maxAllowedContentLength30,000,000 bytes, about 28.6 MB404.13, not 413

Fix 1: raise client_max_body_size in nginx

The directive works in the http, server and location blocks, and the most specific one wins. That is useful: give the whole site a sensible value and open only the route that accepts big files.

/etc/nginx/sites-available/example.com
server {
    # ... your existing listen, server_name and ssl_* lines stay as they are ...
    client_max_body_size 64m;          # the whole site

    location /api/import {
        client_max_body_size 512m;     # one route that takes big files
        proxy_pass http://127.0.0.1:3000;
    }
}
find every value, then reload
sudo nginx -T 2>/dev/null | grep -n client_max_body_size
sudo nginx -t && sudo systemctl reload nginx

nginx -T prints the full configuration with every included file, so it also finds a smaller value hidden in a snippet or a location that quietly overrides yours. Three more traps:

  • Two nginx in a row. If your host nginx proxies to a container that runs its own nginx, or you use a proxy manager in front of an app, every one of them needs the higher limit.
  • Plesk regenerates its nginx files. Set the limit per domain under Apache & nginx Settings in the panel, not in the generated config.
  • Do not set it to 0 site-wide. Zero disables the check, and then any client can push a body as large as your disk. Raise it where it is needed instead.

If you set up nginx as a reverse proxy from our nginx reverse proxy guide, the config there already sets the limit; adjust that line.

Fix 2: raise the PHP limits

PHP’s limits usually do not produce a 413 at all, which is why they confuse people. A file over upload_max_filesize arrives with an upload error, which WordPress shows as The uploaded file exceeds the upload_max_filesize directive in php.ini. A whole request over post_max_size arrives with empty $_POST and $_FILES, plus a warning in the PHP log that the POST Content-Length exceeds the limit. Forms then fail in odd ways, such as a missing CSRF token. Laravel is the exception: its ValidatePostSize middleware compares the request size with post_max_size and answers with a 413 of its own, a PostTooLargeException.

The defaults are 2M and 8M. Raise both, keep post_max_size above upload_max_filesize (a form sends more than the file), and keep memory_limit above post_max_size, as the PHP manual advises. Use a small override file rather than editing php.ini, so a package upgrade never undoes it. PHP is 8.3 on Ubuntu 24.04, 8.2 on Debian 12 and 8.4 on Debian 13; adjust the version in the paths:

PHP-FPM · Ubuntu 24.04
ls /etc/php/                                 # which version is installed?
sudo tee /etc/php/8.3/fpm/conf.d/99-uploads.ini > /dev/null <<'EOF'
upload_max_filesize = 64M
post_max_size = 70M
memory_limit = 256M
EOF
sudo systemctl restart php8.3-fpm

# check every place that sets them, pool files included
sudo grep -rE 'upload_max_filesize|post_max_size' /etc/php/8.3/fpm/

Do not trust php -i for this check. It shows the command-line PHP, which reads /etc/php/8.3/cli/, not the FPM settings your site uses. With Apache and mod_php, the file to change lives under /etc/php/8.3/apache2/ and you restart apache2 instead. On hosting where you cannot touch the server config, both values can go in a .user.ini file in the site’s directory, which PHP-FPM reads. PHP caches that file for five minutes by default, so a change there is not instant. Calling ini_set() from your code cannot change either value.

Fix 3: work around Cloudflare’s upload cap

When the site is proxied (orange cloud), Cloudflare checks the body size before your server is involved. Per Cloudflare’s documentation the cap is 100 MB on the Free and Pro plans and 200 MB on Business; Enterprise accounts can raise theirs, up to 5 GB without contacting Cloudflare. No setting on your server changes this. Your options:

  • Upload in chunks. A resumable uploader sends the file in pieces that each stay under the cap, and the server joins them. It also survives a dropped connection.
  • Send big uploads around the proxy. A separate hostname such as upload.example.com, set to DNS only (grey cloud), reaches your server directly. That record reveals your server’s IP address, so decide whether you mind.
  • Upload straight to object storage with a pre-signed URL, so the file never passes through your web server or the proxy.
  • Move to a plan with a higher cap, if the upload sizes justify it.

Fix 4: Apache and ModSecurity

Since Apache 2.4.54 the default LimitRequestBody is 1 GiB, so plain Apache rarely hits it. When it does, the cause is usually a lower value set in a virtual host or an .htaccess file. The value is in bytes. Apache logs the refusal (AH01588) only at LogLevel info, so with the default level the access log is the place to look:

Apache · Debian · Ubuntu
# recent 413s (the status is field 9 in the combined log format)
sudo awk '$9 == 413' /var/log/apache2/access.log | tail -5
# where is a lower limit set?
sudo grep -rn LimitRequestBody /etc/apache2/ /var/www/
Apache config · VirtualHost, Directory or .htaccess
# 100 MiB, in bytes
# (.htaccess honours it only where AllowOverride All is set)
LimitRequestBody 104857600
test and reload
sudo apachectl configtest && sudo systemctl reload apache2

ModSecurity is a frequent hidden source on servers that run it, including many control panels. Its recommended configuration caps request bodies at 12.5 MiB when they contain files (SecRequestBodyLimit) and at 128 KiB for the rest (SecRequestBodyNoFilesLimit), which a large JSON API call can exceed. The error log then says the request body is larger than the configured limit. Raise the matching directive in your ModSecurity config, on Debian and Ubuntu usually /etc/modsecurity/modsecurity.conf, and reload the web server.

Fix 5: limits inside your application

Frameworks parse the body themselves and have their own caps. Express’s body parsers default to 100kb and answer larger bodies with a 413 that says request entity too large:

Express · app.js
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ limit: '10mb', extended: true }));
// multipart uploads handled by multer use multer's own limits.fileSize

Next.js Server Actions accept 1 MB by default, raised with serverActions.bodySizeLimit under experimental in next.config.js. ASP.NET Core on Kestrel defaults to 30,000,000 bytes, raised with MaxRequestBodySize or the [RequestSizeLimit] attribute on one action.

On Windows: IIS answers with 404.13, not 413

IIS request filtering rejects bodies above maxAllowedContentLength, which defaults to 30,000,000 bytes, about 28.6 MB. It does not send a 413: the client gets a 404, and the IIS log records the substatus 404.13. If uploads over roughly 28 MB fail with “not found” on a Windows VPS, raise the limit in the site’s web.config (value in bytes, here 100 MiB):

web.config
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="104857600" />
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

How do I set limits that will not bite again?

  • Decide on the largest legitimate upload, add some headroom for form overhead, and set the same value through the whole chain: proxy, web server, PHP or app. Keep the outer layers at or above the inner ones, so your app gets the chance to return its own, friendlier error.
  • Raise limits per route, for the upload and import endpoints, and leave the rest of the site small.
  • Mind the disk. nginx writes request bodies larger than its small memory buffer to a temporary file, and PHP stores uploads in its temp directory before your code moves them. A full disk breaks uploads in new ways, see No space left on device. On a VMHeaven VPS you can add storage in 15 GB blocks from the customer panel while the server keeps running, then extend the partition inside the system to use it.
  • Watch the timeouts for slow uploads. A big file over a slow connection can outlast PHP’s max_input_time or the timeouts of a proxy, which ends in a different error.
  • Do not push huge files through a web form at all. Database dumps, backups and media archives travel better over SFTP or rsync, as shown in transferring files to a VPS.

Frequently asked

What does 413 Request Entity Too Large mean?

The request body, usually a file upload or a large form or JSON payload, is larger than a size limit on the server or a proxy in front of it, so the request was refused before the application handled it. RFC 9110 now names the status 413 Content Too Large; older software calls it Request Entity Too Large or Payload Too Large.

How do I fix 413 Request Entity Too Large in nginx?

Raise client_max_body_size, which defaults to 1 MB, in the server or location block that receives the upload, for example 'client_max_body_size 64m;'. Then run 'sudo nginx -t && sudo systemctl reload nginx', and raise PHP's upload_max_filesize and post_max_size or your app's own limit to match.

What is Cloudflare's maximum upload size?

Cloudflare's documentation lists 100 MB for the Free and Pro plans and 200 MB for Business, while Enterprise accounts can raise their limit up to 5 GB without contacting support. Larger requests through the proxy get a 413, so use chunked uploads or a DNS-only hostname for big files.

Related articles

All troubleshooting