n8n Webhook Not Working? Fix 404 Errors Behind Nginx, Traefik & Caddy
The complete guide to fixing n8n webhook 404 errors on self-hosted deployments. Covers WEBHOOK_URL, N8N_PROXY_HOPS, Nginx config, and the test-vs-production webhook trap.
The Most Common Self-Hosted n8n Problem
You set up n8n on your VPS, build a workflow with a Webhook trigger, test it in the editor -- and it works perfectly. Then you activate the workflow, send a request from an external service, and get:
{"code": 404, "message": "The requested webhook is not registered."}
This is the single most reported issue in the n8n community forum. It affects nearly every self-hosted deployment behind a reverse proxy, and the root cause is almost always a misconfigured environment variable or a misunderstanding of how n8n registers webhooks.
Here's how to fix it permanently.
The Two Environment Variables That Fix 90% of Webhook Issues
WEBHOOK_URL
This is the strongest override. It tells n8n exactly what base URL to use for all webhook registrations. If you're behind a reverse proxy, this should be your public domain:
WEBHOOK_URL=https://n8n.yourdomain.com/
Without this, n8n constructs webhook URLs from its internal view of the network -- which behind a proxy is typically http://localhost:5678. External services can't reach that.
N8N_PROXY_HOPS
This tells n8n how many reverse proxies sit between it and the internet. For most setups (one Nginx, Traefik, or Caddy instance), set it to 1:
N8N_PROXY_HOPS=1
When this is set, n8n reads the X-Forwarded-* headers from your proxy to determine the real protocol, host, and client IP. Without it, n8n ignores these headers entirely.
If your webhook URLs show http:// instead of https://, or show port 5678 in the URL, you're missing one or both of these variables.
Complete Docker Compose Environment
Here's a production-ready environment configuration that prevents webhook issues:
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- N8N_HOST=n8n.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourdomain.com/
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=UTC
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=your-secure-password
volumes:
- n8n_data:/home/node/.n8n
Note the key settings: N8N_PROTOCOL=https, WEBHOOK_URL with the full public domain including trailing slash, and N8N_PROXY_HOPS=1.
Test Webhooks vs Production Webhooks
This catches a lot of people. n8n has two webhook paths that behave completely differently:
| Feature | Test Webhook | Production Webhook |
|---|---|---|
| URL path | /webhook-test/{id} | /webhook/{id} |
| When active | Only while "Listen for Test Event" is open | Only when workflow is Active |
| Lifespan | Single call, or 120-second timeout | Permanent (until workflow deactivated) |
| Shows data in editor | Yes | No |
| Requires active workflow | No | Yes |
The Trap
You build a workflow, click "Listen for Test Event", send a request to /webhook-test/..., and it works. You then give the same test URL to an external service. It works once (because the test listener was still active), then stops. Or you use the production URL (/webhook/...) but forget to activate the workflow.
The fix: Always use the production URL (/webhook/...) for external integrations, and always toggle the workflow to Active before expecting it to receive data.
Nginx Configuration for n8n
This is the proven Nginx server block for n8n behind a reverse proxy:
server {
listen 80;
server_name n8n.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name n8n.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:5678;
# Required: Forward client identity
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;
# Required: WebSocket support for editor
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Required: Allow large webhook payloads
client_max_body_size 50m;
# Recommended: Increase timeouts for long workflows
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# Recommended: Disable buffering for real-time events
proxy_buffering off;
proxy_cache off;
}
}
Why Each Header Matters
- X-Forwarded-Proto -- Without this, n8n doesn't know the connection is HTTPS. Webhook URLs will show
http://even though your site uses SSL. - X-Forwarded-Host -- Tells n8n the real hostname. Without it, webhook URLs may contain
localhostor the container's internal hostname. - X-Forwarded-For -- Passes the client's real IP. Important for rate limiting and logging.
- Upgrade + Connection -- Enables WebSocket connections. Without these, the n8n editor's real-time features (including test webhook listening) break silently.
- client_max_body_size -- Default Nginx limit is 1 MB. Webhook payloads with file attachments need more.
If WebSocket headers are missing, the n8n editor will appear to work but "Listen for Test Event" will never receive data. You'll think webhooks are broken when it's actually the WebSocket connection that's failing.
Traefik Configuration
If you're using Traefik instead of Nginx, add these labels to your n8n Docker Compose service:
services:
n8n:
image: n8nio/n8n:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
Traefik handles X-Forwarded headers automatically, but you still need WEBHOOK_URL and N8N_PROXY_HOPS in your n8n environment.
Caddy Configuration
Caddy is the simplest option for n8n reverse proxying. It handles SSL, WebSocket, and forwarded headers automatically:
n8n.yourdomain.com {
reverse_proxy localhost:5678
}
That's it. Caddy auto-provisions Let's Encrypt certificates, sets all required X-Forwarded headers, and handles WebSocket upgrades by default. You still need WEBHOOK_URL and N8N_PROXY_HOPS in your n8n environment.
Hostinger
Hostinger VPS comes with full root access for Nginx, Traefik, or Caddy setup. KVM 1 at $6.49/mo handles n8n + reverse proxy with room to spare.
* Affiliate link — we may earn a commission at no extra cost to you.
n8n v2.0 Webhook Changes
If you recently upgraded to n8n v2.0, be aware of these breaking changes:
OAuth callback authentication changed. The default for N8N_SKIP_AUTH_ON_OAUTH_CALLBACK changed from true to false. If your OAuth integrations stopped working after upgrading, set this variable explicitly:
N8N_SKIP_AUTH_ON_OAUTH_CALLBACK=true
Respond to Webhook node behavior changed. In v2.0, the Respond to Webhook node may return an empty body even when the node executes successfully. Test your webhook response workflows after upgrading.
Environment variable parsing changed. Backtick characters in .env values must be wrapped in quotes in v2.0.
Step-by-Step Troubleshooting Checklist
When your webhook returns 404, work through this list in order:
1. Is the workflow active? Open the n8n editor, go to the workflow, and check the toggle in the top-right. It must show "Active". Just saving a workflow doesn't activate it.
2. Are you using the production URL?
Production webhooks use /webhook/{path}. Test webhooks use /webhook-test/{path}. External services should always use the production path.
3. Is WEBHOOK_URL set correctly?
Check your Docker environment. The URL should include https://, your domain, and a trailing slash:
docker exec n8n env | grep WEBHOOK
4. Is N8N_PROXY_HOPS set?
docker exec n8n env | grep PROXY_HOPS
Should return 1 (or more if you have multiple proxies).
5. Is your reverse proxy forwarding headers? Test with curl from your server:
curl -I https://n8n.yourdomain.com/webhook/test-path
If this returns a connection error, your reverse proxy or SSL isn't configured correctly.
6. Did you recently restart n8n? After a restart, webhook registrations happen via the Task Broker when workflows are saved in the UI. If webhooks stop after a restart, open each affected workflow in the editor and click Save again.
7. Are there duplicate webhook paths? Two workflows using the same webhook path and HTTP method will conflict. Check for duplicates.
8. Check n8n logs for errors:
docker logs n8n --tail 100 | grep -i webhook
9. Test the webhook directly:
curl -X POST https://n8n.yourdomain.com/webhook/your-webhook-path \
-H "Content-Type: application/json" \
-d '{"test": true}'
A 200 response means the webhook works. A 404 means it's not registered. A connection error means the proxy or network is the problem.
For debugging, temporarily set N8N_LOG_LEVEL=debug in your environment. This will log every webhook registration and incoming request, making it easy to spot mismatches.
Preventing Webhook Issues Long-Term
Once your webhooks work, keep them working:
- Pin your n8n version in Docker Compose (
image: n8nio/n8n:1.82.1instead of:latest) to avoid surprise breaking changes - Set up monitoring -- use UptimeRobot or a similar service to ping your webhook URL every 5 minutes
- Automate SSL renewal -- expired certificates cause silent webhook failures
- Back up your environment variables -- losing
N8N_ENCRYPTION_KEYafter a migration will break all stored credentials and webhook configurations
Conclusion
n8n webhook 404 errors are frustrating but predictable. In almost every case, the fix is some combination of setting WEBHOOK_URL, adding N8N_PROXY_HOPS=1, and ensuring your reverse proxy forwards the right headers.
Get the environment variables right from the start, use the Nginx/Traefik/Caddy configs above, and you'll have bulletproof webhook endpoints that external services can rely on.
DigitalOcean
DigitalOcean's managed load balancers handle SSL and proxy headers automatically. Pair with an n8n Droplet for the easiest webhook setup.
* Affiliate link — we may earn a commission at no extra cost to you.
Ready to start automating? Get a VPS today.
Get started with Hostinger VPS hosting today. Special pricing available.
* Affiliate link — we may earn a commission at no extra cost to you