Who this is for: Developers and DevOps engineers who run n8n in production and need to lock down public webhook endpoints. We cover this in detail in the n8n Security & Hardening Guide.
Quick diagnosis
Publicly reachable n8n webhook URLs can be invoked by anyone on the Internet. An attacker can trigger workflows, exfiltrate data, or launch privilege‑escalation chains simply by sending a HTTP request to the raw webhook endpoint.
Featured‑snippet solution – Immediately limit webhook access by (1) enabling basic‑auth or JWT on the n8n server, (2) restricting the endpoint to a trusted IP range via a reverse‑proxy or firewall, and (3) rotating the webhook secret after each deployment.
1. How n8n webhooks become public ?
If you encounter any default credentials vulnerability resolve them before continuing with the setup.
| Exposure vector | What it looks like | Typical impact |
|---|---|---|
Direct internet‑facing port (e.g., 0.0.0.0:5678) |
https://example.com/webhook/12345 reachable from any IP |
Unauthenticated workflow execution, data leakage |
Reverse‑proxy mis‑configuration (missing auth, open allow all) |
Nginx/Traefik passes all traffic to /webhook/* |
Same as above, plus potential DoS |
| Cloud‑exposed URL (e.g., Ngrok, Vercel preview) left active | Temporary tunnel URL still alive after dev session | One‑off abuse, but can persist if tunnel not closed |
| Logging the full URL (including secret) in plain text | INFO: Webhook URL: https://.../webhook/abcd?token=secret |
Secrets harvested from logs, later replay attacks |
EEFA note: In production, never expose the raw webhook URL without an additional authentication layer. Even a “secret” query param is not sufficient because it can be harvested from logs, browser history, or referer headers.
2. Step‑by‑step hardening of n8n webhook endpoints
2.1 Enable built‑in HTTP basic authentication
Add the following environment variables (or Docker‑compose overrides) and restart n8n:
# .env or docker-compose.yml N8N_BASIC_AUTH_ACTIVE=true N8N_BASIC_AUTH_USER=webhookadmin N8N_BASIC_AUTH_PASSWORD=$(openssl rand -base64 32) # store securely
EEFA: Use a strong, randomly generated password and rotate it every 90 days. Do not commit the .env file to source control. If you encounter any environment variable secrets leakage resolve them before continuing with the setup.
2.2 Restrict access with an IP allow‑list (NGINX example)
Server block – SSL termination (excerpt):
server {
listen 443 ssl;
server_name n8n.example.com;
# SSL certs omitted for brevity
}
Location block – IP whitelist and auth header forwarding:
location /webhook/ {
allow 203.0.113.0/24; # corporate subnet
deny all; # block everyone else
proxy_set_header Authorization $http_authorization;
proxy_pass http://localhost:5678;
}
| Directive | Effect |
|---|---|
| allow 203.0.113.0/24; | Whitelists corporate subnet |
| deny all; | Blocks every other source IP |
| proxy_set_header Authorization | Passes the basic‑auth credentials to n8n |
EEFA: When using a cloud firewall (AWS SG, GCP firewall rules, Azure NSG), replicate the same CIDR restrictions at the network layer for defense‑in‑depth.
2.3 Rotate webhook secrets automatically
Generate a fresh token (run during CI/CD):
WEBHOOK_TOKEN=$(openssl rand -base64 32)
Inject the token into the n8n container:
# docker‑compose override
environment:
- N8N_WEBHOOK_TOKEN=${WEBHOOK_TOKEN}
Reference the token inside a workflow node (JSON snippet):
{
"url": "https://n8n.example.com/webhook/{{ $env.N8N_WEBHOOK_TOKEN }}",
"method": "POST"
}
EEFA: Store WEBHOOK_TOKEN in a secret manager (AWS Secrets Manager, HashiCorp Vault, etc.) and grant the n8n container read‑only access. Never hard‑code it.
3. Advanced mitigation – JWT‑protected webhooks
n8n can verify a JWT before executing a workflow. Add a pre‑hook Function node that checks the token. If you encounter any jwt auth misconfiguration resolve them before continuing with the setup.
Extract and verify the JWT (first part):
const jwt = require('jsonwebtoken');
const authHeader = $json["headers"]["authorization"] || '';
const token = authHeader.replace('Bearer ', '');
if (!token) {
throw new Error('Missing JWT');
}
Validate the token and expose claims (second part):
try {
const payload = jwt.verify(token, $env.JWT_SECRET);
$set('userId', payload.sub); // optional: store for later nodes
} catch (e) {
throw new Error('Invalid JWT');
}
Deploy the signing secret via environment variable:
N8N_ENV_JWT_SECRET=$(openssl rand -base64 48)
Why it works: The JWT is signed with a secret known only to your issuing service, preventing replay attacks and allowing fine‑grained claims (user ID, scopes).
EEFA: Use RS256 (asymmetric) for cross‑team environments; rotate the signing key annually and revoke compromised tokens immediately.
4. Monitoring & incident response
| Tool | What to monitor | Alert threshold |
|---|---|---|
n8n built‑in logs (/logs) |
`Webhook called` events without a valid auth header | > 5 unauthenticated calls per minute |
| Cloud‑flare Access logs | IPs outside the allow‑list hitting /webhook/* |
Any hit |
Prometheus exporter (N8N_METRICS=true) |
`n8n_webhook_requests_total{status=”401″}` | Spike > 200 % over 5‑min window |
| SIEM (Splunk, ELK) | Search for webhook + error |
Immediate ticket |
TL;DR checklist
- [ ] Enable
N8N_BASIC_AUTH_ACTIVEwith a strong password. - [ ] Place a reverse‑proxy (NGINX/Traefik) in front and whitelist trusted IPs.
- [ ] Store webhook secrets in a secret manager; rotate on each deploy.
- [ ] Consider JWT validation for high‑value endpoints.
- [ ] Set up alerts for unauthenticated webhook calls.
5. Secure n8n webhooks in 3 minutes
- Add basic‑auth – set
N8N_BASIC_AUTH_ACTIVE=trueand a random password. - Restrict IPs – configure your reverse‑proxy to
allow <trusted‑CIDR>anddeny all. - Rotate secret – generate a new
WEBHOOK_TOKENper release and keep it in a secret manager.
Result: Only authorized users or services can trigger your workflows, eliminating the “insecure webhook exposure” vector.



