Uploading a 500 MB video or a large backup through a web app often fails with 413 Request Entity Too Large, a timeout, or a silent error. Each layer between the browser and your code has its own limit: the CDN, Nginx, PHP or Node, and the application. Here’s how to raise them safely, and when to use better upload methods.
The layers and their limits
| Layer | Setting | Default |
|---|---|---|
| Cloudflare (proxied) | Request body limit by plan | 100 MB on Free/Pro |
| Nginx | client_max_body_size |
1 MB |
| Nginx → app | proxy_read_timeout, client_body_timeout |
60 s |
| PHP | upload_max_filesize, post_max_size, max_execution_time, max_input_time |
2 MB / 8 MB |
| Node/Express | body parser limits (express.json({limit}), multer limits) |
~100 KB (JSON) |
| App/framework | Validation rules | App-specific |
The smallest limit wins.
Nginx
server {
# …
client_max_body_size 1G; # per server or per location
client_body_timeout 300s;
proxy_read_timeout 300s; # when proxying to an app
proxy_send_timeout 300s;
location /upload {
proxy_request_buffering off; # stream to the app instead of buffering to disk first
proxy_pass http://127.0.0.1:3000;
}
}
By default Nginx buffers the whole upload to a temp file before passing it on. That’s fine, but it needs disk space in client_body_temp_path. See Nginx 413 errors and Nginx 504 timeouts.
PHP
; /etc/php/8.3/fpm/conf.d/99-uploads.ini
upload_max_filesize = 1G
post_max_size = 1G ; must be >= upload_max_filesize
max_execution_time = 300
max_input_time = 300
memory_limit = 256M ; uploads stream to disk; memory needn't match file size
sudo systemctl reload php8.3-fpm
WordPress has its own guide: increase the WordPress upload size. See php.ini settings.
Node.js
Stream uploads to disk or object storage, instead of loading them into memory:
import multer from "multer";
const upload = multer({ dest: "/var/uploads/tmp", limits: { fileSize: 1024 * 1024 * 1024 } });
app.post("/upload", upload.single("file"), (req, res) => res.json({ ok: true, size: req.file.size }));
busboy (lower level), or streaming directly to S3 with @aws-sdk/lib-storage, avoids memory spikes.
Better: resumable or chunked uploads
Huge single requests fail on flaky connections, and hit proxy limits. Chunked, resumable uploads split files into pieces, and resume after interruptions:
- tus protocol (tusd server, Uppy client): widely used, open standard.
- Framework packages (Laravel chunk upload, Resumable.js and others).
Chunks also stay under Cloudflare’s per-request limit.
Best for very large files: direct-to-object-storage
Have the browser upload directly to S3-compatible storage (MinIO, AWS S3, Backblaze B2) with a presigned URL generated by your app:
- The client asks your API for an upload URL.
- The API returns a short-lived presigned PUT URL (or multipart upload URLs).
- The browser uploads straight to storage. Your VPS never carries the bytes.
- The client tells your API the upload finished. The API verifies it, and records it.
That saves bandwidth, disk space and app workers. See MinIO.
Security for uploads
- Validate type and size server-side (check content, not just the extension).
- Store uploads outside the web root, or in a location where scripts can’t execute. Block PHP in upload directories. See the ideal Nginx config for WordPress.
- Generate your own file names. Never trust user-supplied paths.
- Scan uploads if users share files with others (ClamAV).
- Rate-limit upload endpoints, and require authentication.
Disk and bandwidth planning
Large uploads fill disks quickly: temp files, the uploads themselves, and backups of them. Monitor free space, and consider a storage VPS or object storage for user files. See VPS storage planning and storage VPS.
Quick reference
| Layer | Key setting |
|---|---|
| Nginx | client_max_body_size, timeouts, proxy_request_buffering off |
| PHP | upload_max_filesize, post_max_size, time limits |
| Node | multer/busboy limits; stream, don’t buffer |
| Cloudflare | Per-request limit → chunking or bypass |
| Huge files | tus resumable uploads, or presigned S3 uploads |
Related: Nginx reverse proxy and Express security.