Best use case
ngrok-tunnels skill is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## When to use
Teams using ngrok-tunnels skill should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/ngrok-tunnels/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How ngrok-tunnels skill Compares
| Feature / Agent | ngrok-tunnels skill | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## When to use
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# ngrok-tunnels skill
## When to use
Use this skill when the user needs to:
- Expose a local HTTP service at a public HTTPS URL
- Understand how the webhook-relay integrates with ngrok vs Cloudflare Tunnel
- Troubleshoot tunnel startup failures
- Switch tunnel providers at runtime
## What is a tunnel
A tunnel opens a persistent connection from your machine to a cloud relay. The cloud relay receives HTTPS traffic at a public URL and forwards it over the persistent connection to a local port. No firewall or router changes are required.
webhook-relay supports two providers:
- `ngrok` - uses the `@ngrok/ngrok` Node.js SDK. Requires an auth token. Free tier gives one random subdomain.
- `cloudflare` - uses `cloudflared` binary via child_process.spawn. Requires no account for temporary tunnels.
- `none` - no tunnel. The relay server is only reachable on localhost.
## ngrok setup
1. Create a free account at ngrok.com
2. Copy your auth token from the ngrok dashboard
3. Set the environment variable:
```
RELAY_NGROK_AUTHTOKEN=your_token npx webhook-relay
```
The token is stored only in the environment - never written to disk by webhook-relay.
## Cloudflare Tunnel setup
1. Install `cloudflared`: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
2. Set the provider:
```
RELAY_TUNNEL_PROVIDER=cloudflare npx webhook-relay
```
Cloudflare provides a random `trycloudflare.com` subdomain. No account or token required. The URL changes on every restart.
## Implementation reference
```typescript
// Tunnel interface implemented by both providers
interface TunnelHandle {
url: string;
close(): Promise<void>;
}
// ngrok provider
import * as ngrok from '@ngrok/ngrok';
async function startNgrokTunnel(port: number, token: string): Promise<TunnelHandle> {
const listener = await ngrok.forward({
addr: port,
authtoken: token,
});
const url = listener.url();
if (!url) throw new Error('ngrok did not return a URL');
return {
url,
close: () => listener.close(),
};
}
// Cloudflare provider (spawn cloudflared, parse URL from stderr)
import { spawn } from 'node:child_process';
async function startCloudflareTunnel(port: number): Promise<TunnelHandle> {
return new Promise((resolve, reject) => {
const child = spawn('cloudflared', [
'tunnel', '--url', `http://localhost:${port}`,
'--no-autoupdate',
], { stdio: ['ignore', 'ignore', 'pipe'] });
const URL_RE = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
let buf = '';
child.stderr.on('data', (chunk: Buffer) => {
buf += chunk.toString();
const match = URL_RE.exec(buf);
if (match) {
resolve({
url: match[0],
close: async () => { child.kill('SIGTERM'); },
});
}
});
child.on('exit', (code) => {
reject(new Error(`cloudflared exited with code ${code}`));
});
setTimeout(() => reject(new Error('cloudflared URL timeout after 15s')), 15_000);
});
}
```
## Tunnel lifecycle
1. Server starts and listens on `RELAY_PORT` (default 7701)
2. Tunnel provider starts and connects to the cloud relay
3. The public URL is stored in memory and emitted via SSE to the dashboard
4. The TunnelStatusBar in the dashboard updates from `starting` to `connected`
5. On shutdown (SIGTERM/SIGINT), `TunnelHandle.close()` is called before process exit
## SSE tunnel events
The dashboard receives tunnel state changes over the `/api/sse` event stream:
```
event: tunnel
data: {"status":"connected","url":"https://abc123.ngrok-free.app"}
event: tunnel
data: {"status":"starting"}
event: tunnel
data: {"status":"disconnected","error":"auth token invalid"}
```
## Status values
| Status | Meaning |
|--------|---------|
| `starting` | Tunnel process is starting, URL not yet available |
| `connected` | Tunnel is up, URL is available |
| `disconnected` | Tunnel process exited or failed |
| `local-only` | Provider is set to `none` |
## Common pitfalls
- Do not parse ngrok URLs from the ngrok web dashboard URL (port 4040). Use the SDK `listener.url()` return value.
- `cloudflared` writes the URL to stderr, not stdout. Always capture stderr.
- The ngrok free tier allows only one concurrent tunnel per account. If another process is using ngrok, the second will fail with `ERR_NGROK_108`.
- cloudflared URLs are ephemeral and change on every restart. Do not hardcode them in webhook provider settings.
- Both providers require outbound TCP connections. Verify firewall rules allow outbound on port 443.
- If `cloudflared` is not on PATH, the spawn will fail with `ENOENT`. Check installation with `which cloudflared`.
## Troubleshooting
| Symptom | Resolution |
|---------|------------|
| `ERR_NGROK_108` | Another ngrok agent is already running with this token. Stop it before starting webhook-relay |
| `auth token invalid` | The `RELAY_NGROK_AUTHTOKEN` value is wrong or expired. Regenerate at dashboard.ngrok.com |
| `cloudflared URL timeout` | cloudflared is installed but slow to connect. Check outbound connectivity |
| `cloudflared: command not found` | Install cloudflared and add it to PATH |
| Dashboard shows "starting" for > 30s | Tunnel is stuck. Restart the relay server |
| URL changes on every restart | Expected behavior for ngrok free tier and all cloudflare tunnels. Configure a fixed domain in ngrok paid plans |Related Skills
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose
Skill: Pastebin Core
## Purpose