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 forward port 5678 or 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:
Internet → ngrok tunnel → nginx proxy → n8n (localhost:5678)
Only explicitly allowed webhook paths get forwarded. Everything else gets a 403. The n8n UI is never reachable from outside.
Setup #
1. n8n running locally #
n8n runs on localhost:5678. Nothing exposed publicly yet.
2. nginx as whitelist proxy #
Install nginx and create a config that only forwards specific webhook paths:
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://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Each allowed webhook gets its own location = /webhook/<uuid> block. Exact match (=) means no path traversal tricks.
3. ngrok points at nginx, not n8n #
ngrok http 8080
ngrok now tunnels to port 8080 (nginx), not 5678 (n8n). The tunnel URL hits the proxy first.
4. UUIDs as path obfuscation #
In n8n, when you create a webhook node, set the path to a UUID:
/webhook/550e8400-e29b-41d4-a716-446655440000
Generate one with:
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 to nginx config:
location = /webhook/<your-new-uuid> {
proxy_pass http://localhost:5678;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
- Reload nginx:
sudo nginx -t && sudo systemctl reload 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.