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.

Get Started View on GitHub

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

  1. Go to ngrok API dashboard
  2. Click "New API Key"
  3. Give it a description (e.g., "ngrokd daemon")
  4. 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

  1. Go to ngrok Endpoints dashboard
  2. Click "New Endpoint" → "Cloud"
  3. Choose "Kubernetes Operator" as the binding type
  4. Configure your endpoint:
    • URL: identifier.subdomain (e.g., api.myapp)
    • Traffic Policy: Optional routing rules, auth, rate limiting
  5. 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 ngrokd0 with 10.107.0.0/16 subnet
  • macOS: loopback aliases on lo0 with 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

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