One Upload Limit, Four Layers
An 8 MiB upload cap is not one setting. FastAPI never rejects big files; the real gates are the browser, nginx, and Next.js.
TL;DR
An 8.5 MiB upload stalled silently, and debugging showed the FastAPI backend wasn't the culprit. Instead, size limits hide across the whole stack: browser Blob.size, nginx's client_max_body_size, and Next.js's bodySizeLimit, which counts multipart overhead rather than just the file. Consistent enforcement means aligning one limit constant across all four layers.
During a routine QA session for KotaPortal, uploading an 8.5 MiB attachment stalled with no clear message. The interface simply stopped responding, leaving the file in limbo. The initial assumption was that the backend API layer must be enforcing a strict per-file cap and silently dropping the oversized request. Extensive debugging of the route handlers revealed a complete reversal of that assumption. The API layer was not rejecting the file at all. The actual rejection gates were hiding elsewhere in the stack, operating as a chain of silent defaults. The most user-friendly gate is actually the client side. The browser can pre-validate the file before a single byte is transmitted over the network. The File interface inherits from Blob, exposing a read-only size property in bytes, which has been a baseline capability since July 2015 [5]. Checking this property allows the application to reject oversized files instantly, providing immediate and clear feedback to the user. If the file bypasses the browser validation, it immediately hits the reverse proxy. By default, nginx sets the client_max_body_size directive to 1m. Any request exceeding this threshold triggers a 413 Request Entity Too Large response. This check is valid in the http, server, or location context. Configuring this directive requires careful placement, and setting it to 0 disables the check entirely, which is rarely advisable for public-facing applications. Unfortunately, browsers cannot correctly display this raw error page, leaving users confused when the proxy silently blocks the upload [2]. Should the proxy allow the request through, it reaches the Next.js layer. Next.js server actions cap the request body at 1MB by default via the serverActions.bodySizeLimit configuration. Crucially, this cap applies to the raw HTTP body, which includes all multipart boundaries, part headers, and field metadata bytes, not just the file payload itself. This distinction is critical because an 8 MiB file will actually generate a request body larger than 8 MiB due to the multipart encoding overhead. The official documentation advises maintaining approximately 10 to 20 kilobytes of headroom when setting this value, such as configuring it to '3mb' for a 3 megabyte target [1]. Finally, the request arrives at the FastAPI backend. Handling uploads here requires the python-multipart library. The official documentation explicitly notes that reading a file as bytes consumes memory proportional to the file size, making it unsuitable for large payloads [3]. Using UploadFile is safer, as it delegates to the underlying Starlette implementation, which spools small files into memory and larger files to disk. The Starlette MultiPartParser, by contrast, only raises a "Part exceeded maximum size of 1024KB." error for non-file parts. For actual file parts, the data streams into a SpooledTemporaryFile where the max_size parameter acts merely as a memory-to-disk spool threshold, not a hard rejection limit. The parser also enforces a maximum of 1000 files and 1000 fields, but it will not block a single large file based on size alone [4]. This architecture explains the silent failure observed during testing. In the browser to nginx to Next.js to FastAPI pipeline, nginx rejects first at its configured cap. Next.js server actions reject raw bodies above the body size limit. Starlette never rejects files by size. Therefore, enforcing a consistent one upload limit four layers strategy requires aligning a single constant, such as 8 MiB, across all four stages. The client checks the file size, nginx adjusts the client maximum body size, Next.js updates the body size limit, and the application layer performs an explicit final check to guarantee the upload succeeds or fails with a clear, actionable message.