~ / blog / make-n8n-webhooks-safe-again
misc2026-03-17

Make N8N Webhooks Safe Again

Connect your Homelab N8N Server with ngrok and secure it with an nginx whitelist proxy

#N8n#Webhooks#Automations#nginx#ngrok#Docker

Do you want to trigger your n8n workflows via Telegram or Discord? Here is how I solved it — without exposing the entire n8n UI to the internet.


The Problem #

Running n8n at home is great. But the moment you want to trigger workflows from outside — via a Telegram bot, a Discord slash command, or any external service — you need n8n to be reachable from the internet.

The naive approach: just throw ngrok directly in front of n8n. Problem: that exposes everything. The entire n8n UI, all endpoints, all workflows. Any scanner that hits your ngrok URL has full visibility of what's running.


The Architecture #

The fix is a proxy layer between ngrok and n8n that acts as a strict whitelist. Only explicitly allowed webhook paths get forwarded. Everything else gets a 403.

  Internet
     │
     ▼
 ngrok tunnel
     │
     ▼
 nginx container :8080   ← whitelist proxy
     │
     │  /webhook/<uuid> only
     ▼
 n8n container :5678

Setup #

Everything runs in Docker. The docker-compose.yml:

services:
  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n

  nginx:
    image: nginx:alpine
    container_name: nginx-webhook-proxy
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - n8n

volumes:
  n8n_data:

nginx config (nginx.conf) #

server {
    listen 8080;

    # Block everything by default
    location / {
        return 403;
    }

    # Only allow specific webhook paths
    location = /webhook/550e8400-e29b-41d4-a716-446655440000 {
        proxy_pass http://n8n:5678;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Note: inside Docker Compose, nginx can reach n8n via the container name n8n — no localhost needed.

ngrok points at nginx, not n8n #

ngrok http 8080

ngrok tunnels to port 8080 (nginx), not 5678 (n8n). The tunnel URL hits the proxy first.

UUIDs as path obfuscation #

In n8n, when you create a webhook node, set the path to a UUID:

uuidgen
# or
python3 -c "import uuid; print(uuid.uuid4())"

A UUID has 2^122 possible values. No scanner is going to find that path.


Why This Works #

Attack vectorResult
Scanner hits /403 Forbidden
Scanner enumerates /webhook/*403 on every path
Someone finds the ngrok URLStill only sees 403
Known UUID pathForwarded to n8n ✓
n8n UI (/)Never reachable

Adding a new webhook #

  1. Create webhook node in n8n, set path to a new UUID
  2. Add a location block to nginx.conf:
location = /webhook/<your-new-uuid> {
    proxy_pass http://n8n:5678;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}
  1. Reload the nginx container:
docker compose restart nginx

Triggering from Telegram / Discord #

Use the ngrok URL + the UUID path as your webhook endpoint:

https://<your-ngrok-id>.ngrok-free.app/webhook/550e8400-e29b-41d4-a716-446655440000

Paste that into your Telegram bot's webhook config or Discord bot's interaction URL. Only that exact path works — everything else is dead.


Simple setup, solid security posture for a homelab. No auth tokens, no firewall rules — just a UUID that nobody will ever guess.

← back to blog