If you're running a site on your own VPS — Nginx in front, maybe Apache or PHP-FPM behind it — and a file upload suddenly dies with "413 Request Entity Too Large," you're not looking at a broken app. You're looking at a web server that's been told, somewhere in its config, to reject anything past a certain size. The frustrating part is that on a VPS there's no single "upload limit" field to click like there is in cPanel. There are three or four places the limit can be set, and they don't always agree with each other.

Here's how to find which layer is actually blocking the request, and how to raise it without accidentally opening your server up to abuse.

Symptom

The exact wording depends on what's serving the error page:

  • 413 Request Entity Too Large with a plain Nginx-style error page (usually just that text, sometimes with a version string in small print at the bottom).
  • A WordPress media upload that fails instantly — not slowly, not with a progress bar crawling and stalling, just an immediate rejection — which is a strong hint it's happening before PHP even runs.
  • An API client (Postman, a mobile app, a webhook sender) getting a 413 on a POST with a file or a large JSON payload.
  • Sometimes it shows up as a connection reset instead of a proper 413, particularly through Cloudflare or another proxy sitting in front of your VPS.

The instant-rejection behavior is the tell. If PHP's upload_max_filesize were the culprit, the browser would usually show some upload progress before PHP kicks the request back. A 413 that appears the moment the request lands means something in front of PHP already said no.

Cause: request-size limits exist at every layer of the stack

On a bare VPS, a file upload passes through several checkpoints before it reaches your application code. Each one has its own idea of "too big," and the smallest limit in the chain wins:

LayerDirectiveTypical default
Nginx (as web server or reverse proxy)client_max_body_size1MB
ApacheLimitRequestBody0 (unlimited) unless explicitly set
PHP-FPM / php.iniupload_max_filesize, post_max_size2M – 8M
Cloudflare (if you're proxied)Max upload size (plan-dependent, not editable directly)100MB on Free/Pro

Nginx's default of 1MB is the one that catches people off guard, because it's so much smaller than PHP's own defaults. If you set upload_max_filesize to 500M in php.ini and never touch Nginx, every upload over 1MB still gets rejected — Nginx never even hands the request to PHP.

Fix: raise the limit at every layer, starting with Nginx

1. Nginx: client_max_body_size

Add this inside the relevant server block (or http block to apply it site-wide) in your Nginx config, usually /etc/nginx/sites-available/yoursite.conf or /etc/nginx/nginx.conf:

server {
    ...
    client_max_body_size 100M;
}

Then test the config and reload — never just restart blind:

nginx -t && systemctl reload nginx

If Nginx is acting as a reverse proxy in front of Apache or a Node app, set client_max_body_size in the server block that's actually handling the incoming connection, not just the upstream. It's a common mistake to fix it in one block and leave a second, catch-all server block still capped at 1MB.

2. Apache: LimitRequestBody

If Apache is serving requests directly (or sitting behind Nginx as the app server), check your virtual host config or an .htaccess file for LimitRequestBody. A value of 0 means unlimited; anything else is a hard cap in bytes:

<Directory /var/www/yoursite>
    LimitRequestBody 104857600
</Directory>

That's 100MB in bytes (104857600 = 100 × 1024 × 1024). Restart Apache after editing the main config; a config-only change inside .htaccess takes effect immediately if AllowOverride permits it there.

3. PHP-FPM: post_max_size and upload_max_filesize

Find the pool's php.ini — on most VPS setups it's something like /etc/php/8.2/fpm/php.ini — and set both directives, keeping post_max_size equal to or larger than upload_max_filesize:

upload_max_filesize = 100M
post_max_size = 110M
memory_limit = 256M

Restart PHP-FPM for the change to take effect:

systemctl restart php8.2-fpm

If you're running multiple PHP versions or pools, double check you edited the pool your site actually uses — look at the fastcgi_pass or proxy_pass line in your Nginx vhost to confirm which socket it's pointed at.

4. Cloudflare, if you're proxied through it

Cloudflare enforces its own ceiling regardless of what your origin server allows — 100MB on Free and Pro plans, higher on Business and Enterprise, with a separate "Enterprise-only" bump available for very large uploads. If large-file uploads matter for your app, either raise your plan tier, use Cloudflare's chunked/resumable upload approach, or route that specific upload path (e.g. a direct-to-origin subdomain) around the Cloudflare proxy with a DNS-only (grey-clouded) record.

A quick way to confirm which layer is blocking you

Instead of guessing, test with curl and watch which server answers:

curl -v -X POST -F "file=@testfile.zip" https://yourdomain.com/upload.php

If the 413 comes back near-instantly with an Nginx-style error page and no PHP-FPM log entry at all, Nginx is the blocker. If PHP-FPM's error log (/var/log/php8.2-fpm.log or similar) shows a related warning, the problem's downstream of Nginx. Checking logs in this order — Nginx access/error log first, then PHP-FPM, then application logs — saves you from tuning the wrong layer.

Prevention

  • Set all four limits (Nginx, Apache if used, PHP-FPM, and any CDN/proxy) to consistent, deliberate values instead of raising just one and hoping. Document the numbers somewhere so the next person doesn't repeat the same hunt.
  • Don't set limits absurdly high "just in case." A client_max_body_size of 2GB on a public upload form is an invitation for someone to fill your disk or tie up a worker process with a slow, oversized POST. Match the limit to what your app legitimately needs.
  • For genuinely large files (video, big database dumps, backups), prefer a dedicated upload path — direct-to-object-storage uploads, chunked upload libraries, or SFTP for admin-only transfers — rather than pushing the web-facing limit sky-high everywhere.
  • If you manage more than one site on the VPS, remember these are usually set per-server-block and per-pool. A fix on one site's vhost doesn't carry over to the others.

Frequently Asked Questions

Why does WordPress's Media Settings page show a smaller max upload size than what I set in php.ini?

WordPress reports the lowest of upload_max_filesize, post_max_size, and memory_limit from whichever PHP-FPM pool is actually serving the request. If the number on that page hasn't changed after your edit, you likely updated the wrong pool's php.ini, or PHP-FPM hasn't been restarted yet.

I raised client_max_body_size but I still get a 413. What am I missing?

Check that you edited the server block that's actually terminating the client connection, not an upstream/internal block, and confirm with nginx -T that the directive is present in the active config after reload. Also rule out a CDN or load balancer in front of your VPS enforcing its own separate limit.

Is there a downside to setting client_max_body_size very high across the whole server?

Yes — a high global limit means every endpoint, including ones that were never meant to accept large uploads, can be hit with oversized requests, which is an easy way to waste worker processes or disk. Scope large limits to the specific location or server block that needs them where possible.

Does this affect API requests with large JSON bodies too, not just file uploads?

Yes. client_max_body_size and post_max_size apply to the whole request body, regardless of content type. A large JSON payload from a webhook or API client hits the same ceiling a file upload does.

How do I check the current effective value without editing anything?

Run nginx -T | grep client_max_body_size to see every place it's set across your active config, and php -i | grep -E "post_max_size|upload_max_filesize" against the CLI php.ini (note this may differ from the FPM pool's php.ini, so check both).