n8n Troubleshooting: Fix Webhooks, Execution Timeouts, and Reverse Proxy Errors

Updated on Aug 18, 2026
Mila H
10 MINS READ
Table of Contents
troubleshoot n8n errors

If you've ever tried to troubleshoot n8n errors after a fresh install, you already know the difficulties. Everything works fine during setup, then things go wrong after you push to production. This guide covers the most common n8n errors, including webhook URLs not firing, 502 and 504 errors behind Nginx or Caddy, workflows timing out, and environment variables that silently disappear after a Docker Compose migration.

You need n8n running in Docker on a Linux VPS, a domain with DNS pointed to your server, and either Nginx or Caddy as a reverse proxy. If you haven't set it up, you can check this guide on Installing n8n on a VPS.

1. n8n Webhook URLs Not Firing

This is the most common issue for self-hosted n8n. The symptom is that your webhook works perfectly in test mode when you click Listen for test event, but in production, it returns a 404 or never triggers.

When n8n runs behind a reverse proxy, it builds the webhook URL internally by combining:

HTML/XML
N8N_PROTOCOL, N8N_HOST, and N8N_PORT

Since n8n listens on port 5678 internally, the generated URL looks like:

HTML/XML
http://your-server:5678/webhook/…

This is wrong for external callers who reach your server on port 443. The fix is to override the webhook URL manually.

Fix 1: Set WEBHOOK_URL in Docker Compose

To troubleshoot n8n errors for Webhook, open your docker-compose.yml file:

Bash
nano ~/n8n/docker-compose.yml

Add or update the environment section:

YAML
services:  n8n:    image: docker.n8n.io/n8nio/n8n    environment:      - WEBHOOK_URL=https://your-domain.com/      - N8N_PROXY_HOPS=1      - N8N_HOST=your-domain.com      - N8N_PROTOCOL=https      - N8N_PORT=5678

Apply the changes:

Bash
docker compose down && docker compose up -d

Expected output:

HTML/XML
[+] Running 1/1 ✔ Container n8n  Started

Fix 2: Verify the Webhook URL Is Correct Inside the Container

After restarting, you must check that the environment variables are loaded correctly:

Bash
docker exec -it n8n printenv | grep -i webhook

Expected output:

HTML/XML
WEBHOOK_URL=https://your-domain.com/

If you see localhost:5678 here, the variable did not load. Check for typos in your docker-compose.yml or ensure your .env file is referenced properly.

Fix 3: Test the Production Webhook with curl

N8n has two webhook URLs, one for testing and one for production. Make sure you're using the Production URL without the word test in it:

Bash
curl -X POST https://your-domain.com/webhook/your-path \  -H "Content-Type: application/json" \  -d '{"test": "hello"}'

Expected output:

JSON
{  "status": "success"}

If you get a 404 error, double-check that:

  • The workflow is active, not just saved.
  • You are using the production URL, not the test URL.
  • The workflow has been saved at least once. Unsaved workflows do not register webhooks.

Fix 4: Set N8N_PROXY_HOPS for Proxy Header Trust

When n8n sits behind a reverse proxy, it needs to know how many proxy layers are in front of it so it can trust the X-Forwarded-* headers:

YAML
environment:  - N8N_PROXY_HOPS=1

Also, this is documented in the official n8n docs as a required step when using any reverse proxy.

2. How to Troubleshoot n8n Errors Behind Nginx (502 / 504)

502 Bad Gateway and 504 Gateway Timeout are the most common errors when you troubleshoot n8n errors in production. They almost always come from one of three causes:

  • Nginx's default read timeout is too low.
  • Buffer limits on large webhook payloads.
  • WebSocket support is missing for the n8n editor.

Check if your error happens at a consistent time, for example, always around 60 seconds. That is a clear sign of a proxy timeout, not an application bug:

Bash
# Watch n8n logs in real timedocker logs -f n8n # Check Nginx error logssudo tail -f /var/log/nginx/error.log

Full Working Nginx Config for n8n

Create or replace your Nginx site config:

Bash
sudo nano /etc/nginx/sites-available/n8n

Paste the following complete configuration:

Bash
# WebSocket upgrade mappingmap $http_upgrade $connection_upgrade {    default upgrade;    ''      close;} server {    listen 443 ssl http2;    server_name your-domain.com;     ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;     location / {        proxy_pass http://localhost:5678/;        proxy_http_version 1.1;         # Headers        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_set_header X-Forwarded-Host  $host;        proxy_set_header X-Forwarded-Port  $server_port;         # WebSocket support        proxy_set_header Upgrade    $http_upgrade;        proxy_set_header Connection $connection_upgrade;         # Timeouts — increase for long-running workflows        proxy_read_timeout    3600s;        proxy_send_timeout    3600s;        proxy_connect_timeout 300s;         # Disable buffering (important for n8n streaming responses)        proxy_buffering off;        proxy_cache     off;        chunked_transfer_encoding off;         # Allow large webhook payloads        client_max_body_size 50M;    }} server {    listen 80;    server_name your-domain.com;    return 301 https://$host$request_uri;}

