You raise the upload limit in Strapi, restart, try again, and get the same thing:
HTTP 413
The uploaded file exceeds the maximum allowed asset size.The frustrating part is that your configuration is often correct; it is just not the only limit in the path. A file upload passes through four independent gates, and every one of them can reject it with a similar-looking error.
Browser ──▶ Reverse proxy ──▶ body middleware ──▶ Upload plugin ──▶ Storage provider
client_max_body_size formidable.maxFileSize sizeLimit provider limitRaise them in that order and check after each one.
Gate 1: the reverse proxy
Nginx defaults to 1 MB. If you are behind Nginx and have not changed this, nothing else matters, because Nginx rejects the request before Strapi is involved, and returns its own HTML 413 page rather than a Strapi JSON error. That difference is the tell.
server {
client_max_body_size 512M;
# Big uploads take time
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_request_buffering off; # stream instead of buffering to disk first
}Equivalents elsewhere:
- Apache:
LimitRequestBody 536870912 - Traefik: no request size limit by default; check any
bufferingmiddleware you added - AWS ALB: no configurable body limit, but the 60-second default idle timeout will kill slow uploads, so raise it
- Cloudflare: hard limits by plan (100 MB on Free/Pro). Cloudflare will not let a 500 MB upload through regardless of your configuration. Upload direct-to-storage, or use a subdomain that bypasses the proxy.
Gate 2: the body middleware
Strapi parses multipart requests with koa-body, which uses formidable for files. Configure it in config/middlewares:
export default [
'strapi::logger',
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::query',
{
name: 'strapi::body',
config: {
formLimit: '512mb', // form body
jsonLimit: '512mb', // JSON body
textLimit: '512mb', // text body
formidable: {
maxFileSize: 512 * 1024 * 1024, // bytes: this is the one for files
},
},
},
'strapi::session',
'strapi::favicon',
'strapi::public',
];formidable.maxFileSize is the value that governs uploaded files. formLimit, jsonLimit, and textLimit cover the other body types and are worth raising alongside it, but they are not what caps your video.
Do not replace the array. A very common mistake is to write config/middlewares.ts containing only the strapi::body entry. That removes strapi::security, strapi::cors, strapi::errors, and the rest, so the admin panel breaks and you get CORS failures that look unrelated. Keep the full default list and modify the one entry.
Gate 3: the Upload plugin
This is the one people miss, and it is the reason the original forum thread went unresolved.
The Upload plugin has its own sizeLimit, and it lives in config/plugins, not in a config/uploads file, which is not a thing Strapi reads.
export default ({ env }) => ({
upload: {
config: {
sizeLimit: 512 * 1024 * 1024, // bytes
},
},
});The default is 1000000000 (1 GB). If you are stuck at a lower number, something else in the chain is the cause, but if you need to go above 1 GB, this is the value to raise.
Note that it goes in upload.config.sizeLimit, not upload.config.providerOptions.sizeLimit. The local provider still honours the providerOptions position, but it emits a deprecation warning and it takes precedence over the real setting, so a stale providerOptions.sizeLimit will silently override the value you just changed.
Putting this in the wrong file produces exactly the symptom from the forum thread: the setting appears to be applied, Strapi restarts cleanly, and the limit does not move. Nothing errors, because Strapi has no reason to complain about a config file it does not read.
Gate 4: the storage provider
Your provider has limits of its own, and they are not negotiable from Strapi.
- Local (
public/uploads): limited by disk space, and unsuitable for multi-instance deployments because each instance has its own disk. - AWS S3: 5 GB via a single
PutObject. Beyond that requires multipart upload; check whether your provider package supports it. - Cloudinary: 100 MB on free plans, higher on paid; video has separate limits.
- Strapi Cloud: non-image uploads are capped at 200 MB on every plan, enforced at the infrastructure level, and cannot be raised via the
strapi::bodymiddleware config. Images have a separate, memory-driven recommended maximum that varies by format and plan; you raise it by disabling Responsive friendly upload and Size optimization in the Media Library settings, not by changing a limit. Switching to S3 or Cloudinary does not help, because the resize still runs in the instance's memory. See Upload size limits for Strapi Cloud.
After changing anything
# Restart in development
npm run develop
# Rebuild and restart in production
NODE_ENV=production npm run build
NODE_ENV=production npm run startThen test at the boundary, not from the admin panel. The API gives you a clearer error:
curl -i -X POST https://cms.example.com/api/upload \
-H "Authorization: Bearer $API_TOKEN" \
-F "files=@./big-video.mp4"Reading the error you get
| Response | Rejected by |
|---|---|
Nginx HTML page, 413 Request Entity Too Large | Reverse proxy (client_max_body_size) |
Strapi JSON, "The uploaded file exceeds the maximum allowed asset size" | Upload plugin (sizeLimit) |
Strapi JSON, 413 with a formidable message | strapi::body (formidable.maxFileSize) |
504 Gateway Timeout | Proxy timeout, not a size limit |
| Provider error (S3, Cloudinary) in the log | Storage provider limit |
| Upload works, then the process is OOM-killed | Memory, see below |
When you should not raise the limits
Past a certain size, proxying uploads through Strapi is the wrong architecture:
Memory. Multipart parsing buffers, and image processing with sharp is memory-hungry. A 512 MB upload on a 1 GB container will get the process OOM-killed mid-request. Strapi Cloud's documentation makes a memory-based recommendation for image uploads for this reason. If you must handle large files in a constrained container, disable responsive image generation for them.
Timeouts. A 2 GB upload over a slow connection holds a request open for minutes, occupying a worker and colliding with every proxy and load balancer timeout in the path.
Cost and latency. Bytes travel client → your server → object storage, so you pay for and wait on the round trip.
For files above a few hundred megabytes, use direct-to-storage uploads: your backend issues a presigned URL, the browser uploads straight to S3 or Cloudinary, and you store the resulting URL in a Strapi field. The file never passes through Strapi.
Complete working example, ~500 MB
client_max_body_size 512M;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_request_buffering off;export default [
'strapi::logger',
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::query',
{
name: 'strapi::body',
config: {
formLimit: '512mb',
jsonLimit: '512mb',
textLimit: '512mb',
formidable: { maxFileSize: 512 * 1024 * 1024 },
},
},
'strapi::session',
'strapi::favicon',
'strapi::public',
];export default ({ env }) => ({
upload: {
config: {
provider: 'aws-s3',
providerOptions: {
s3Options: {
region: env('AWS_REGION'),
params: { Bucket: env('AWS_BUCKET') },
},
},
sizeLimit: 512 * 1024 * 1024,
breakpoints: { large: 1000, medium: 750, small: 500 },
},
},
});Remember to add your bucket hostname to the CSP img-src and media-src directives in strapi::security, or the Media Library will show broken thumbnails.




