How to Fix Request Entity Too Large (HTTP 413) Errors
A 413 on a file upload usually arrives at the worst possible time. The form looked fine, the backend was deployed, the payload wasn't strange, and yet the browser or API client gets blocked before the request finishes. In practice, Request Entity Too Large is less about broken application logic and more about a size cap somewhere along the request path.
That's why the fix often feels inconsistent. One teammate raises a backend setting, another bumps a PHP limit, and the error still shows up because the first rejecting hop was a reverse proxy, a gateway, or middleware the app never reached. The right response is to trace the request from the browser to the origin, find the earliest limit, and change only the smallest relevant setting.
Table of Contents
- What the Request Entity Too Large Error Actually Means
- Mapping the Request Path From Browser to Backend
- Fixing 413 in Nginx, Apache, and HAProxy
- Raising Framework Limits in Express, Django, and PHP
- Upstream Limits in Cloudflare, AWS ELB, and Kubernetes Ingress
- When Increasing the Limit Is the Wrong Fix
- Verifying the Fix, Logging It, and Preventing Recurrence
What the Request Entity Too Large Error Actually Means
HTTP 413 means the request body exceeded a server-defined limit. MDN notes that HTTP Semantics standardized the status as “Payload Too Large”, replacing the older “Request Entity Too Large” wording, and production systems still use both names because logs, support tickets, and client libraries haven't fully converged yet. That older wording is still visible in many stacks, so seeing either phrase usually points to the same underlying event, a request body that's larger than the receiving component allows MDN's 413 status reference.
The key detail is where the error gets generated. A 413 is returned by the first hop that has a cap, not necessarily by the application code being edited at the time. If a reverse proxy or gateway rejects the payload before it reaches the app server, changing the backend alone won't help.
Practical rule: don't assume the layer you can edit is the layer that failed.
That distinction matters because the request path is layered. A browser can send a normal-looking upload, but a proxy, a web server, or middleware can enforce its own ceiling on the same body. The visible error message doesn't tell the whole story, and the same payload can succeed in one environment and fail in another because one upstream component has a tighter limit.
The safest mental model is simple. Treat 413 as a boundary problem, not a broken endpoint problem. The fix usually starts with identifying which boundary was hit, then lifting only that boundary enough to accept the intended workload.
Mapping the Request Path From Browser to Backend
The fastest way to stop guessing is to draw the request path in the same order the data travels. Start with the browser, then check DNS and any CDN, then move to the load balancer or reverse proxy, then the web server, then framework middleware, and finally the application handler. Each hop can impose a different cap, and each cap can fail in a slightly different way.

