n8n Webhooks in Production: Reverse Proxies, Queue Mode, and Reliable Ingress
This production-oriented follow-up turns current vendor guidance into operating controls, migration decisions, and verifiable release criteria.
What changed in 2026
We revisited this subject because the operating boundary has moved. The durable principles remain useful, but current versions make several old shortcuts incomplete or unsafe. This follow-up starts with the vendor documentation available on 14 July 2026, separates facts from local choices, and treats every configuration change as a controlled production intervention.
Current primary sources
How we use the update
Read the sources first, record the versions actually deployed, and define the observable result before changing a setting. Test the smallest reversible intervention in a representative environment. A successful command is not the acceptance criterion: service health, data integrity, latency, security boundaries, and rollback time are. Where the earlier guide offers a useful diagnostic sequence, we retain it below as an operating foundation; version-specific examples must still be checked against the current documentation.
Before release, capture a configuration diff, a backup or snapshot that has been restored at least once, and the commands needed to reverse the intervention. Assign one person to observe the first production window and another to approve escalation if the expected indicators move in the wrong direction. Alerts should describe the user-visible risk, not merely the component that emitted them. During the readback window, compare error rates, queue depth, resource saturation, processing latency, and data completeness with the agreed baseline. Review both averages and tail behavior, because a stable average can conceal failures affecting a small but important share of work. Close the change only after delayed jobs, retries, scheduled tasks, and downstream exports have completed. Record any manual action taken during recovery; an undocumented rescue step is evidence that the runbook is not yet complete. This release discipline creates evidence that can be reused at the next upgrade instead of forcing the team to rediscover the same dependencies.
- Complete n8n Self-Hosted Troubleshooting Guide 2025: Fixing Execution Data Size & Webhook Problems with Traefik
- Traefik Reverse Proxy: The Complete Self-Hosting Guide for HTTPS and SSL Automation
- Self-Hosting n8n on Hetzner Cloud: Complete Docker Setup Tutorial
Operating foundation
Self-hosting n8n gives you incredible power and cost savings, but webhook integrations can sometimes fail in ways that don’t occur with cloud-hosted solutions. If you’re seeing “Execution cancelled” errors or webhooks that simply don’t trigger, you’re not alone. We’ll show you how to diagnose and fix the most common webhook problems in self-hosted n8n installations.
The Problem: When Webhooks Go Silent
You’ve successfully set up your n8n instance following our complete tutorial. Everything looks perfect, but suddenly your Telegram bots stop responding, API webhooks time out, and executions get canceled with cryptic error messages. This is typically caused by a missing but critical configuration that cloud-hosted n8n handles automatically.
What You’ll Fix
By the end of this guide, you’ll have:
- ✅ Properly configured webhook URLs for all external services
- ✅ Working Telegram bot integrations with reliable message handling
- ✅ Functional API webhooks from third-party services
- ✅ Fewer “execution cancelled” errors caused by webhook misconfiguration
- ✅ Production-ready webhook configuration that survives server restarts
- ✅ Advanced troubleshooting skills for future webhook issues
Prerequisites
- Working n8n installation (preferably from our Hetzner setup guide)
- n8n running behind a reverse proxy (Traefik, Nginx, etc.)
- HTTPS enabled with valid SSL certificates
- SSH access to your server
- Basic Docker and command-line knowledge
Understanding the Root Cause
Why Self-Hosted n8n Webhooks Fail
When you run n8n behind a reverse proxy (which you should for security and SSL), n8n needs to know its public URL to generate correct webhook endpoints. Without this configuration, n8n creates webhook URLs like:
❌ Wrong: https://yourdomain.com:5678/webhook/abc123
❌ Wrong: http://localhost:5678/webhook/abc123
Instead of the correct format:
✅ Correct: https://yourdomain.com/webhook/abc123
The Missing Environment Variable
The solution is the WEBHOOK_URL environment variable that tells n8n exactly where it’s publicly accessible. This is automatic in cloud solutions but must be manually configured in self-hosted setups. As of October 2026, n8n 2.35.0 and later document it as N8N_WEBHOOK_URL; WEBHOOK_URL still works as a deprecated alias that logs a warning at startup.
Step 1: Diagnose Your Current Setup
Check Your Container Configuration
First, let’s see what your current n8n configuration looks like:
# Navigate to your n8n directory
cd /opt/n8n
# Check current environment variables
grep -A 20 "environment:" docker-compose.yml
Test Webhook URL Generation
Create a simple webhook workflow to see what URLs n8n is generating:
- Open your n8n interface
- Create a new workflow
- Add a “Webhook” trigger node
- Note the generated webhook URL
If the URL includes :5678 or uses localhost, you have the problem we’re fixing.
Check Container Logs
Look for webhook-related errors:
# Check n8n logs for webhook issues
docker logs n8n-n8n-1 2>&1 | grep -i webhook
# Look for execution errors
docker logs n8n-n8n-1 2>&1 | grep -i "cancelled\|timeout\|failed"
Step 2: Fix the Primary Issue – Add WEBHOOK_URL
For a Single n8n Instance
If you followed our original setup guide, edit your Docker Compose file:
cd /opt/n8n
# Create backup first
cp docker-compose.yml docker-compose.yml.backup
# Edit the configuration
nano docker-compose.yml
Add the WEBHOOK_URL environment variable to your existing configuration:
services:
n8n:
image: n8nio/n8n:2.30.7
restart: always
environment:
- N8N_HOST=n8n.yourdomain.com
- NODE_ENV=production
- N8N_PROTOCOL=https
- N8N_PORT=5678
- N8N_EDITOR_BASE_URL=https://n8n.yourdomain.com
- N8N_EMAIL_MODE=smtp
- N8N_SMTP_HOST=mailserver
- N8N_SMTP_PORT=25
- N8N_SMTP_SSL=false
- N8N_SMTP_USER=
- N8N_SMTP_PASS=
- [email protected]
# n8n sits behind one reverse proxy (Traefik)
- N8N_PROXY_HOPS=1
- N8N_RUNNERS_ENABLED=true
# 🔧 ADD THIS LINE - Critical for webhook functionality
- WEBHOOK_URL=https://n8n.yourdomain.com
volumes:
- ./data:/home/node/.n8n
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"
- "traefik.http.routers.n8n.entrypoints=https"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
networks:
proxy:
external: true
For Multiple n8n Instances
If you’re running multiple n8n instances (e.g., for different teams), each needs its own WEBHOOK_URL:
# First instance (/opt/n8n/docker-compose.yml)
environment:
- WEBHOOK_URL=https://n8n.yourdomain.com
# Second instance (/opt/n8n-team2/docker-compose.yml)
environment:
- WEBHOOK_URL=https://team2.yourdomain.com
Restart Your Container
Apply the changes:
# Stop the container
docker compose down
# Start with new configuration
docker compose up -d
# Verify it's running
docker ps | grep n8n
Step 3: Verify the Fix
Check the Container Logs
You should now see the correct URL in the logs:
# The editor URL confirms the public address; check a Webhook node for the webhook URL
docker logs n8n-n8n-1 --tail 10
Look for this line:
Editor is now accessible via:
https://n8n.yourdomain.com # ✅ Should NOT include :5678
Test Webhook Generation
- Go back to your test webhook workflow
- Delete the old webhook node
- Add a new webhook node
- Verify the generated URL is correct: https://yourdomain.com/webhook/... (no port number)
Test an External Webhook
Create a simple test workflow:
# Needs an active (published) workflow with a POST Webhook node on the path "test"
curl -X POST https://n8n.yourdomain.com/webhook/test \
-H "Content-Type: application/json" \
-d '{"test": "webhook working"}'
Step 4: Configure Telegram Bot Integration
The Telegram-Specific Challenge
Telegram requires HTTPS webhooks and has strict URL requirements. Here’s how to set up a working Telegram bot:
Create a Telegram Bot Workflow
- Create a new workflow in n8n
- Add a “Telegram Trigger” node
- Configure your bot token
- The webhook URL should now be correctly formatted
Register the Webhook with Telegram
# Replace YOUR_BOT_TOKEN with your actual token
# Replace the webhook URL with your actual webhook URL from n8n
curl -F "url=https://n8n.yourdomain.com/webhook/abc123" \
https://api.telegram.org/botYOUR_BOT_TOKEN/setWebhook
Verify the Telegram Webhook
Check if Telegram can reach your webhook:
curl https://api.telegram.org/botYOUR_BOT_TOKEN/getWebhookInfo
You should see:
{
"ok": true,
"result": {
"url": "https://n8n.yourdomain.com/webhook/abc123",
"has_custom_certificate": false,
"pending_update_count": 0
}
}
Step 5: Advanced Webhook Configuration
Enable WebSocket Support in Traefik
Ordinary webhook requests are plain HTTP and do not need WebSocket support. The n8n editor’s push connection uses WebSockets, which Traefik supports out of the box, so extra labels are normally unnecessary.
Configure Webhook Timeouts
For long-running webhook processes, set EXECUTIONS_TIMEOUT and EXECUTIONS_TIMEOUT_MAX (both in seconds); payload and execution-data settings such as N8N_PAYLOAD_SIZE_MAX do not change timeouts.
Troubleshooting Common Issues
Problem: Still Getting Port Numbers in URLs
Symptoms:
- Webhook URLs still show :5678
- External services can’t reach webhooks
Solution:
# Check if environment variable is properly set
docker exec n8n-n8n-1 env | grep WEBHOOK_URL
# If missing, recreate container:
docker compose down
docker compose up -d --force-recreate
Problem: “Bad webhook: HTTPS URL must be provided”
Symptoms:
- Telegram bot setup fails
- Error mentions HTTPS requirement
Cause: Your webhook URL is not using HTTPS or has SSL certificate issues.
Solution:
# Test SSL certificate
curl -I https://n8n.yourdomain.com
# Check Traefik logs for SSL issues
docker logs traefik 2>&1 | grep -i certificate
# Verify domain DNS points to your server
nslookup n8n.yourdomain.com
Problem: Webhooks Work in Test Mode but Fail in Production
Symptoms:
- Test webhook executions work fine
- Production webhooks time out or fail
Solution:
# Test URLs (/webhook-test/...) only work while the editor is listening;
# production URLs (/webhook/...) need the workflow to be active (published)
docker logs n8n-n8n-1 2>&1 | grep -i "webhook"
# Call the production URL, not the test URL
curl -X POST https://n8n.yourdomain.com/webhook/test
Problem: “Connection reset by peer” or 502 Bad Gateway
Symptoms:
- Intermittent webhook failures
- Gateway errors in logs
Solution:
# Check if container is running out of memory
docker stats --no-stream n8n-n8n-1
# Increase container resources if needed:
# Add to docker-compose.yml under the n8n service:
deploy:
resources:
limits:
memory: 2G
reservations:
memory: 1G
Security Considerations
Webhook IP Whitelisting
For sensitive webhooks, consider IP restrictions:
# Webhook node > Options > IP(s) Allowlist (parameter "ipWhitelist"):
# a comma-separated list of IP addresses or CIDR ranges
"ipWhitelist": "192.168.1.0/24, 10.0.0.0/8"
# Behind Traefik, set N8N_PROXY_HOPS=1 so n8n sees the client IP
Webhook Authentication
Always use webhook authentication when possible:
# Webhook node > Authentication: Header Auth
"authentication": "headerAuth"
# The header name and value are stored in a "Header Auth" credential:
# Name: X-API-Key
# Value: your-secret-key
Rate Limiting
Protect against webhook abuse:
# Add to Traefik labels
# A router takes one middlewares label; list several as middlewares=a,b
- "traefik.http.middlewares.rate-limit.ratelimit.average=10"
- "traefik.http.middlewares.rate-limit.ratelimit.burst=20"
- "traefik.http.routers.n8n.middlewares=rate-limit"
Monitoring and Maintenance
Monitor Webhook Health
Create a monitoring workflow:
- Add a “Cron” trigger (every 5 minutes)
- Add an “HTTP Request” node to test your webhook
- Add notification logic for failures
Log Webhook Activity
Enable detailed webhook logging:
environment:
- N8N_LOG_LEVEL=debug
- N8N_LOG_OUTPUT=console,file
- N8N_LOG_FILE_COUNT_MAX=100
# Size per log file in MB
- N8N_LOG_FILE_SIZE_MAX=16
Back Up Webhook Configurations
#!/bin/bash
# Back up workflows and credentials (credentials stay encrypted:
# keep the instance's encryption key with the backup)
set -e
mkdir -p /opt/backups/webhook-configs
# Backup workflow definitions (--all writes one file)
docker exec n8n-n8n-1 n8n export:workflow --all --output=/tmp/workflows-backup.json
docker cp n8n-n8n-1:/tmp/workflows-backup.json /opt/backups/webhook-configs/workflows-$(date +%Y%m%d).json
# Backup credentials
docker exec n8n-n8n-1 n8n export:credentials --all --output=/tmp/credentials-backup.json
docker cp n8n-n8n-1:/tmp/credentials-backup.json /opt/backups/webhook-configs/credentials-$(date +%Y%m%d).json
Performance Optimization
Webhook Response Optimization
Configure efficient webhook responses:
environment:
# Keep binary data on disk instead of in memory (single instance;
# queue mode needs S3 or database storage instead)
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
# Maximum request payload in MiB (default 16)
- N8N_PAYLOAD_SIZE_MAX=16
- GENERIC_TIMEZONE=UTC
Database Cleanup for Webhooks
High-volume webhooks can fill your database:
environment:
# Built-in pruning of execution data (enabled by default)
- EXECUTIONS_DATA_PRUNE=true
# Keep executions for 30 days (value in hours)
- EXECUTIONS_DATA_MAX_AGE=720
# Keep at most 1,000 executions
- EXECUTIONS_DATA_PRUNE_MAX_COUNT=1000
Testing Webhook Integrations
Test Suite for Common Webhook Types
Create test workflows for different webhook types:
Simple REST API Webhook:
{
"method": "POST",
"url": "https://n8n.yourdomain.com/webhook/test-api",
"headers": {
"Content-Type": "application/json"
},
"body": {
"test": true,
"timestamp": "{{ new Date().toISOString() }}"
}
}
Telegram Bot Test: Send a message to your bot and verify the workflow triggers correctly.
Form Submission Test:
<form action="https://n8n.yourdomain.com/webhook/form-test" method="POST">
<input type="text" name="name" value="Test User">
<input type="email" name="email" value="[email protected]">
<button type="submit">Test Webhook</button>
</form>
Scaling Webhook Infrastructure
Multiple n8n Instances with Load Balancing
For high-volume webhook processing, several n8n instances behind one URL need queue mode with a shared PostgreSQL database, Redis and the same encryption key on every instance; a multi-main setup is a self-hosted Enterprise feature.
Webhook Queue Management
For handling webhook bursts, use queue mode: the main instance passes executions through Redis to separate worker processes. See the n8n queue mode documentation listed above for the required settings.
A minimal Docker Compose sketch with one main instance, one worker, PostgreSQL and Redis; the main instance and every worker share the same N8N_ENCRYPTION_KEY:
# docker-compose.yml: queue mode with one main instance and one worker
# Set POSTGRES_PASSWORD and N8N_ENCRYPTION_KEY in .env; when migrating,
# reuse the encryption key of the existing instance (./data/config)
services:
n8n:
image: n8nio/n8n:2.30.7
restart: always
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_BULL_REDIS_PORT=6379
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_HOST=n8n.yourdomain.com
- N8N_PROTOCOL=https
- N8N_PROXY_HOPS=1
- WEBHOOK_URL=https://n8n.yourdomain.com
volumes:
- ./data:/home/node/.n8n
networks:
- proxy
- n8n-internal
depends_on:
- postgres
- redis
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:5678/healthz"]
interval: 10s
retries: 10
start_period: 30s
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"
- "traefik.http.routers.n8n.entrypoints=https"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
# Workers run the executions; add more with: docker compose up -d --scale n8n-worker=3
n8n-worker:
image: n8nio/n8n:2.30.7
restart: always
command: worker
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- QUEUE_BULL_REDIS_PORT=6379
# Must match the main instance
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
# Execution timeouts in seconds
- EXECUTIONS_TIMEOUT=3600
- EXECUTIONS_TIMEOUT_MAX=7200
networks:
- n8n-internal
# Start after the main instance has run the database migrations
depends_on:
n8n:
condition: service_healthy
postgres:
image: postgres:17
restart: always
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n-internal
redis:
image: redis:8-alpine
restart: always
volumes:
- redis_data:/data
networks:
- n8n-internal
volumes:
postgres_data:
redis_data:
networks:
proxy:
external: true
n8n-internal:
Cost and Performance Impact
Webhook Processing Costs
When properly configured, webhook processing is extremely efficient:
- Server Resources: Minimal CPU/memory impact for most webhooks
- Storage: Execution data scales with webhook volume
- Network: Bandwidth scales with payload sizes
Cost Comparison
Self-hosted webhook processing vs. cloud alternatives:
- n8n Cloud Starter: €20/month billed annually (2,500 executions, as of October 2026)
- Self-hosted (Hetzner CX23): €5.99/month excl. VAT, incl. a primary IPv4 address, as of October 2026 (no per-execution fees; capacity limited by server resources)
- Savings: about €168 annually, without execution quotas
Advanced Integration Examples
Webhook to Database Pipeline
Complete example for webhook-to-database integration:
# Workflow: API Webhook → Data Validation → Database Insert
# 1. Webhook Trigger (receives data)
# 2. Code Node (validates/transforms data)
# 3. Database Node (inserts processed data)
# 4. Respond to Webhook (confirms receipt)
Multi-Service Webhook Router
Route different webhook types to appropriate handlers:
# Workflow: Webhook Router
# 1. Webhook Trigger (catches all webhooks)
# 2. Switch Node (routes by webhook source)
# 3. Multiple branches for different services
# 4. Appropriate response formatting
Conclusion
Properly configured webhooks are essential for a production-ready n8n installation. The WEBHOOK_URL environment variable is the single most important configuration for webhook reliability in self-hosted setups. Combined with proper SSL configuration and monitoring, your self-hosted n8n instance can handle webhook workloads that would otherwise need a higher-tier cloud plan.
Key Benefits of This Configuration
- Cost-effective: No per-execution fees on a server from about €6/month (as of October 2026)
- Reliable: Production-tested configuration handles high-volume scenarios
- Secure: HTTPS, authentication, and rate limiting protect your infrastructure
- Scalable: Architecture supports growth from personal projects to enterprise workflows
- Private: Webhook payloads are processed on your own server; data leaves it only when your workflows send it to external services such as Telegram
This guide builds upon our original n8n setup tutorial to create a complete, webhook-ready automation platform. Whether you’re processing Telegram messages, API callbacks, or form submissions, your self-hosted n8n instance now handles them all reliably and cost-effectively.
For complex webhook scenarios or enterprise-grade implementations, consider seeking professional advice to optimize your specific use case.
From configuration to an operating decision
The practical question is not whether a platform can be configured. It is whether the team can explain ownership, detect drift, recover without improvisation, and demonstrate that the change improved the intended outcome. We therefore pair configuration with an owner, a baseline, a rollback path, and a readback window. That turns a one-off fix into an operating capability. The production record should include the reason for the change, the evidence reviewed, the exact scope, and the person authorized to stop or reverse it. After release, compare the agreed indicators with the baseline and keep the result with the change record. If the expected improvement is absent, rollback is a valid outcome rather than a failure. This discipline matters more than any individual configuration value. The same record gives the next operator a trustworthy starting point and makes later optimization a measured decision rather than another round of guesswork.