
A 413 error means your reverse proxy rejected the upload before your app (WordPress, phpMyAdmin, a custom API) ever saw it. In many cases, the fix Nginx 413 error is one directive.
The catch is that raising only that limit can still fail later. PHP size caps, upstream timeouts, or a full disk can stop the upload midstream.
This guide gives you a production-safe workflow on a VPS.
You’ll raise Nginx’s request-body limit, align PHP-FPM settings, and validate with repeatable tests. You’ll also avoid two common follow-up problems: memory pressure and timeouts that turn into 502/504s.
What 413 means (and how to confirm it’s Nginx)
Nginx returns 413 Request Entity Too Large when the request body exceeds what it allows. The setting that controls this is client_max_body_size.
Before you change configs, confirm the 413 is actually coming from Nginx. It may be coming from a CDN, load balancer, or an app gateway instead.
On your VPS, start here:
sudo nginx -v
sudo systemctl status nginx --no-pager
Reproduce the error once (try the upload again). Then check logs:
# Ubuntu/Debian default paths
sudo tail -n 50 /var/log/nginx/error.log
sudo tail -n 50 /var/log/nginx/access.log
A typical Nginx 413 entry looks like this:
client intended to send too large body: 15728640 bytes, client: 203.0.113.10, server: example.com, request: "POST /wp-admin/async-upload.php HTTP/2.0"
If you see that line, you’ve found the right knob to turn.
If you don’t see it, keep going anyway. After you lift size limits, timeouts often become the next bottleneck.
This is especially common when the failure changes to a 502/504 later.
Fix Nginx 413 error by setting client_max_body_size
You can set client_max_body_size at three levels: http (global), server (per site), or location (per path).
For most VPS setups, per-site (inside the domain’s server {}) is the safest default.
Set a limit you actually need. If you upload 200MB videos, set 256M. If you occasionally import WordPress backups, 128M is usually plenty.
Don’t crank it up “just in case.” Large bodies increase temp-file pressure and widen the blast radius of abusive requests.
Step 1: Find your active site config
On Ubuntu/Debian, you’ll usually have:
ls -la /etc/nginx/sites-available
ls -la /etc/nginx/sites-enabled
Find the server block for your domain (example: /etc/nginx/sites-available/example.com).
On AlmaLinux/Rocky, virtual hosts are often pulled in from includes.
Use this to see what Nginx is actually loading:
sudo nginx -T | sed -n '1,140p'
nginx -T prints the full active config and shows each file included.
Step 2: Add (or raise) client_max_body_size
Edit the site config. Here’s a common example:
sudo nano /etc/nginx/sites-available/example.com
Add the directive inside the correct server block. Don’t put it above the block or outside braces.
server {
# ... your existing config
client_max_body_size 128M;
# ... the rest
}
This applies whether you’re proxying to PHP-FPM or to an upstream app (Node/Python/etc.).
Step 3: Validate and reload Nginx
sudo nginx -t
sudo systemctl reload nginx
If nginx -t fails, stop there.
Fix the syntax error first, then reload.
Verify: confirm the directive is active
Nginx doesn’t provide a clean “effective settings for this one vhost” view.
Still, you can confirm the loaded config includes your directive:
sudo nginx -T | grep -n "client_max_body_size" -n
Try the upload again.
If the 413 is gone but the upload still fails, you’re likely hitting PHP limits or timeouts. Handle those next.
Align PHP-FPM upload and post limits (WordPress, PHP apps)
If you run PHP (WordPress, Laravel, phpMyAdmin), Nginx accepting the request is only step one.
PHP enforces its own ceilings:
upload_max_filesize– max size of a single uploaded filepost_max_size– max size of the entire POST body (must be bigger thanupload_max_filesize)memory_limit– too low can kill imports/unzips; too high can OOM your VPSmax_execution_time/max_input_time– large imports can hit these
Step 1: Identify your PHP version and FPM service
php -v
List PHP-FPM services (common names):
sudo systemctl list-units --type=service | grep -E "php.*fpm"
On Ubuntu/Debian you’ll typically see php8.3-fpm or php8.4-fpm in 2026.
Step 2: Edit the correct php.ini for FPM
Make changes in the FPM ini, not the CLI ini.
- Ubuntu/Debian example:
/etc/php/8.3/fpm/php.ini - AlmaLinux/Rocky (Remi packages vary): often
/etc/php.iniplus pool overrides
On Ubuntu/Debian, for example:
sudo nano /etc/php/8.3/fpm/php.ini
Set values that match your target upload size. Example for 128MB uploads:
upload_max_filesize = 128M
post_max_size = 140M
memory_limit = 256M
max_execution_time = 180
max_input_time = 180
Rule of thumb: keep post_max_size about 10–20% higher than upload_max_filesize.
Also, don’t set memory_limit to gigabytes on a small VPS.
PHP-FPM workers can get OOM-killed and take your site with them.
Step 3: Reload PHP-FPM
sudo systemctl reload php8.3-fpm
If reload fails, restart and check logs:
sudo systemctl restart php8.3-fpm
sudo journalctl -u php8.3-fpm -n 80 --no-pager
Verify: confirm PHP sees the new limits
The clean check is a tiny temporary script that prints the values PHP is using.
Create the file:
sudo mkdir -p /var/www/example.com/public
sudo nano /var/www/example.com/public/php-limits.php
Paste:
<?php
header('Content-Type: text/plain');
foreach (['upload_max_filesize','post_max_size','memory_limit','max_execution_time','max_input_time'] as $k) {
echo $k.': '.ini_get($k)."\n";
}
Visit https://example.com/php-limits.php and confirm the values match what you set.
Important: remove it right after testing:
sudo rm -f /var/www/example.com/public/php-limits.php
If your error changed from 413 to a gateway issue, use this HostMyCode guide to isolate upstream problems: Nginx 502 Bad Gateway fix on Ubuntu VPS.
Docker/Compose deployments: set the limit in your reverse proxy container
With Docker Compose, you might have two proxies in play.
You can have Nginx on the host and another proxy in a container (Nginx/Caddy/Traefik) at the edge. A 413 can come from either layer.
Quick check: inspect response headers and see who answers the request.
Look for server: nginx, and whether it matches your container build:
curl -I https://example.com/ | sed -n '1,20p'
Nginx container example
If an Nginx container is your edge proxy, add a small config snippet and mount it into the container.
Create a file on the host:
mkdir -p ~/proxy/nginx/conf.d
nano ~/proxy/nginx/conf.d/uploads.conf
Add:
client_max_body_size 128M;
Then mount it in docker-compose.yml for the proxy service:
services:
nginx:
image: nginx:1.27
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
ports:
- "80:80"
- "443:443"
Apply changes:
docker compose up -d
docker compose logs --tail=50 nginx
Caddy and Traefik note
Caddy and Traefik don’t use client_max_body_size.
If you front a PHP app with Caddy, PHP limits are still the usual culprit.
If you’re using Caddy for TLS termination on a VPS, see: Set Up Let’s Encrypt With Caddy for a VPS Site.
Stop uploads from stalling: timeouts and buffering that actually matter
Once size limits are high enough, the next failure often looks like a “stuck” upload.
It eventually turns into a 504/499, or a WordPress import that dies mid-run.
Two Nginx settings help most on slow connections and large request bodies:
client_body_timeout– how long Nginx waits for the client to send the request bodysend_timeout– how long Nginx waits while sending a response to the client
On a typical VPS, increase them conservatively in the same server block:
server {
# uploads
client_max_body_size 128M;
# tolerate slower uploads without keeping sockets forever
client_body_timeout 120s;
send_timeout 120s;
}
Don’t go extreme (like 30 minutes) unless you’ve measured a real need.
Overlong timeouts make slow-client abuse easier.
Buffering and temp disk space (why /var can suddenly fill up)
Nginx buffers request bodies to disk once they outgrow in-memory buffers.
Large uploads can end up under /var/lib/nginx (the exact path depends on distro/build).
If that disk fills, uploads can fail with “no space left on device”.
Check free space before you test big uploads:
df -h
sudo du -sh /var/lib/nginx 2>/dev/null || true
If space is tight, rotate/clean logs safely (don’t delete active log files) or increase your VPS storage.
WordPress-specific checks (Media Library, plugin uploads, and imports)
WordPress uploads pass through several layers: browser → Nginx → PHP-FPM → WordPress.
After Nginx and PHP accept the size, WordPress can still block certain operations via settings/constants or workflow quirks.
Practical checks:
- In WordPress admin, open Media → Add New and confirm “Maximum upload file size” matches your PHP limit.
- Upload a file that’s 10–20% under your cap (for a 128MB limit, try ~100MB). It should complete without drama.
- Upload a file slightly above the cap (for a 128MB limit, try ~140MB). A predictable failure is healthy—it proves limits are enforced.
If you’re also tuning WordPress performance on a VPS (caching and PHP worker efficiency), you can pair this fix with: Install LiteSpeed Cache for WordPress on a VPS.
Test the fix properly (without guessing)
Browser uploads are convenient, but they’re noisy to debug.
Use a repeatable curl test so you can separate “size limit” from “app problem.”
Create a file with a known size (example: 110MB):
# On Linux/macOS
dd if=/dev/zero of=upload-test-110M.bin bs=1M count=110
Now POST it to an endpoint that accepts uploads.
WordPress uploads require auth and a nonce, so they’re not curl-friendly.
Instead, test against a temporary upload handler, then delete it.
Temporary upload endpoint (remove after test)
Create a minimal PHP upload receiver:
sudo nano /var/www/example.com/public/upload-receiver.php
<?php
header('Content-Type: text/plain');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo "POST only\n";
exit;
}
if (!isset($_FILES['file'])) {
http_response_code(400);
echo "missing file field\n";
exit;
}
$dst = __DIR__ . '/_upload_test_' . basename($_FILES['file']['name']);
if (!move_uploaded_file($_FILES['file']['tmp_name'], $dst)) {
http_response_code(500);
echo "move failed\n";
exit;
}
echo "ok: " . filesize($dst) . " bytes\n";
Run the curl upload:
curl -sS -F "file=@upload-test-110M.bin" https://example.com/upload-receiver.php
You want an ok: response.
If you still get 413, Nginx is blocking somewhere.
If you get 500, check PHP logs, permissions, and available disk.
Clean up immediately after:
sudo rm -f /var/www/example.com/public/upload-receiver.php
sudo rm -f /var/www/example.com/public/_upload_test_*
rm -f upload-test-110M.bin
Common mistakes that keep 413 coming back
- You edited the wrong config file. Use
nginx -Tto confirm which files are loaded. - You put the directive in the wrong context.
client_max_body_sizemust live insidehttp,server, orlocation. - You reloaded the wrong service. OpenResty, containerized Nginx, or panel-managed Nginx can change service names and reload behavior.
- You only adjusted Nginx. PHP can still reject uploads via
upload_max_filesize/post_max_size. - You raised limits and ran out of disk. Upload buffering can fill
/var. Checkdf -hbefore and after testing.
Control panels (Plesk/cPanel/DirectAdmin): where the limit is set
On a panel-managed VPS, Nginx configs are often generated from templates.
Direct edits may disappear after updates or a “reconfigure” run.
- Plesk: add per-domain Nginx directives in the domain’s Apache & Nginx settings, and align PHP settings per domain.
- cPanel (with Nginx or as a proxy): upload limits may live in includes/templates; PHP limits are typically set via MultiPHP INI Editor.
- DirectAdmin: changes may need to be made in templates or per-domain settings so they survive template rebuilds.
If you can’t tell whether your stack is panel-managed or hand-managed, a managed VPS hosting plan can save you from chasing config changes that keep getting overwritten.
Monitoring after the change (catch the next bottleneck)
After uploads succeed, watch what tends to break next.
Common issues include PHP-FPM worker saturation, rising 499s (client closed request), and slow disk creep from temp files and logs.
These quick checks cover the basics:
# Nginx error spikes
sudo grep -R "too large body" -n /var/log/nginx/error.log | tail
# PHP-FPM status and errors
sudo journalctl -u php8.3-fpm -n 100 --no-pager
# Disk trends
df -h
If you want a lightweight live dashboard for a VPS, HostMyCode has a practical walkthrough for Netdata: Run Netdata on a VPS for live server monitoring.
Summary: a safe upload fix that doesn’t create new outages
To stop 413s for good, raise the limit where the request is rejected (client_max_body_size).
Then match PHP-FPM’s POST/upload settings so the app can actually handle the file.
After that, test with a known-size upload and watch disk usage and timeouts.
If you’re doing this on production infrastructure and want predictable behavior under load, run on a VPS sized for your traffic and PHP workload.
A HostMyCode VPS gives you full control over Nginx/PHP settings and consistent resources, which makes upload limits far easier to manage.
If uploads are failing because your site has outgrown shared hosting limits, move to a VPS you control. Start with a HostMyCode VPS, or pick managed VPS hosting if you want help keeping Nginx/PHP and security settings clean as you scale.
FAQ
Should I set client_max_body_size globally in nginx.conf?
Only if every site on the server needs the same limit. On a multi-site VPS, set it per domain in the site’s server {} block.
I increased client_max_body_size but uploads still fail. What next?
Check PHP-FPM: upload_max_filesize and post_max_size must be high enough. Then review logs for timeouts (504) or upstream errors (502).
Why does WordPress show a lower “Maximum upload file size” than Nginx?
WordPress displays the PHP limit, not the Nginx limit. If PHP is set to 64M, WordPress will show 64MB even if Nginx allows 128M.
Can raising upload limits create security risk?
Yes. Higher limits increase exposure to large request-body abuse and disk pressure. Keep the cap close to what you need, use reasonable timeouts, and add rate-limiting/WAF controls if the site is public and targeted.
Do I need to restart Nginx after changing the config?
A reload is usually enough: sudo systemctl reload nginx. Run sudo nginx -t first so you don’t load a broken config.