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 vector | Result |
|---|---|
Scanner hits / | 403 Forbidden |
Scanner enumerates /webhook/* | 403 on every path |
| Someone finds the ngrok URL | Still only sees 403 |
| Known UUID path | Forwarded to n8n ✓ |
n8n UI (/) | Never reachable |
Adding a new webhook #
- Create webhook node in n8n, set path to a new UUID
- Add a
locationblock tonginx.conf:
location = /webhook/<your-new-uuid> {
proxy_pass http://n8n:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
- 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.