Tunnel Setup Guide
This guide walks you through setting up a public tunnel for webhook delivery. Tunnels provide instant webhook notifications without port forwarding or firewall changes.
When do you need a tunnel? Tunnels are required when external services (GitHub, Linear, Jira) need to send webhooks to your local machine. If you’re using polling-only mode, you don’t need a tunnel.
Quick Start
Install cloudflared
brew install cloudflare/cloudflare/cloudflaredcurl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/winget install --id Cloudflare.cloudflaredRun Setup
pilot tunnel setupThis interactive wizard will:
- Detect available tunnel providers
- Authenticate with Cloudflare (if using Cloudflare)
- Create a tunnel named
pilot-webhook - Optionally configure a custom domain
Start Pilot with Tunnel
pilot start --tunnel --github --telegramPilot will display the public URL:
🌐 Public tunnel: https://abc123.trycloudflare.com
Webhooks:
GitHub: https://abc123.trycloudflare.com/webhooks/github
Linear: https://abc123.trycloudflare.com/webhooks/linear
Jira: https://abc123.trycloudflare.com/webhooks/jiraVerify
pilot tunnel url
# Output: https://abc123.trycloudflare.comProvider Setup
Cloudflare (Free, Recommended)
Cloudflare Tunnel provides free, secure tunnels without requiring an account for temporary URLs.
Temporary Tunnel (No Account)
pilot start --tunnelThis creates a random URL like https://abc123.trycloudflare.com that changes each restart.
Persistent Tunnel (Free Account)
For a stable URL that survives restarts:
- Create a free Cloudflare account at dash.cloudflare.com
- Authenticate cloudflared:
cloudflared tunnel login - Run setup with a custom domain:
pilot tunnel setup --domain pilot.yourdomain.com
Configuration:
# ~/.pilot/config.yaml
tunnel:
enabled: true
provider: cloudflare
domain: pilot.yourdomain.com # Optional: custom domainngrok (Paid)
ngrok provides stable URLs but requires a paid plan for custom domains.
Setup:
- Sign up at ngrok.com
- Install ngrok:
brew install ngrok/ngrok/ngrok # macOS - Authenticate:
ngrok config add-authtoken YOUR_AUTH_TOKEN - Configure Pilot:
tunnel: enabled: true provider: ngrok domain: your-domain.ngrok.io # Requires paid plan
Start:
pilot start --tunnel --githubWebhook URL Configuration
After starting the tunnel, configure webhooks in each service using the tunnel URL.
GitHub
- Go to your repository → Settings → Webhooks
- Click Add webhook
- Configure:
- Payload URL:
https://<tunnel-url>/webhooks/github - Content type:
application/json - Secret: (same as
github_webhook_secretin config.yaml) - Events: Select “Issues” and “Pull requests”
- Payload URL:
- Click Add webhook
# ~/.pilot/config.yaml
adapters:
github:
enabled: true
webhook_secret: "your-secret-here" # Match the GitHub webhook secretLinear
- Go to Linear → Settings → API → Webhooks
- Click New webhook
- Configure:
- URL:
https://<tunnel-url>/webhooks/linear - Resource types: Issues
- URL:
- Click Create webhook
Jira
- Go to Jira → Settings → System → Webhooks
- Click Create a WebHook
- Configure:
- URL:
https://<tunnel-url>/webhooks/jira - Events: Issue created, Issue updated
- Secret: (optional, for HMAC verification)
- URL:
- Click Create
# ~/.pilot/config.yaml
jira:
enabled: true
webhook_secret: "your-jira-secret" # Optional: enables signature verificationGitLab
- Go to your project → Settings → Webhooks
- Configure:
- URL:
https://<tunnel-url>/webhooks/gitlab - Secret token: (optional)
- Triggers: Issues events, Merge request events
- URL:
- Click Add webhook
Auto-Start Service
Install a service to automatically start the tunnel on boot.
macOS (launchd)
pilot tunnel service installThis creates a launchd service at ~/Library/LaunchAgents/com.pilot.tunnel.plist.
Manage the service:
# Check status
pilot tunnel service status
# Stop tunnel
pilot tunnel stop
# Uninstall service
pilot tunnel service uninstallLinux (systemd)
Create a systemd unit file:
sudo tee /etc/systemd/system/pilot-tunnel.service << 'EOF'
[Unit]
Description=Pilot Cloudflare Tunnel
After=network.target
[Service]
Type=simple
User=pilot
ExecStart=/usr/local/bin/cloudflared tunnel run pilot-webhook
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOFEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable pilot-tunnel
sudo systemctl start pilot-tunnelWhy Tunnel Instead of Polling?
| Aspect | Polling | Tunnel (Webhooks) |
|---|---|---|
| Latency | 30s delay (poll interval) | Instant |
| API quota | Uses API calls each interval | Zero API usage for detection |
| Reliability | May miss rapid changes | Real-time delivery |
| Setup | Simple, no network config | Requires tunnel setup |
Best practice: Use both together. Webhooks provide instant detection, while polling acts as a fallback if a webhook is missed.
# Recommended: both enabled
adapters:
github:
enabled: true
polling:
enabled: true
interval: 60s # Longer interval when webhooks active
tunnel:
enabled: true
provider: cloudflareTroubleshooting
Tunnel won’t start
# Check cloudflared is installed
cloudflared --version
# Check for existing tunnel processes
pgrep -f cloudflared
# View tunnel logs
pilot tunnel start --foregroundWebhooks not arriving
-
Verify the tunnel is running:
pilot tunnel status -
Test the webhook endpoint:
curl -X POST https://<tunnel-url>/webhooks/github \ -H "Content-Type: application/json" \ -d '{"action": "test"}' -
Check webhook delivery status in the service (GitHub → Settings → Webhooks → Recent Deliveries)
Random URL keeps changing
This is expected for free Cloudflare tunnels without authentication. For a stable URL:
- Create a free Cloudflare account
- Run
cloudflared tunnel login - Re-run
pilot tunnel setup
Service fails to start on boot
# Check service status
pilot tunnel service status
# View service logs (macOS)
cat /tmp/pilot-tunnel.log
# View service logs (Linux)
journalctl -u pilot-tunnel -f