expose is a self-hosted HTTP tunnel. Run your own server, then expose local HTTP ports from any machine - no third-party tunnel service required.
- HTTPS-only public traffic with automatic TLS (ACME) or static wildcard certificates
- Built-in WAF blocks SQL injection, XSS, path traversal, and other attacks before they reach your app, including bounded JSON/form/text body inspection
- Static site publishing via
expose staticfor quick docs, SPAs, and local folders - Server-hosted static sites via
expose pub, with persistent URLs, optional expiry, guarded archive uploads, and live stats viaexpose pub connect - Multi-route configs via
expose.yml- expose multiple services under one subdomain with path-based routing - Server-local services via
EXPOSE_BYPASSES- reserve subdomains for local HTTP ports, with WAF enabled by default. See server configuration. - Real-time client dashboard with request log, latency percentiles, WAF counters, and connection stats
- Auto-update - server and client can self-update in the background with automatic restart and client reconnection
- Password-protected tunnels with a built-in access form and edge session cookie (per-tunnel or per-config)
- Env-first configuration - minimal CLI flags,
.envsupport, and interactive setup wizards - Rate limiting on tunnel registration plus optional public traffic throttling per hostname+client IP
- Persistent login - authenticate once with
expose login, credentials are saved locally - Automatic reconnection with exponential backoff and keepalive pings
- HTTP/3 over QUIC support - clients can use
--transport=quicfor compatibility or negotiated multi-stream mode - Optional WebRTC TURN relay on the public server, with UDP, TCP/TLS and short-lived credentials. See TURN relay.
- The server terminates TLS, runs WAF inspection, and routes requests by hostname to the correct tunnel
- The client registers via API key, then opens a persistent WebSocket or HTTP/3 tunnel to the server
- Requests and responses flow over the tunnel as versioned binary frames, with raw body bytes for inline and streamed payloads
Large HTTP uploads and downloads stream in bounded chunks without a total-body size cap. Upstream applications enforce their own upload limits; WAF inspection, frame sizes, queues, and timeouts remain bounded.
For the full request lifecycle and component breakdown, see Architecture Overview.
For transport mode details (ws|quic) and QUIC requirements, see Client Configuration and UDP Deployment Topologies.
- A server or VPS with a public IP - or a home server with port forwarding configured
- A domain you control (e.g.
example.com) - A DNS wildcard A record (
*.example.com) pointing to your server's public IP - Port mappings from public
443/tcpto internal10443/tcpfor normal HTTPS URLs, public80/tcpto internal10080/tcpfor ACME HTTP-01, and public443/udpto internal10443/udpfor QUIC
Release binaries are published for macOS, Linux, and Windows on both amd64 and arm64, including Windows on Arm.
macOS and Linux:
curl -fsSL https://raw.githubusercontent.com/koltyakov/expose/main/scripts/install.sh | shWindows PowerShell:
irm https://raw.githubusercontent.com/koltyakov/expose/main/scripts/install.ps1 | iexTo uninstall, run the matching script:
curl -fsSL https://raw.githubusercontent.com/koltyakov/expose/main/scripts/uninstall.sh | shirm https://raw.githubusercontent.com/koltyakov/expose/main/scripts/uninstall.ps1 | iexOr install from source with Go 1.26 or newer:
go install github.com/koltyakov/expose/cmd/expose@latestOn your public-facing machine, run the interactive setup (writes a .env for you):
expose server init # guided setup
expose server # start the tunnel serverThen create an API key for your client(s):
expose apikey create --name defaultOn any machine you want to expose:
# Login once to save credentials locally
expose login
# Expose a local HTTP port (e.g. 3000) to the internet
expose http 3000
# Or expose it with a named subdomain
expose http 3000 --domain=myapp
# Or expose a static directory as a website
expose static ./public
# Or upload a static site for persistent server-side hosting
expose pub ./dist --ttl=24h
# Track open pages as active visitors:
expose pub ./dist --ws
# List your tunnels and published sites across machines
expose listFor multiple servers, save a named login and select it per command:
expose login --profile work --server work.example.com --api-key <KEY>
expose http 3000 --profile work
expose list --profile workOmit --profile to keep using your existing default login. Named logins do not change it. See Client profiles for details.
Open the URL shown in the terminal - that's it.
β οΈ Security notice: if your server is using per-host ACME certificates (dynamic, orautowithout a matching wildcard certificate), new public hostnames are often discovered and probed by bots shortly after they are created. Protect new tunnels immediately and use--protectfor anything non-public. See TLS Modes and Static Sites.
For the full walkthrough, DNS setup guides, and multi-route configs, see Quick Start.
See Static site publishing for named domains, listing, deletion, and upload limits.
See the docs/ folder for all guides - server & client configuration, static sites, TLS modes, DNS setup, deployment, WAF, auto-update, troubleshooting, and more.
Build and install locally:
make installGenerate coverage reports:
make cov- ngrok - the gold standard for HTTP tunnels and a huge inspiration.
exposeexists because I needed more freedom and control over infrastructure, but ngrok paved the way. - OpenAI and Anthropic - AI-assisted development boosted the entire build lifecycle by 10Γ.
MIT


