DEV Community

Sanghun Yun
Sanghun Yun

Posted on Originally published at gearflowlab.com

Fix n8n Webhook 413 Payload Too Large on Docker & Nginx (The 3-Layer Solution)

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
Enter fullscreen mode Exit fullscreen mode

or inside your Docker logs:

PayloadTooLargeError: request entity too large
Enter fullscreen mode Exit fullscreen mode

n8n Webhook 413 Payload Too Large Error

[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 Large and 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:

  1. 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
Enter fullscreen mode Exit fullscreen mode
  1. 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
Enter fullscreen mode Exit fullscreen mode

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:

Nginx Configuration for n8n Webhook

[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 with client_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; and proxy_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";
    }
}
Enter fullscreen mode Exit fullscreen mode

Verify the syntax and reload Nginx:

sudo nginx -t && sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

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.

n8n Docker Compose Environment Settings

[Image Guide / Prompt]:

  • Screenshot Capture: A split-screen terminal showing docker-compose.yml on the left and docker exec n8n printenv | grep N8N on 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=64 and NODE_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:
Enter fullscreen mode Exit fullscreen mode

Warning: Running docker restart n8n does NOT apply new environment variables. You must recreate the container:

docker compose up -d --force-recreate
Enter fullscreen mode Exit fullscreen mode

Confirm that the variables are active inside the container:

docker exec n8n printenv | grep -E "N8N_PAYLOAD_SIZE_MAX|NODE_OPTIONS"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

n8n Webhook Execution Success

[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
Enter fullscreen mode Exit fullscreen mode

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

  1. 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.
  2. 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-data with N8N_DEFAULT_BINARY_DATA_MODE=filesystem.
  3. 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)