✨ Strapi MCP is now Generally Available - let your agents manage your Strapi content ✨

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 limit

Raise 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 buffering middleware 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::body middleware 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 start

Then 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

ResponseRejected by
Nginx HTML page, 413 Request Entity Too LargeReverse 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 messagestrapi::body (formidable.maxFileSize)
504 Gateway TimeoutProxy timeout, not a size limit
Provider error (S3, Cloudinary) in the logStorage provider limit
Upload works, then the process is OOM-killedMemory, 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.

Further reading

Theodore Kelechukwu OnyejiakuDevRel and Community | Software Developer | Technical Writer

Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.

Seeding Data in Strapi: Why Migrations are the Wrong Tool
EcosystemIntermediate·7 min read

Seeding Data in Strapi: Why Migrations are the Wrong Tool

Migrations run before Strapi's schema sync, so seeding a brand-new content type fails on the missing table. Use bootstrap instead, idempotently.

·September 9, 2026
Strapi and TypeScript: compiling when there are type errors
EcosystemAdvanced·6 min read

Strapi and TypeScript: compiling when there are type errors

A single type error stops your Strapi dev server. The tsconfig settings that let it build anyway, and when each one is actually appropriate.

·September 9, 2026
"Cannot read properties of undefined (reading 'attributes')" in Strapi
EcosystemIntermediate·6 min read

"Cannot read properties of undefined (reading 'attributes')" in Strapi

Strapi will not boot because a content type references a component or relation that does not exist. How to find the broken reference quickly.

·September 9, 2026