Test and reload Nginx:

Bash
sudo nginx -tsudo systemctl reload nginx

Key Settings:

Setting Default Recommended Why
proxy_read_timeout 60s 3600s Long workflows exceed 60s easily
proxy_send_timeout 60s 3600s Prevents drops on large responses
client_max_body_size 1M 50M Large JSON payloads from webhooks
proxy_buffering on off Required for n8n's streaming output
proxy_http_version 1.0 1.1 Required for WebSocket and keepalive

If n8n is in the Same Docker Network as Nginx

Do not use localhost as the proxy target if both containers are on the same Docker network. Use the container name instead:

Bash
proxy_pass http://n8n:5678/;

And make sure both services share the same Docker network in docker-compose.yml:

YAML
services:  nginx:    networks:      - n8n_net  n8n:    networks:      - n8n_net networks:  n8n_net:

3. Fix Caddy Reverse Proxy Errors in n8n

Caddy is simpler to configure than Nginx, but it still has some troubles when used with n8n. The most common issue is n8n generating webhook URLs with :5678 appended because Caddy is not forwarding the correct port headers.

Working Caddyfile for n8n

To troubleshoot n8n errors for the Caddy reverse proxy, adjust the file:

Bash
sudo nano /etc/caddy/Caddyfile
Bash
your-domain.com {    reverse_proxy localhost:5678 {        header_up X-Forwarded-Proto https        header_up X-Forwarded-Port  443        header_up Host              {host}        flush_interval              -1    }}

The flush_interval -1 line disables response buffering, which is important for n8n's real-time log streaming.

Reload Caddy:

Bash
sudo systemctl reload caddy

Test that the headers are passing correctly:

Bash
curl -I https://your-domain.com/webhook/test

Look for X-Forwarded-Proto: https in the response headers. If it's missing, the proxy headers are not being forwarded.

If n8n is in a Docker Container and Caddy is on the Host

Bash
your-domain.com {    reverse_proxy localhost:5678 {        header_up X-Forwarded-Proto https        header_up X-Forwarded-Port  443    }}

If both are in Docker Compose, use the container name:

Bash
your-domain.com {    reverse_proxy n8n:5678 {        header_up X-Forwarded-Proto https        header_up X-Forwarded-Port  443    }}

4. Fix Execution Timeouts on Long Workflows in n8n

When you troubleshoot n8n errors related to long workflows stopping mid-run, the culprit is usually one of two things:

  • The n8n execution timeout environment variable.
  • The reverse proxy is cutting the connection before n8n finishes.

N8n uses two separate environment variables for timeouts:

Variable Default Description
EXECUTIONS_TIMEOUT -1 (disabled) Default timeout in seconds for all workflows
EXECUTIONS_TIMEOUT_MAX 3600 Maximum time in seconds a user can set per workflow

Important Note: EXECUTIONS_TIMEOUT values are in seconds, not milliseconds. Setting EXECUTIONS_TIMEOUT=3600 means 1 hour.

Set Execution Timeout in Docker Compose

YAML
services:  n8n:    image: docker.n8n.io/n8nio/n8n    environment:      - EXECUTIONS_TIMEOUT=7200        # 2 hours default      - EXECUTIONS_TIMEOUT_MAX=14400   # 4 hours max per workflow

Apply the changes:

Bash
docker compose down && docker compose up -d

Verify the variables loaded:

Bash
docker exec -it n8n printenv | grep EXECUTIONS

Expected output:

HTML/XML
EXECUTIONS_TIMEOUT=7200EXECUTIONS_TIMEOUT_MAX=14400

Fix for Code Node Timeouts (Task Runner)

If you upgraded n8n to version 1.x or 2.x and your Code nodes now time out after 5 minutes, the new task runner system may be the cause.

Add this to your environment:

YAML
environment:  - N8N_RUNNERS_ENABLED=true  - N8N_RUNNERS_TASK_TIMEOUT=600000    # 10 minutes in milliseconds

If the problem persists, you can disable the new runner system to fall back to the older method:

YAML
environment:  - N8N_RUNNERS_ENABLED=false

Check What Is Actually Timing Out

Run this to see what is happening when a workflow stops:

Bash
docker logs n8n --since 30m | grep -i "timeout|error|killed"

If you see no timeout-related log entries from n8n but the execution still stopped, the timeout is coming from your reverse proxy; go back to Section 2 and increase proxy_read_timeout.

5. Fix n8n Broken Environment Variables After Moving to Docker Compose

