You’re self-hosting n8n on Docker behind an Nginx reverse proxy. Your workflows run flawlessly with small test payloads and test webhooks.
Then, a real production request hits: a Stripe webhook carrying a heavy metadata object, an external form pushing a 25MB PDF attachment, or a web scraper dispatching a large JSON dataset.
Suddenly, the sender receives:
HTTP/1.1 413 Request Entity Too Large
or inside your Docker logs:
PayloadTooLargeError: request entity too large
[Image Guide / Prompt]:
- Screenshot Capture: Screenshot of Postman, Insomnia, or a dark terminal sending a POST request with a ~20MB file returning
413 Request Entity Too Largeand an HTML body containing<center>nginx</center>.- AI Generation Prompt: "A realistic screenshot of a dark-mode developer API client (Postman or Insomnia) showing a failed POST request to an n8n webhook endpoint with an HTTP 413 Payload Too Large error status badge in red, raw response body showing HTML header, ultra-clean software engineer UI, 4k."
The most infuriating part? The workflow execution log in n8n is completely blank. The request was dropped before the Webhook node ever received it.
Most quick forum posts say "just add client_max_body_size 64M; to Nginx." But if you only do that, you'll still hit a wall. In real-world self-hosting, there are three distinct layers that enforce payload limits, plus a hidden Node.js V8 memory heap trap.
Here is the complete engineering walkthrough to identify which layer is rejecting your request and the configuration required to fix all of them permanently.
The Webhook Chain: Three Layers, Three Limits
A webhook request passes through a multi-tiered pipeline before reaching your n8n canvas:
flowchart LR
Client["Webhook Sender<br/>(Stripe, Web Scraper, Form)"]
CF["Cloudflare Proxy<br/>(100MB Cap on Free/Pro)"]
Nginx["Nginx Reverse Proxy<br/>(client_max_body_size: 1MB Default)"]
n8n["n8n Express Server<br/>(N8N_PAYLOAD_SIZE_MAX: 16MiB Default)"]
V8["Node.js V8 Heap<br/>(max-old-space-size)"]
Node["Webhook Node Execution<br/>(n8n Canvas Workflow)"]
Client --> CF
CF -- "< 100MB" --> Nginx
Nginx -- "< 64MB" --> n8n
n8n --> V8
V8 --> Node
Each layer enforces its own payload cap, and the smallest limit always wins:
| Layer | Directive / Setting | Default Limit | Error Signature |
|---|---|---|---|
| Nginx | client_max_body_size |
1 MB | HTML page: <title>413 Request Entity Too Large</title>...<center>nginx</center>
|
| n8n (JSON / Body) | N8N_PAYLOAD_SIZE_MAX |
16 MiB |
PayloadTooLargeError: request entity too large in Docker logs |
| n8n (Multipart File) | N8N_FORMDATA_FILE_SIZE_MAX |
200 MiB | Upload rejected by busboy parser in container logs |
| Cloudflare | Plan-based upload limit | 100 MB (Free/Pro) | Cloudflare branded error page, Server: cloudflare header |
Step 1: Isolate Which Layer is Rejecting the Payload
Before editing configs, determine exactly where the payload is dropped:
- Send a 20 MB test payload through your public URL (full chain):
python3 -c "import json; print(json.dumps({'data': 'x' * 20_000_000}))" > payload.json
curl -s -i -X POST "https://n8n.yourdomain.com/webhook-test/your-path" \
-H "Content-Type: application/json" \
--data-binary @payload.json | head -n 20
- Send the identical payload directly to n8n from the host machine (bypassing Nginx):
curl -s -i -X POST "http://127.0.0.1:5678/webhook-test/your-path" \
-H "Content-Type: application/json" \
--data-binary @payload.json | head -n 20
How to diagnose the response:
-
Public URL returns 413 with
<center>nginx</center>, but local 127.0.0.1 succeeds: Nginx is the culprit. Proceed to Step 2. -
Direct curl to 127.0.0.1 returns 413 or logs
PayloadTooLargeError: n8n itself rejected it. Proceed to Step 3. -
Public URL returns
Server: cloudflare: Cloudflare's edge proxy rejected it before reaching your origin.
Step 2: Raise Nginx Limits & Adjust Timeouts
In /etc/nginx/sites-available/n8n (or /etc/nginx/conf.d/n8n.conf), scope the body size to your webhook endpoints so your administrative editor UI remains strictly protected:
[Image Guide / Prompt]:
- Screenshot Capture: Screenshot of VS Code or Neovim with Nginx syntax highlighting on a Linux server, showing the
location ~ ^/(webhook|webhook-test)/block withclient_max_body_size 64m;highlighted in line numbers.- AI Generation Prompt: "High-resolution screenshot of a code editor with a dark modern theme (One Dark / Dracula) displaying an nginx configuration file, highlighting
client_max_body_size 64m;andproxy_read_timeout 300s;within a location block, syntax-highlighted Nginx directives."
server {
listen 443 ssl http2;
server_name n8n.yourdomain.com;
# General protection for the editor UI & API
client_max_body_size 4m;
# Dedicated block for incoming Webhook payloads
location ~ ^/(webhook|webhook-test)/ {
client_max_body_size 64m;
# Prevent 504 Gateway Timeouts while uploading large payloads
client_body_timeout 120s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
# Disable buffering so large uploads stream directly to n8n
proxy_request_buffering off;
proxy_buffering off;
}
# Main editor & websocket block
location / {
proxy_pass http://127.0.0.1:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Verify the syntax and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Step 3: Tune n8n Environment Variables & Node.js V8 Heap
n8n parses JSON bodies using its internal Express body-parser middleware. By default, N8N_PAYLOAD_SIZE_MAX is capped at 16 MiB.
Furthermore, if you accept a 50 MB JSON payload, Node.js will parse that entire object tree into the V8 memory heap. By default, Node.js inside the official Alpine container runs with limited heap memory (~1.4 GB) and will crash with JavaScript heap out of memory.
[Image Guide / Prompt]:
- Screenshot Capture: A split-screen terminal showing
docker-compose.ymlon the left anddocker exec n8n printenv | grep N8Non the right confirming the 64M payload limit and 4096MB heap allocation.- AI Generation Prompt: "A developer workstation split-screen terminal in dark mode, showing a clean docker-compose.yml configuration with environment variables
N8N_PAYLOAD_SIZE_MAX=64andNODE_OPTIONS=--max-old-space-size=4096, modern terminal font (JetBrains Mono), clean contrast."
Update your docker-compose.yml:
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
# 1. Raise n8n JSON / raw body limit (in MiB)
- N8N_PAYLOAD_SIZE_MAX=64
# 2. Raise multipart/form-data binary upload limit (in MiB)
- N8N_FORMDATA_FILE_SIZE_MAX=250
# 3. CRITICAL: Store binary files on disk instead of holding them in RAM
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
# 4. Prevent Node.js V8 Heap OOM crashes on large JSON parsing (allocate 4GB heap)
- NODE_OPTIONS=--max-old-space-size=4096
# Generic timezone settings
- GENERIC_TIMEZONE=UTC
- TZ=UTC
volumes:
- n8n_data:/home/node/.n8n
# If using binary data mode filesystem, ensure local mount persists:
- ./n8n-local-files:/files
volumes:
n8n_data:
Warning: Running
docker restart n8ndoes NOT apply new environment variables. You must recreate the container:
docker compose up -d --force-recreate
Confirm that the variables are active inside the container:
docker exec n8n printenv | grep -E "N8N_PAYLOAD_SIZE_MAX|NODE_OPTIONS"
Step 4: Verification & End-to-End Test
Re-run your 20MB payload test through your public domain:
curl -s -o /dev/null -w "HTTP %{http_code} | sent %{size_upload} bytes | %{time_total}s\n" \
-X POST "https://n8n.yourdomain.com/webhook/your-path" \
-H "Content-Type: application/json" \
--data-binary @payload.json
[Image Guide / Prompt]:
- Screenshot Capture: Screenshot of the n8n Workflow canvas with the Webhook node glowing green with a checkmark ("Executed successfully"), showing an input payload of 20MB and a status badge of 200 OK.
- AI Generation Prompt: "UI screenshot of n8n automation canvas in dark mode, showing a Webhook node connected to a Code node, green success badges, node output inspector open showing a clean 200 OK response with a large JSON data payload."
Expected output:
HTTP 200 | sent 20000013 bytes | 1.84s
Check your n8n workflow execution history: the execution now appears immediately, and the node outputs the full payload without dropping or choking the container.
Production Architecture Best Practices
- Keep Nginx limit ≥ n8n limit: If n8n allows 64 MiB but Nginx allows 32 MB, Nginx will drop the connection before n8n ever sees it.
-
Never inline massive binaries as base64 in JSON: Base64 inflates payload size by ~33% and forces the V8 engine to allocate massive contiguous memory chunks. Always use
multipart/form-datawithN8N_DEFAULT_BINARY_DATA_MODE=filesystem. - If behind Cloudflare: Cloudflare Free and Pro plans hard-cap uploads at 100 MB. If you need payloads >100 MB, route webhooks through a DNS-only (grey-cloud) subdomain or have the client upload directly to S3/Cloudflare R2 and pass a pre-signed URL to n8n instead.
Originally published at GearFlow Lab.




Top comments (0)