Where size limits usually hide
The browser itself can influence request shape through headers, multipart encoding, and retry behavior, but it usually isn't the component that returns the final 413. The more common blockers live in infrastructure. A CDN may reject a body before origin traffic is created, a load balancer may enforce request sizing at the edge, and a reverse proxy can stop the upload even though the backend is healthy.
The trap is that different layers limit different things. One component may care about the request body, another about headers, another about the request line, and another about multipart form fields. A payload that seems small as a file can become much larger once it's serialized into JSON or base64, which is why teams need to check the effective request size instead of the file size alone.
The most useful habit is to isolate the first rejection point. Send the same upload through each hop in turn, then compare what happens. If direct origin access works but the routed path fails, the rejecting limit sits upstream. If the request dies only after app middleware processes it, the framework is the actual gate.
See also the logging and trace examples in an IIS log analysis walkthrough for a practical way to confirm which component handled the request last.
A diagnosis checklist that actually narrows the search
- Check the client response: look at the response code, headers, and any gateway-specific request ID.
- Test the routed path and the origin path separately: if only the routed path fails, the limit is upstream.
- Compare payload serialization: a file, a JSON blob, and a multipart form don't consume the same number of bytes.
- Inspect the first device with a body-size policy: that's usually the component that generated the 413.
- Validate after every change: one successful upload doesn't prove the whole chain is fixed.
The point isn't to collect every possible limit. It's to find the first one that fires and stop there unless a later hop still rejects the same request.
Fixing 413 in Nginx, Apache, and HAProxy
The common web-layer fixes are direct, but they only solve one layer at a time. The right move is to raise the smallest relevant cap, reload the service, and then keep tracing upward if the request still fails. That keeps the configuration conservative and avoids inflating body limits everywhere by default.
Nginx
For Nginx, the usual directive is client_max_body_size. A safe starting change looks like this:
server {
client_max_body_size 16m;
}
If uploads are buffered, client_body_buffer_size can matter too, and large_client_header_buffers becomes relevant when the problem is header-related rather than body-related. After the change, reload Nginx with:
sudo nginx -t && sudo systemctl reload nginx
A useful reminder from a common Nginx baseline is that the default body limit is often 1 MB unless client_max_body_size is raised the operational baseline is summarized here. That doesn't mean every stack has that exact cap, only that the default is often much lower than teams expect.
Apache
Apache usually uses LimitRequestBody and, in some cases, LimitRequestFieldSize. A VirtualHost-level example looks like this:
<VirtualHost *:80>
LimitRequestBody 16777216
</VirtualHost>
If the site uses .htaccess, the directive must be allowed in that context, which is why a change that works in one environment does nothing in another. Reload Apache after the edit:
sudo apachectl configtest && sudo systemctl reload apache2
The practical issue with Apache is placement. A limit in the wrong scope can look correct in a review but never affect the request path that served the upload.
HAProxy
HAProxy can become the early gate in front of the origin. The directive often checked first is tune.http.maxrequest, and it interacts with frontend capacity and connection handling, so body-size symptoms sometimes appear alongside connection pressure. A minimal configuration adjustment may look like this:
global
tune.http.maxrequest 16384
Then validate and reload the service:
sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy
That reload matters because a stale config can leave operators thinking the limit changed when the old policy is still active.
The main trade-off across all three is the same. Increase only the layer that's failing, then re-test the full request path before touching the next one. Changing backend limits first and skipping the proxy check is one of the most common reasons 413 incidents linger.
See a separate Nginx and Apache troubleshooting reference for how quickly a front-layer rule can override a backend that looks correct on paper.
Raising Framework Limits in Express, Django, and PHP
A lot of 413s never make it to the web server at all. Middleware can reject the request before the reverse proxy or origin handler sees the payload, which is why framework limits need the same level of attention as web-server directives.
Express
Express's JSON and URL-encoded parsers default to 100 KB, so a payload that looks ordinary for batch APIs or base64-encoded uploads can still fail at the parser boundary Express parser defaults are described in this Node/Express guidance. The change is straightforward:
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
For binary uploads, express.raw can be useful, but buffering the entire request in memory is usually the wrong long-term design. A streaming parser such as busboy avoids holding the whole body in RAM and reduces the chance that a bigger limit turns into a memory problem later.
Operational preference: stream large payloads when possible, buffer only when the business case is narrow and controlled.
Django
Django has separate knobs for uploaded files and for general request data. DATA_UPLOAD_MAX_MEMORY_SIZE controls memory handling for request data, FILE_UPLOAD_MAX_MEMORY_SIZE affects file handling, and DATA_UPLOAD_MAX_NUMBER_FIELDS helps keep large forms from becoming a parsing problem. Those settings matter when a form looks small to a user but expands into a much larger multipart request.
The safe pattern is to align the Django limits with the actual upload workflow, not the largest possible file someone might attempt. If the app accepts lots of small attachments, the field-count setting can become the hidden bottleneck even when file size looks fine.
PHP
PHP still shows up in many stacks as the effective gate. The usual directives are upload_max_filesize, post_max_size, and, depending on the environment, related request handling limits in Apache or IIS. If upload_max_filesize is raised but post_max_size stays lower, the request can still fail because the full POST body exceeds the cap.
A clean fix keeps the values aligned with the intended workflow and then confirms whether the web server or reverse proxy has its own tighter ceiling. That prevents the common pattern where the PHP config looks correct but the upload still fails upstream.
The larger lesson is simple. Framework middleware often has the lowest limit in the stack. When the app layer rejects the request first, server-level tuning alone won't change the outcome.
Upstream Limits in Cloudflare, AWS ELB, and Kubernetes Ingress
Upstream components are where many teams lose time, because the request never reaches the origin and the app logs stay empty. A 413 from the edge usually means the origin settings are irrelevant until the upstream boundary is confirmed.
The cap can live before the app ever sees the request
Cloudflare, load balancers, API gateways, WAFs, and ingress controllers can all reject a body independently of the application. Microsoft documents cases where Bot Framework and Teams return RequestEntityTooLarge even when the visible limit seems acceptable, because the serialized payload plus headers push the effective request over the edge Microsoft's guidance and discussion are here. Apigee also recommends reducing the client payload first, not just increasing the limit on the proxy side.
That difference matters in incident response. Backend-only fixes don't touch the boundary that already rejected the request, so the upload still fails even though the app server is now permissive. The response headers and gateway request IDs often reveal whether the request died at the edge or on the origin.
Common upstream body-size caps
| Platform | Default body cap | Where to change it |
|---|---|---|
| Cloudflare | Plan-dependent limits, edge enforced | Cloudflare dashboard or architecture change |
| AWS ELB or ALB | Protocol and service path dependent | Load balancer settings, architecture, or upload route |
| Kubernetes Ingress | Controller-specific limit | Ingress annotations or controller config |
| Apigee | Gateway policy enforced | API proxy or client payload redesign |
Cloudflare, AWS, and ingress controllers don't all surface limits the same way, so the safe response is to verify the exact rejection point with logs and headers rather than assuming the origin is guilty. The same request can succeed via a direct path and fail through the edge, which is usually the clue that the upstream layer owns the cap.
See software for load balancing guidance for additional context on where proxy and edge behavior can diverge from origin behavior.
The practical takeaway is to test both routes. If the direct origin upload succeeds, the upstream service is the one to inspect. If both fail, keep moving inward until the first rejecting hop is clear.
When Increasing the Limit Is the Wrong Fix
Raising the cap solves the symptom, not the design problem. That can be the right trade-off for a trusted admin workflow, but it's a poor default for public-facing or high-volume systems because larger request bodies increase the blast radius of abuse, resource exhaustion, and memory pressure.
Better patterns for large submissions
Chunked uploads split one large file into smaller requests, which keeps each request under the limit. Resumable uploads go further by allowing retries after interruption, which is useful for unstable clients and long transfers. Direct-to-storage signed URLs bypass the app server entirely for the heaviest uploads, and streaming ingestion with busboy or similar parsers keeps memory use flatter than buffering the whole payload.
That also aligns with the broader operational advice in the SwiftNet Wifi storage tips on compressing large files, where shrinking the payload before transfer is often the easiest path to fewer upload failures.
A quick decision table
| Situation | Better pattern |
|---|---|
| Large files, weak retry tolerance | Resumable uploads |
| Large files, app server should stay thin | Direct-to-storage signed URL |
| Structured events or API batches | Streaming ingestion |
| Intermittent mobile or browser clients | Chunked uploads |
The decision usually comes down to client capability and how much state the server should hold. If the payload can be split without losing meaning, split it. If the upload must be large and reliable, keep the data out of the app process as early as possible.
A bigger limit is a convenience choice, not a design strategy.
The safest default is to make the payload smaller or more streamable first, then increase the cap only when the business case is clear and the upstream path has already been verified.

Verifying the Fix, Logging It, and Preventing Recurrence
A fix is only real after the same payload succeeds through the same path that failed before. Use curl with a representative body, not a tiny test file that never approaches the limit, and repeat the check against the routed endpoint and the origin path separately. If a JSON API is involved, test both --data-binary and the actual serialized request pattern the client sends.
The logs should prove the request size, not just the status code. Nginx can expose body-related fields such as $body_bytes_received, Apache can capture request I/O with mod_logio, and framework middleware should log the request-size boundary that processed the upload. For PHP-heavy stacks, a PHP logging reference helps teams make those request-size traces easier to correlate during repeat incidents.
Set alerts before users hit the wall. Watch for 413 response spikes and for near-limit request sizes that keep appearing in logs, because those are the early signs that a new integration or client release is close to breaking. Add a synthetic upload probe to staging and production so a tightened limit shows up in telemetry before a customer does.

Fivenines helps teams spot request failures, proxy issues, and upload-related regressions before they turn into customer tickets. If this incident pattern looks familiar, visit Fivenines to see how centralized infrastructure monitoring, uptime checks, and alerting can make 413 troubleshooting faster the next time a limit changes unexpectedly.