When you migrate n8n from a bare Docker run command to Docker Compose, environment variables often stop working, especially if you used an .env file incorrectly.

Step 1: Check if Your .env File Is in the Right Place

Docker Compose automatically reads a .env file only if it is in the same directory as your docker-compose.yml.

Bash
ls -la ~/n8n/

You should see:

HTML/XML
docker-compose.yml.env

If the .env file is missing, create it:

Bash
cp .env.example .envnano .env

Step 2: Verify Docker Compose Picks Up the Variables

Bash
docker compose config

This command prints the fully resolved configuration. Search for your variable in the output:

Bash
docker compose config | grep WEBHOOK_URL

Expected output:

HTML/XML
WEBHOOK_URL: https://your-domain.com/

If the variable is blank or missing, the .env file is not being loaded. Check for syntax errors, no spaces around =, no quotes unless necessary:

Bash
# CorrectWEBHOOK_URL=https://your-domain.com/ # Wrong: spaces break variable loadingWEBHOOK_URL = https://your-domain.com/

Step 3: Reference Variables Correctly in docker-compose.yml

There are two ways to pass variables. Use one, not both:

Method A. inline value, no .env file needed:

YAML
environment:  - WEBHOOK_URL=https://your-domain.com/  - N8N_PROXY_HOPS=1

Method B. reference from .env file:

YAML
environment:  - WEBHOOK_URL=${WEBHOOK_URL}  - N8N_PROXY_HOPS=${N8N_PROXY_HOPS}

Your .env file:

Bash
WEBHOOK_URL=https://your-domain.com/N8N_PROXY_HOPS=1

Step 4: Force a Clean Restart

Docker Compose may cache old values. Do a full restart to make sure fresh variables are applied:

Bash
docker compose downdocker compose up -d

Verify all key variables inside the running container:

Bash
docker exec -it n8n printenv | grep -E "WEBHOOK|N8N_HOST|N8N_PROTOCOL|EXECUTIONS"

Expected output:

HTML/XML
WEBHOOK_URL=https://your-domain.com/N8N_HOST=your-domain.comN8N_PROTOCOL=httpsEXECUTIONS_TIMEOUT=7200

Useful n8n Debug Commands Cheat Sheet

When you need to troubleshoot n8n errors quickly without reading through logs manually, these commands save time:

Bash
# View all n8n container logs (last 100 lines)docker logs n8n --tail 100 # Follow logs in real timedocker logs -f n8n # Check all environment variables inside containerdocker exec -it n8n printenv # Test a webhook endpoint directlycurl -v -X POST https://your-domain.com/webhook/your-path \  -H "Content-Type: application/json" \  -d '{"key":"value"}' # Check if port 5678 is reachable locally (bypassing reverse proxy)curl -v http://localhost:5678/healthz # Check Nginx error logsudo tail -50 /var/log/nginx/error.log # Test Nginx config before reloadingsudo nginx -t # Reload Nginx without downtimesudo systemctl reload nginx # Reload Caddysudo systemctl reload caddy # Check Docker network to confirm containers can see each otherdocker network inspect bridge # Inspect which networks a container is ondocker inspect n8n | grep -A 20 Networks

Running Complex or Scaled Workflows

If your n8n instance handles high-volume automations or long-running jobs, a single-container setup may not be enough. Queue mode lets you separate the main process from workers, so one slow workflow does not block others.

To troubleshoot n8n errors related to performance and scaling at the same time, read the n8n Queue Mode scaling guide, which covers Redis integration, worker configuration, and how to set EXECUTIONS_MODE to queue.

A reliable Linux VPS with consistent uptime is also important. If your server runs out of memory or restarts unexpectedly, all active executions are lost. For stable n8n hosting with enough RAM for Docker and a database, take a look at PerLod Linux VPS plans; they're a solid base for production automation setups.

Conclusion

Most n8n production issues depend on a few root causes, such as wrong WEBHOOK_URL, Nginx/Caddy timeouts that are too short, execution timeout variables set incorrectly, and .env files not loading properly. When you troubleshoot n8n errors, always start by checking your environment variables inside the container, then verify your reverse proxy logs, and finally test your webhook URL directly with curl.

For domain setup and SSL, a proper domain pointing to your VPS IP is required for HTTPS webhooks. You can easily register and manage domains at PerLod Domain Registration.

It tells n8n to trust the X-Forwarded-* headers from one proxy layer in front of it. Without this, n8n may read the wrong IP or protocol and generate incorrect webhook URLs.

Both work well. Caddy is simpler and handles SSL automatically. Nginx gives you more control over timeouts and buffers. For most self-hosted setups, Caddy is faster to configure correctly.

That is Nginx's default proxy_read_timeout. Set it to 3600s or higher in your Nginx config.