How to fix "413 Request Entity Too Large" and raise the upload limit for PHP on Nginx

An upload can be stopped in three places: by Nginx, by PHP's file limit and by PHP's form limit. Only one of them shows an error page. This guide shows how to find which one stopped it, and how to raise it in the right place.

We tested every step in this guide on Debian 13 with Nginx 1.26 and PHP 8.4. We uploaded files of 5, 12, 40 and 70 MB and wrote down what happened at each limit. Ubuntu works the same way. Change 8.4 to your PHP version.

The three limits

These are the defaults on a fresh Debian or Ubuntu server:

  • Nginx client_max_body_size: 1 MB. Anything bigger gets a 413 Request Entity Too Large page from Nginx. PHP never sees it.
  • PHP upload_max_filesize: 2 MB. The biggest single file PHP accepts. A bigger file is dropped without an error page and without a log line. Your app just sees an upload that failed.
  • PHP post_max_size: 8 MB. The biggest whole form, with all files in it. If the form is bigger, PHP throws away everything: the files and the other fields. The form looks like it was sent empty.

So a 5 MB photo gets a 413 page first. Once you fix Nginx, it fails again, but now quietly. That's why people often think their fix "didn't work". You need to raise all three.

1. Find out which limit stopped the upload

Try the upload once, then look at the Nginx error log:

sudo tail -n 20 /var/log/nginx/error.log
  • client intended to send too large body: 5243087 bytes: Nginx stopped it. See step 2.
  • PHP Warning: PHP Request Startup: POST Content-Length of 12583120 bytes exceeds the limit of 8388608 bytes: the form was bigger than post_max_size. See step 3.
  • Nothing in the log, and the upload still failed: the file was bigger than upload_max_filesize. PHP marks the file with error code 1 (UPLOAD_ERR_INI_SIZE) and logs nothing. See step 3.

In WordPress, Media → Add New shows "Maximum upload file size". It shows the smaller of the two PHP limits, so it's a quick way to see if your PHP change worked. It doesn't know about the Nginx limit.

2. Raise the Nginx limit

Debian and Ubuntu don't set client_max_body_size at all, so the 1 MB default applies. Add it to the server block of your site, in /etc/nginx/sites-available/:

server {
    server_name example.com;
    client_max_body_size 64m;
    ...
}

Then test and reload:

sudo nginx -t && sudo systemctl reload nginx

Pick a number a bit bigger than the biggest file you really need. Don't use 0. It turns the check off, and then anyone can send your server huge requests.

Only one page needs big uploads? Keep a small limit for the site and raise it for that one URL. In our test, a 5 MB upload worked on /upload.php and got a 413 on every other page:

server {
    client_max_body_size 2m;

    location = /upload.php {
        client_max_body_size 64m;
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    }
}

For WordPress, the media uploader sends files to /wp-admin/async-upload.php.

3. Raise the PHP limits

The web server uses PHP-FPM, which has its own settings, separate from the php command you type in the terminal. Changing /etc/php/8.4/cli/php.ini does nothing for your site. In our test, the website used 64M after the change while php in the terminal still said 2M.

Put your changes in a small file of their own, so they're easy to find later:

sudo nano /etc/php/8.4/fpm/conf.d/99-uploads.ini
upload_max_filesize = 64M
post_max_size = 64M

Make post_max_size at least as big as upload_max_filesize. If people upload several files at once, make it bigger still, because it covers the whole form.

Then check and reload PHP-FPM. A reload was enough in our test:

sudo php-fpm8.4 -t && sudo systemctl reload php8.4-fpm

Check the values PHP-FPM really uses (not the terminal ones):

php-fpm8.4 -i | grep -E '^(upload_max_filesize|post_max_size)'

You should see upload_max_filesize => 64M => 64M. If not, another file in conf.d may set it again. Files are read in name order, so the last one wins. That's why ours starts with 99-.

You don't need a bigger memory_limit. PHP writes uploads to a temporary file, not to memory. In our test, a 40 MB upload worked fine with memory_limit = 32M. Only raise it if your app loads the whole file into memory, for example to resize a big image.

One more limit: max_file_uploads is 20 by default. That's the most files one form can send at once.

Different limits for each site

If each site has its own PHP-FPM pool, set the limits in that site's pool file instead, for example /etc/php/8.4/fpm/pool.d/shop.conf:

php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M

Then sudo php-fpm8.4 -t && sudo systemctl reload php8.4-fpm. Values set with php_admin_value can't be changed by the site's own files. In our test, a .user.ini that tried to set 2M was ignored.

No root access? Use .user.ini

If you can only change files in the website's folder, put a file named .user.ini in the site's main folder (where index.php is):

upload_max_filesize = 64M
post_max_size = 64M

This works with PHP-FPM. Two things to know from our test:

  • It can take up to 5 minutes. PHP reads the file again only every 300 seconds. A reload of PHP-FPM makes it work right away.
  • ini_set() in your code doesn't work for these two settings. PHP has already read the upload by the time your code runs. Our test page still said 2M after ini_set('upload_max_filesize', '64M').

A .htaccess file with php_value lines does nothing on Nginx.

On Apache

Apache doesn't have Nginx's 1 MB limit. In our test (Apache 2.4 with PHP-FPM), a 70 MB upload went through Apache to PHP, and only PHP's limits stopped it. So on Apache, step 3 is usually all you need.

Behind Cloudflare

If your site goes through Cloudflare's proxy, Cloudflare has its own limit. On the Free and Pro plans it's 100 MB per request, and bigger uploads get a 413 from Cloudflare, before they reach your server. For bigger files, upload to a subdomain that isn't proxied, or split the file into parts in the browser.

Keep it safe

  • Don't raise limits more than you need. Big limits let one visitor fill your disk or keep PHP workers busy.
  • Check what people upload. A bigger limit is also a bigger door for unwanted files. Never let uploads run as PHP: see hardening WordPress for how to block PHP in the uploads folder, and scanning uploads with ClamAV.
  • Watch the disk. Big uploads add up. See stop logs filling the disk for how to find what's using space.

Quick list

  • 413 page from Nginx: add client_max_body_size 64m; to the site's server block, then nginx -t and reload.
  • Upload fails quietly: raise upload_max_filesize in /etc/php/8.4/fpm/conf.d/99-uploads.ini.
  • Form comes back empty: raise post_max_size in the same file.
  • Changed php.ini but nothing happened: you probably changed the CLI one. Check with php-fpm8.4 -i.
  • .user.ini change not working: wait 5 minutes or reload PHP-FPM.
  • Over 100 MB behind Cloudflare: Cloudflare's own limit. Use a subdomain that isn't proxied.

Official sources

Next steps: if PHP shows a 502 or 504 instead, see fixing 502 and 504 errors. New server? Start with the security checklist for a PHP server.

ServerNest is being built to let you change PHP settings like these for each site from one simple dashboard. Join early access to try it first.

← Back to the security blog