ngrokd
Forward Proxy Daemon for Private End-to-End Connectivity
A standalone daemon that enables local and network applications to connect to private endpoints in ngrok's cloud via mTLS, without requiring a Kubernetes cluster.
What is ngrokd?
ngrokd is a background daemon that:
Auto-Discovery
Automatically discovers Kubernetes bound endpoints from ngrok API and reconciles dynamically every 30 seconds.
Virtual Network Interfaces
Creates unique IPs per endpoint with automatic IP allocation and persistence across restarts.
Automatic DNS
Built-in DNS server for network mode with /etc/hosts management for virtual mode. Supports both exact and wildcard endpoints.
Secure mTLS
Forwards traffic securely via mTLS to ngrok cloud with automatic certificate provisioning.
Auto Certificate Renewal
Automatically renews mTLS certificates before expiry. Manual renewal available via CLI.
Dynamic Reconciliation
Endpoints added or removed on-the-fly with automatic listener lifecycle management.
State Persistence
Same hostname gets same IP/port across daemon restarts for consistency.
Wildcard Endpoints
Full support for *.example.com endpoints with SNI/Host header routing and built-in DNS resolution.
Network Mode
Share endpoints with phones, VMs, and CI runners on your LAN via shared listeners and built-in DNS.
Prerequisites
Before installing ngrokd, you'll need:
1. Create an ngrok API Key
- Go to ngrok API dashboard
- Click "New API Key"
- Give it a description (e.g., "ngrokd daemon")
- Copy the API key - you'll use it with
ngrokctl set-api-key
The API key allows ngrokd to discover your bound endpoints and provision certificates.
2. Create Private Cloud Endpoints
- Go to ngrok Endpoints dashboard
- Click "New Endpoint" → "Cloud"
- Choose "Kubernetes Operator" as the binding type
- Configure your endpoint:
- URL:
identifier.subdomain(e.g.,api.myapp) - Traffic Policy: Optional routing rules, auth, rate limiting
- URL:
- Click "Create Endpoint"
ngrokd automatically discovers these endpoints and creates local access points for them.
Platform Support
| Platform | IP Range | Interface |
|---|---|---|
| Linux | 10.107.0.0/16 | dummy (ngrokd0) |
| macOS | 127.0.0.0/8 | lo0 aliases |
| Windows | 127.0.0.0/8 | Loopback Adapter |
| Docker | Configurable | virtual/0.0.0.0 |
All platforms support both virtual mode (unique IPs per endpoint) and network mode (shared listeners with SNI/Host routing + built-in DNS).
Installation
Quick Install (Recommended)
curl -fsSL https://raw.githubusercontent.com/ngrok/ngrokd/main/install.sh | sudo bash
Download Pre-built Binaries
# Apple Silicon
curl -LO https://github.com/ngrok/ngrokd/releases/download/v0.3.8/ngrokd-v0.3.8-darwin-arm64.tar.gz
tar xzf ngrokd-v0.3.8-darwin-arm64.tar.gz
cd ngrokd-v0.3.8-darwin-arm64
sudo ./install.sh
# Intel Mac
curl -LO https://github.com/ngrok/ngrokd/releases/download/v0.3.8/ngrokd-v0.3.8-darwin-amd64.tar.gz
tar xzf ngrokd-v0.3.8-darwin-amd64.tar.gz
cd ngrokd-v0.3.8-darwin-amd64
sudo ./install.sh
Start Daemon
# Background mode (recommended)
sudo nohup ngrokd --config=/etc/ngrokd/config.yml > ~/ngrokd.log 2>&1 &
# Foreground mode (for debugging)
sudo ngrokd --config=/etc/ngrokd/config.yml
Set API Key
ngrokctl set-api-key YOUR_NGROK_API_KEY
Verify
ngrokctl status
ngrokctl list
curl http://your-endpoint/
Quick Install (Recommended)
curl -fsSL https://raw.githubusercontent.com/ngrok/ngrokd/main/install.sh | sudo bash
Download Pre-built Binaries
# AMD64
curl -LO https://github.com/ngrok/ngrokd/releases/download/v0.3.8/ngrokd-v0.3.8-linux-amd64.tar.gz
tar xzf ngrokd-v0.3.8-linux-amd64.tar.gz
cd ngrokd-v0.3.8-linux-amd64
sudo ./install.sh
# ARM64
curl -LO https://github.com/ngrok/ngrokd/releases/download/v0.3.8/ngrokd-v0.3.8-linux-arm64.tar.gz
tar xzf ngrokd-v0.3.8-linux-arm64.tar.gz
cd ngrokd-v0.3.8-linux-arm64
sudo ./install.sh
Start Daemon
# Background mode (recommended)
sudo nohup ngrokd --config=/etc/ngrokd/config.yml > ~/ngrokd.log 2>&1 &
# Or use systemd
sudo tee /etc/systemd/system/ngrokd.service << 'EOF'
[Unit]
Description=ngrokd Forward Proxy Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ngrokd --config=/etc/ngrokd/config.yml
Restart=always
User=root
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable ngrokd
sudo systemctl start ngrokd
Set API Key
ngrokctl set-api-key YOUR_NGROK_API_KEY
Verify
ngrokctl status
ngrokctl list
curl http://your-endpoint/
Installation Steps
Run PowerShell as Administrator
1. Download and extract:
iwr https://github.com/ngrok/ngrokd/releases/download/v0.3.8/ngrokd-v0.3.8-windows-amd64.tar.gz -OutFile ngrokd.tar.gz
tar -xzf ngrokd.tar.gz
cd ngrokd-v0.3.8-windows-amd64
2. Install binaries:
# Copy to permanent location
mkdir "C:\Program Files\ngrokd" -Force
Copy-Item .\ngrokd.exe "C:\Program Files\ngrokd\"
Copy-Item .\ngrokctl.exe "C:\Program Files\ngrokd\"
# Add to PATH
$oldPath = [Environment]::GetEnvironmentVariable("Path", "Machine")
$newPath = "$oldPath;C:\Program Files\ngrokd"
[Environment]::SetEnvironmentVariable("Path", $newPath, "Machine")
$env:Path += ";C:\Program Files\ngrokd"
3. Create config:
mkdir C:\ProgramData\ngrokd -Force
notepad C:\ProgramData\ngrokd\config.yml
Paste this config and save:
api:
url: https://api.ngrok.com
key: ""
ingressEndpoint: kubernetes-binding-ingress.ngrok.io:443
server:
log_level: info
bound_endpoints:
poll_interval: 30
selectors: ['true']
net:
subnet: 127.0.0.0/8
listen_interface: virtual
start_port: 9080
Start Daemon
Restart PowerShell as Administrator (for PATH to take effect), then:
# Foreground mode
ngrokd --config="C:\ProgramData\ngrokd\config.yml"
# Background mode
Start-Process ngrokd -ArgumentList '--config=C:\ProgramData\ngrokd\config.yml' -WindowStyle Hidden
Set API Key
ngrokctl set-api-key YOUR_NGROK_API_KEY
Verify
ngrokctl status
ngrokctl list
curl.exe http://your-endpoint/
Note: ngrokd requires Administrator privileges to manage the hosts file and network adapters. See the Windows Guide for details.
Pull Image
docker pull ngrok/ngrokd:latest
Run Container (Network Mode)
Endpoints share ports 80/443 via SNI/Host header routing. Accessible from the host and other machines.
docker run -d \
--name ngrokd \
--cap-add=NET_ADMIN \
-e NGROK_API_KEY=your_api_key \
-p 8081:8081 \
-p 80:80 \
-p 443:443 \
-p 9080-9100:9080-9100 \
-v ngrokd-data:/etc/ngrokd \
ngrok/ngrokd:latest
Set listen_interface: "0.0.0.0" in config. Ports 80/443 are for shared listeners, 9080+ for non-standard port endpoints. If 80/443 conflict on host, remap: -p 8080:80 -p 8443:443
Run Container (Virtual Mode)
Each endpoint gets a unique IP inside the container. Ideal for CI/CD (e.g., CircleCI Docker executors) where processes inside the container need to reach endpoints directly.
docker run -d \
--name ngrokd \
--cap-add=NET_ADMIN \
-e NGROK_API_KEY=your_api_key \
-v ngrokd-data:/etc/ngrokd \
ngrok/ngrokd:latest
Virtual mode requires NET_ADMIN for interface/IP creation. Virtual IPs only exist inside the container — no port mappings needed. Use network mode if you need access from the host or other machines.
Verify
# Check status
docker exec ngrokd ngrokctl status
# List endpoints (wait ~30s first)
docker exec ngrokd ngrokctl list
# Test endpoint
curl http://localhost:9080/
Using Docker Compose
services:
ngrokd:
image: ngrok/ngrokd:latest
container_name: ngrokd
cap_add:
- NET_ADMIN
environment:
- NGROK_API_KEY=${NGROK_API_KEY}
ports:
- "9080-9100:9080-9100"
volumes:
- ngrokd-data:/etc/ngrokd
restart: unless-stopped
volumes:
ngrokd-data:
Start with:
NGROK_API_KEY=your_key docker compose up -d
Edit Config in Container
docker exec -it ngrokd ngrokctl config edit
Configuration
Listen Interface Modes
Virtual Mode (Default)
Each endpoint gets a unique IP address. Best for same-machine access.
net:
listen_interface: "virtual"
Example:
- Endpoint 1: 127.0.0.2:80 (macOS) or 10.107.0.2:80 (Linux)
- Endpoint 2: 127.0.0.3:80
- Endpoint 3: 127.0.0.4:80
Network Mode - All Interfaces
Uses shared listeners with SNI/Host header routing. Multiple endpoints share the original port (80, 443). Built-in DNS server resolves hostnames for external clients (phones, VMs, CI). Best for cross-machine access.
net:
listen_interface: "0.0.0.0"
Example:
- All HTTPS endpoints share 0.0.0.0:443 (routed by SNI)
- All HTTP endpoints share 0.0.0.0:80 (routed by Host header)
- DNS server at <LAN_IP>:53 resolves all managed hostnames
- External clients: set DNS to ngrokd machine's IP
Network Mode - Named Interface
Bind shared listeners to a specific network interface. Great for multi-homed servers.
net:
listen_interface: "eth0" # or "en0", "enp0s1", etc.
Example:
- Automatically resolves eth0 to its IP (e.g., 192.168.1.100)
- Shared listeners on 192.168.1.100:443, 192.168.1.100:80
- DNS server at 192.168.1.100:53
Network Mode - Specific IP
Bind shared listeners to a specific IP address.
net:
listen_interface: "192.168.1.100"
Per-Endpoint Overrides
Customize listen interface for specific endpoints:
net:
listen_interface: "virtual" # Default
start_port: 9080
overrides:
my-endpoint: "0.0.0.0" # This endpoint uses network mode
other-endpoint: "eth0" # This one uses eth0
Configuration File
Default location: /etc/ngrokd/config.yml
Complete Configuration Example
api:
url: https://api.ngrok.com
key: "" # Set via: ngrokctl set-api-key
ingressEndpoint: "kubernetes-binding-ingress.ngrok.io:443"
server:
log_level: info
socket_path: /var/run/ngrokd.sock
client_cert: /etc/ngrokd/tls.crt
client_key: /etc/ngrokd/tls.key
bound_endpoints:
poll_interval: 30 # Seconds between API polls
net:
interface_name: ngrokd0
subnet: 10.107.0.0/16
listen_interface: "virtual"
start_port: 9080
overrides: {} # Per-endpoint overrides
Configuration Options
| Option | Description | Default |
|---|---|---|
api.url |
ngrok API URL | https://api.ngrok.com |
api.key |
ngrok API key (set via ngrokctl) | "" |
ingressEndpoint |
mTLS ingress endpoint | kubernetes-binding-ingress.ngrok.io:443 |
server.log_level |
Log level (info, debug, error) | info |
bound_endpoints.poll_interval |
Seconds between endpoint discovery | 30 |
net.listen_interface |
virtual, 0.0.0.0, IP, or interface name | virtual |
net.start_port |
Starting port for network mode | 9080 |
net.overrides |
Per-endpoint listen_interface overrides | {} |
Hot Reload
The daemon watches the config file and automatically reloads changes:
# Edit config
sudo nano /etc/ngrokd/config.yml
# Or in Docker
docker exec -it ngrokd ngrokctl config edit
# Changes apply automatically in ~5 seconds
# Endpoints are rebound if listen_interface changes
Technical Details
Architecture
┌─────────────────────────────────────────────────────┐
│ Virtual Mode (same-machine) │
│ │
│ App → /etc/hosts → 127.0.0.2 → Dedicated Listener │
│ → Host Rewrite → mTLS → ngrok cloud → Backend │
├─────────────────────────────────────────────────────┤
│ Network Mode (cross-machine) │
│ │
│ External Client → DNS (→ LAN IP) → Shared Listener │
│ → SNI/Host Routing → mTLS → ngrok cloud │
│ → Backend │
└─────────────────────────────────────────────────────┘
How It Works
1. Virtual Network Interface
Creates an interface with subnet for unique IP allocation:
- Linux: dummy interface
ngrokd0with 10.107.0.0/16 subnet - macOS: loopback aliases on
lo0with 127.0.0.0/8 subnet
2. IP Allocation
Each discovered endpoint gets a unique IP:
10.107.0.2 → api.example.com
10.107.0.3 → web.example.com
10.107.0.4 → db.example.com
IP mappings persist across restarts in /etc/ngrokd/ip_mappings.json
3. DNS Management
Virtual mode: Updates /etc/hosts with unique IPs per endpoint:
# BEGIN ngrokd managed section
127.0.0.2 api.example.com
127.0.0.3 web.example.com
# END ngrokd managed section
Network mode: Runs a built-in DNS server that resolves all managed hostnames to the LAN IP. External clients point their DNS at the ngrokd machine. Per-domain routing configured automatically (macOS: /etc/resolver/, Linux: resolvectl or resolv.conf).
4. Traffic Forwarding
Virtual mode: Each endpoint has a dedicated listener on its unique IP:
App → Listener (127.0.0.2:443) → mTLS → ngrok cloud → Backend
Network mode: Shared listeners multiplex by SNI (TLS) or Host header (HTTP):
External client → DNS (→ LAN IP) → Shared listener (0.0.0.0:443) → SNI routing → mTLS → ngrok cloud → Backend
Network Modes
Virtual Mode
Pros:
- Multiple endpoints can use the same port (e.g., all on port 80)
- DNS works via /etc/hosts
- Clean separation per endpoint
Cons:
- Only accessible from same machine
- Requires root for /etc/hosts and interface management
Network Mode
Pros:
- Accessible from any device on the network (phones, VMs, CI runners)
- Multiple endpoints share original ports (80, 443) via SNI/Host routing
- Built-in DNS server — external clients just point DNS at ngrokd
- Supports both exact and wildcard (*.example.com) endpoints
Cons:
- Requires port 53 available for DNS server
- External clients must configure DNS to ngrokd machine's IP
- Raw TCP endpoints (non-HTTP/TLS) fall back to port remapping
Automatic Features
- Port Conflict Resolution: Automatically finds next available port if conflict detected
- Built-in DNS: Auto-starts in network mode for external client resolution. Supports exact and wildcard endpoints with per-domain routing.
- Shared Listeners: HTTP/HTTPS/TLS endpoints share original ports (80, 443) with SNI and Host header routing — no port remapping needed.
- Certificate Auto-Renewal: Threshold-based checking renews certificates when remaining lifetime is less than 1/3 of total lifetime
- Hot Certificate Reload: Zero-downtime certificate rotation — new certificates are loaded without restarting listeners
- Operator Recovery: Automatic re-registration if the operator binding is deleted (404 recovery)
- Hot Reload: Config changes detected and applied automatically
- State Persistence: IP and port mappings survive restarts
Troubleshooting
No Endpoints Discovered
Symptoms: ngrokctl list shows 0 endpoints
Solutions:
- Wait 30 seconds for first poll cycle
- Verify API key is set:
ngrokctl status - Check bound endpoints exist in ngrok dashboard
- Check daemon logs:
tail ~/ngrokd.log
Connection Refused
Symptoms: curl: (7) Failed to connect
Solutions:
- Verify daemon is running:
ps aux | grep ngrokd - Check endpoint is listed:
ngrokctl list - Verify DNS entry in /etc/hosts (virtual mode)
- Check IP allocation:
cat /etc/ngrokd/ip_mappings.json
Port Already in Use
Symptoms: bind: address already in use
What happens: Daemon automatically retries with next port (up to 20 attempts)
Check logs:
tail ~/ngrokd.log | grep "Port in use, trying next port"
If you see this, the conflict was automatically resolved!
Docker: Can't Access Endpoints from Host
Issue: Using virtual mode, virtual IPs only exist inside container
Solution: Use listen_interface: "0.0.0.0" for Docker
# Edit config
docker exec -it ngrokd ngrokctl config edit
# Change to:
listen_interface: "0.0.0.0"
# Wait ~5s for reload
docker exec ngrokd ngrokctl list
# Access from host
curl http://localhost:9080/
Network Interface Errors
Symptoms: Failed to create virtual network interface
Solutions:
- Ensure running as root/sudo
- On Linux: Check kernel modules for dummy interface
- On macOS: Verify permissions for ifconfig
- Check logs for specific error details
Certificate Errors
Symptoms: Connection failures after long uptime, TLS handshake errors
Solutions:
- Force certificate renewal:
ngrokctl refresh-cert --force - Check certificate status in logs:
tail ~/ngrokd.log | grep cert - Verify certificate files exist:
ls -la /etc/ngrokd/tls.*
Invalid Interface Name
Symptoms: Config validation failed with interface name
Solution: Check available interfaces:
# Linux
ip addr show
# macOS
ifconfig
# Docker
docker exec ngrokd ip addr show
Use the correct interface name from the list (e.g., enp0s1 instead of eth0)
CLI Reference
ngrokctl Commands
| Command | Description |
|---|---|
ngrokctl status |
Check daemon registration status |
ngrokctl list |
List discovered endpoints |
ngrokctl health |
Detailed health information |
ngrokctl set-api-key KEY |
Set ngrok API key |
ngrokctl config edit |
Edit configuration file |
ngrokctl refresh-cert |
Check and renew certificate if expiring |
ngrokctl refresh-cert --force |
Force immediate certificate renewal |
Requirements
- ngrok API Key: Get from https://dashboard.ngrok.com/api
- Bound Endpoints: Create Kubernetes bound endpoints in ngrok dashboard
- Root/sudo: Required for network interface and /etc/hosts management
- Platform: Linux, macOS, or Docker (Windows planned)