A self-hosted ngrok alternative built in Rust. Expose your local services to the internet through secure tunnels.
# Clone and build
git clone https://github.com/copyleftdev/zgrok.git
cd zgrok
cargo build --release
# Binaries are in target/release/
ls target/release/zgrok target/release/zgrok-edge# Start a tunnel to your local server on port 8080
./target/release/zgrok http 8080
# With a custom subdomain
./target/release/zgrok http 8080 --subdomain myapp
# With authentication
./target/release/zgrok http 8080 --authtoken zg_your_token_here# Use a publicly trusted wildcard certificate, or create a private CA and
# sign a server certificate for both the base and wildcard domains.
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
-keyout ca-key.pem -out ca.pem -subj "/CN=zgrok private CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
openssl req -newkey rsa:4096 -nodes -keyout key.pem -out server.csr \
-subj "/CN=yourdomain.com" \
-addext "subjectAltName=DNS:yourdomain.com,DNS:*.yourdomain.com"
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -days 365 -copy_extensions copy -out cert.pem
# Start the edge server
./target/release/zgrok-edge \
--cert cert.pem \
--key key.pem \
--domain yourdomain.com \
--https-port 443 \
--http-port 80 \
--agent-port 4443 \
--auth-token zg_replace_with_a_secret
# Point the agent at the self-hosted edge
./target/release/zgrok config set edge_url wss://yourdomain.com:4443
./target/release/zgrok config set public_domain yourdomain.com
./target/release/zgrok config set authtoken zg_replace_with_a_secret
./target/release/zgrok config set ca_cert /path/to/ca.pem # private CA only┌─────────────┐ HTTPS ┌─────────────┐ Multiplexed ┌─────────────┐
│ Browser │ ──────────────▶│ Edge │◀───────────────── │ Agent │
│ │ subdomain. │ Server │ Tunnel │ (CLI) │
└─────────────┘ domain.com └─────────────┘ └──────┬──────┘
│ HTTP
▼
┌─────────────┐
│ Your │
│ Service │
└─────────────┘
| Component | Crate | Description |
|---|---|---|
| Agent | zgrok-agent |
CLI client that runs on your machine |
| Edge | zgrok-edge |
Public server that terminates TLS and routes traffic |
| Control | zgrok-control |
Authentication, sessions, subdomain allocation |
| Protocol | zgrok-protocol |
Binary framing, stream multiplexing, keepalive |
| Common | zgrok-common |
Shared utilities, observability, validation |
zgrok - Expose local services via secure tunnels
USAGE:
zgrok [OPTIONS] <COMMAND>
COMMANDS:
http Start an HTTP tunnel
config Manage configuration
OPTIONS:
-c, --config <PATH> Path to config file
-v, --verbose Increase verbosity (-v, -vv, -vvv)
--no-color Disable colored output
zgrok http [OPTIONS] <PORT>
ARGUMENTS:
<PORT> Local port to expose
OPTIONS:
--host <HOST> Local host to forward to [default: localhost]
-s, --subdomain <SUBDOMAIN> Request specific subdomain
--authtoken <TOKEN> API key for authentication
--region <REGION> Edge region to connect to
--inspect Enable request inspection UI
Create ~/.config/zgrok/config.toml:
authtoken = "zg_your_token_here"
region = "us-east"
edge_url = "wss://tunnel.example.com:4443"
public_domain = "tunnel.example.com"
ca_cert = "/path/to/private-ca.pem" # only needed for private/self-signed CAs
[defaults]
inspect = true
[tunnels.webapp]
proto = "http"
addr = "8080"
subdomain = "myapp"
[tunnels.api]
proto = "http"
addr = "3000"
subdomain = "api"zgrok-edge - Edge/gateway server for zgrok tunneling service
OPTIONS:
--https-port <PORT> HTTPS listen port [default: 443]
--http-port <PORT> HTTP redirect port, 0 to disable [default: 80]
--agent-port <PORT> Agent tunnel port, 0 to disable [default: 4443]
--auth-token <TOKEN> Token required from connecting agents [env: ZGROK_AUTH_TOKEN]
--listen <ADDR> Listen address [default: 0.0.0.0]
--cert <PATH> TLS certificate (PEM)
--key <PATH> TLS private key (PEM)
--domain <DOMAIN> Base domain for subdomains [default: zgrok.io]
-v, --verbose Enable verbose logging
| Variable | Description |
|---|---|
ZGROK_AUTHTOKEN |
Default authentication token |
ZGROK_AUTH_TOKEN |
Token accepted by the edge agent listener |
ZGROK_CERT_PATH |
TLS certificate path |
ZGROK_KEY_PATH |
TLS private key path |
FROM rust:1.75 as builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/zgrok-edge /usr/local/bin/
EXPOSE 443 80 4443
ENTRYPOINT ["zgrok-edge"]# /etc/systemd/system/zgrok-edge.service
[Unit]
Description=zgrok Edge Server
After=network.target
[Service]
Type=simple
User=zgrok
ExecStart=/usr/local/bin/zgrok-edge \
--cert /etc/zgrok/cert.pem \
--key /etc/zgrok/key.pem \
--domain example.com \
--agent-port 4443
Environment=ZGROK_AUTH_TOKEN=zg_replace_with_a_secret
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetConfigure your DNS with a wildcard A record:
*.tunnel.example.com A <your-edge-server-ip>
Then run the edge server with --domain tunnel.example.com.
- HTTP Tunneling: Streaming methods, query strings, headers, and binary bodies
- WebSocket Relay: Bidirectional upgraded connections over tunnel streams
- Resilient Tunnels: Concurrent streams, keepalive, and automatic reconnect
- TLS Termination: HTTPS at the edge and TLS-protected agent connections
- Subdomain Routing: Each tunnel gets a unique subdomain
- Authenticated Agents: Required shared-token authentication
- Self-Hosting: Public CA and private CA certificate support
# Run all tests
cargo test
# Run with verbose output
cargo test -- --nocapture
# Lint
cargo clippy -- -D warnings
# Format
cargo fmt
# Build release
cargo build --releasecrates/
├── zgrok-protocol/ # Wire protocol, frame codec, multiplexer
├── zgrok-agent/ # CLI client with TUI
├── zgrok-edge/ # Public-facing gateway server
├── zgrok-control/ # Auth, sessions, subdomain allocation
├── zgrok-common/ # Shared types, observability, validation
└── zgrok-integration-tests/ # Cross-crate integration tests
MIT
See CONTRIBUTING.md for guidelines.