Secure HTTP tunneling solution written in Rust that exposes local services to the internet through WebSocket connections.
- Introduction
- Features
- Prerequisites
- Quick Start (Test Server)
- Production Setup (Your Server)
- Web UI Dashboard
- Architecture
- Authentication & Security
- Routing Modes
- Certificate Management
- DNS Provider Setup
- Configuration Reference
- API Endpoints
- Building from Source
- Troubleshooting
- Version Compatibility
- Changelog
- License
ExposeME lets you share your local development server with the outside world by creating secure tunnels through your own domain. Turn http://localhost:3000 into https://myapp.yourdomain.com and share your work with clients, teammates, or testing tools.
How it works: You run ExposeME on a server (like a VPS) with your own domain. A client program on your development machine connects to your server via WebSocket using a binary protocol. When someone visits your public URL, requests get forwarded through the secure tunnel to your local application.
Why use your own server? Full control with your own domain and SSL certificates, complete privacy with no third-party dependencies, no rate limits, and cost-effective hosting on any VPS.
- Secure Tunneling - WebSocket-based encrypted tunnels with token authentication and binary protocol
- Your Own Domain - Use your domain with automatic SSL certificates via Let's Encrypt
- Multiple Routing Modes - Path-based (
domain.com/app/) or subdomain (app.domain.com) routing - Real-time Support - WebSocket connections and Server-Sent Events (SSE) streaming
- File Streaming - Large file uploads/downloads without memory buffering
- Auto-Reconnection - Automatic reconnection with configurable retry intervals
- Flexible SSL - Let's Encrypt (auto), bring your own certificates, or self-signed for development
- Multi-client Support - Multiple tunnels with unique identifiers
Before setting up your own production server, ensure you have:
- Domain name - A registered domain with DNS management access
- VPS or server - A server with a public IP address and ports 80/443 accessible
- DNS configuration - Ability to create A records pointing to your server
- SSL requirements - Email address for Let's Encrypt, or your own certificates
- For subdomain routing - DNS provider API access (Cloudflare, DigitalOcean, Azure, or Hetzner)
Note: If you just want to try ExposeME without setting up infrastructure, use the Quick Start (Test Server) option below.
Want to test ExposeME quickly without setting up your own server? Use our public test server:
No config file needed - just run with CLI args:
docker run -it --rm ghcr.io/arch7tect/exposeme-client:latest \
--server-url "wss://exposeme.org/tunnel-ws" \
--token "uoINplvTSD3z8nOuzcDC5JDq41sf4GGELoLELBymXTY=" \
--tunnel-id "my-tunnel" \
--local-target "http://host.docker.internal:3000"Or use the config file approach:
# Download the pre-configured client template
curl -O https://raw.githubusercontent.com/arch7tect/exposeme/master/config/client.toml.template
mv client.toml.template client.toml
# Edit only these two lines in client.toml:
tunnel_id = "my-tunnel" # Choose a unique tunnel name
local_target = "http://host.docker.internal:3000" # Your local service port
# Run the client
docker run -it --rm -v ./client.toml:/etc/exposeme/client.toml ghcr.io/arch7tect/exposeme-client:latestYour service will be accessible at: https://my-tunnel.exposeme.org/
Warning: Test Server Limitations:
- No uptime guarantee - service may be unavailable
- Testing only - not suitable for production use
- No support - use at your own risk
Configure DNS records for your domain:
Required:
your-domain.com→your-server-ip(A record)
For subdomain routing:
*.your-domain.com→your-server-ip(A record)
Wait for DNS propagation before proceeding (test with nslookup your-domain.com).
For rapid deployment on a fresh Ubuntu VPS:
Step 1: Install Docker
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common git
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt install docker-ceStep 2: Clone and Configure
git clone https://github.com/arch7tect/exposeme.git
cd exposeme
mkdir -p certs && chmod 777 certs
cp config/server.toml.template config/server.tomlStep 3: Create Environment Configuration
cat > .env <<EOF
EXPOSEME_DOMAIN=<your-domain.com>
EXPOSEME_EMAIL=<admin@your-domain.com>
EXPOSEME_DNS_PROVIDER=cloudflare
EXPOSEME_CLOUDFLARE_TOKEN=<your-cloudflare-token-here>
EXPOSEME_AUTH_TOKEN=<your-secure-auth-token-here>
EXPOSEME_ADMIN_TOKEN=<your-secure-admin-token-here>
RUST_LOG=info
EOFImportant: Replace all placeholders
<...>with your actual values!
Step 4: Start the Server
docker compose pull
docker compose up -d
docker compose logs -fOption A: Command line only (no config file needed):
docker run -it --rm ghcr.io/arch7tect/exposeme-client:latest \
--server-url "wss://example.com/tunnel-ws" \
--token "your_secure_auth_token" \
--tunnel-id "my-app" \
--local-target "http://host.docker.internal:3000"Option B: Using config file:
Create client.toml:
[client]
server_url = "wss://example.com/tunnel-ws"
auth_token = "your_secure_auth_token"
tunnel_id = "my-app"
local_target = "http://host.docker.internal:3000"
auto_reconnect = true
reconnect_delay_secs = 5
insecure = false # Set to true for self-signed certificates (development only)Run the client:
docker run -it --rm \
-v ./client.toml:/etc/exposeme/client.toml \
ghcr.io/arch7tect/exposeme-client:latestAccess your service:
- Subdomain:
https://my-app.example.com/ - Path-based:
https://example.com/my-app/
ExposeME includes a web dashboard built with Leptos (Rust WASM) for real-time monitoring and management.
Access: Visit https://yourdomain.com/ when no tunnel routes match.
The dashboard provides:
- Real-time tunnel status and metrics
- Traffic visualization and statistics
- Certificate management and renewal controls
- Active connection monitoring
ExposeME creates secure HTTP tunnels to expose local services:
- Tunnel Setup: Client connects to server via WebSocket upgrade on
/tunnel-ws(configurable) to establish a persistent tunnel - Request Forwarding: When users visit your public URL, the server forwards their HTTP requests through the existing WebSocket tunnel to your local service using a binary protocol
- Flow: Internet User → Server (HTTP(S)) → WebSocket Tunnel → Client → Local Service → Response back
- HTTP mode: Client connects to
ws://your-domain.com/tunnel-ws - HTTPS mode: Client connects to
wss://your-domain.com/tunnel-ws - Custom path: Configurable via
tunnel_pathsetting
ExposeME uses a binary protocol over WebSocket connections for efficient communication between client and server. The protocol handles HTTP request/response streaming, WebSocket proxying, and authentication.
ExposeME uses token-based authentication to control access. Configure your server with one or more tokens, and clients must provide a valid token to create tunnels.
Generate a secure token:
openssl rand -base64 32Security features:
- Token authentication: Only clients with valid tokens can create tunnels
- Tunnel isolation: Each tunnel has a unique ID and operates independently
- Transport encryption: All tunnel communication is encrypted when SSL is enabled
- Private URLs: Tunnel URLs are only known to those who create them
- Binary protocol: Efficient binary communication reduces overhead
ExposeME supports three routing modes. Choose based on your certificate setup and URL preferences.
https://your-domain.com/tunnel-id/path
- Single-domain SSL certificate
- HTTP-01 challenges (no DNS provider needed)
- Simpler setup
https://tunnel-id.your-domain.com/path
- Wildcard SSL certificate required
- DNS-01 challenges (DNS provider required)
- Cleaner URLs
Supports both routing methods simultaneously.
- Wildcard SSL certificate required
- DNS-01 challenges (DNS provider required)
- Maximum flexibility
ExposeME supports three certificate options:
Free SSL certificates with automatic renewal.
- Works with just domain name and email
- Auto-renewal every 90 days
- Supports both regular and wildcard certificates
Use certificates from any provider (Cloudflare, Namecheap, etc.).
File naming convention:
- Regular:
your-domain-com.pemandyour-domain-com.key - Wildcard:
wildcard-your-domain-com.pemandwildcard-your-domain-com.key - Example: For
example.com→example-com.pemandexample-com.key
Location: Place files in cert cache directory (default: /etc/exposeme/certs)
For local development and testing.
Warning: Browsers will show security warnings. Not suitable for production.
Note: DNS providers are only required for automatic Let's Encrypt wildcard certificates (subdomain routing). Path-based routing uses HTTP-01 challenges and requires no DNS provider. Manual or self-signed certificates don't require DNS providers regardless of routing mode.
- Create API token at https://cloud.digitalocean.com/account/api/tokens
- Add your domain to DigitalOcean Domains
- Set environment variables:
EXPOSEME_DNS_PROVIDER=digitalocean
EXPOSEME_DIGITALOCEAN_TOKEN=your_do_token- Create API token at https://dash.cloudflare.com/profile/api-tokens with:
- Zone:Zone:Read permissions
- Zone:DNS:Edit permissions
- Include: All zones (or specific zones)
- Add your domain to Cloudflare
- Set environment variables:
EXPOSEME_DNS_PROVIDER=cloudflare
EXPOSEME_CLOUDFLARE_TOKEN=your_cf_token- Create API token at https://dns.hetzner.com/ (Console → API tokens)
- Add your domain to Hetzner DNS
- Set environment variables:
EXPOSEME_DNS_PROVIDER=hetzner
EXPOSEME_HETZNER_TOKEN=your_hetzner_token- Create a Service Principal with DNS Zone Contributor role:
az ad sp create-for-rbac --name "exposeme-dns" \
--role "DNS Zone Contributor" \
--scopes "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/YOUR_RG"- Add your domain to Azure DNS (create DNS zone)
- Set environment variables:
EXPOSEME_DNS_PROVIDER=azure
EXPOSEME_AZURE_SUBSCRIPTION_ID=your_subscription_id
EXPOSEME_AZURE_RESOURCE_GROUP=your_resource_group_with_dns_zone
EXPOSEME_AZURE_CLIENT_ID=your_service_principal_client_id
EXPOSEME_AZURE_CLIENT_SECRET=your_service_principal_secret
EXPOSEME_AZURE_TENANT_ID=your_azure_tenant_idExposeME uses a layered configuration system where higher priority sources override lower ones:
- Command Line Arguments (highest priority)
- Environment Variables
- TOML Configuration File (lowest priority)
./exposeme-server [OPTIONS]
OPTIONS:
-c, --config <FILE> Configuration file path [default: server.toml]
--http-bind <IP> HTTP bind address
--http-port <PORT> HTTP port
--https-port <PORT> HTTPS port
--tunnel-path <PATH> WebSocket upgrade path [default: /tunnel-ws]
--domain <DOMAIN> Server domain name
--enable-https Enable HTTPS
--disable-https Disable HTTPS
--email <EMAIL> Contact email for Let's Encrypt
--staging Use Let's Encrypt staging environment
--wildcard Enable wildcard certificates
--routing-mode <MODE> Routing mode: path, subdomain, or both
--request-timeout <SECS> HTTP request timeout in seconds
--generate-config Generate default configuration file
-v, --verbose Enable verbose logging
-h, --help Print help information./exposeme-client [OPTIONS]
OPTIONS:
-c, --config <FILE> Configuration file path [default: client.toml]
-s, --server-url <URL> WebSocket server URL (upgrade endpoint)
-t, --token <TOKEN> Authentication token
-T, --tunnel-id <ID> Tunnel identifier
-l, --local-target <URL> Local service URL to forward to
--insecure Skip TLS certificate verification (for self-signed certificates)
--generate-config Generate default configuration file
-v, --verbose Enable verbose logging
-h, --help Print help information
# Connection Management
--auto-reconnect Enable automatic reconnection on disconnect
--no-auto-reconnect Disable automatic reconnection on disconnect
--reconnect-delay-secs <SECS> Delay before reconnection attempts [default: 5]
# WebSocket Configuration
--websocket-cleanup-interval <SECS> Cleanup check interval [default: 60]
--websocket-connection-timeout <SECS> Connection timeout [default: 10]
--websocket-max-idle <SECS> Maximum idle time [default: 600]
--websocket-monitoring-interval <SECS> Monitoring interval [default: 30]| Section | Option | Description | Default |
|---|---|---|---|
[server] |
domain |
Your domain name | localhost |
[server] |
routing_mode |
path, subdomain, or both |
path |
[server] |
http_port |
HTTP port | 80 |
[server] |
https_port |
HTTPS port | 443 |
[server] |
tunnel_path |
WebSocket upgrade path | /tunnel-ws |
[ssl] |
enabled |
Enable HTTPS | false |
[ssl] |
wildcard |
Use wildcard certificates (required for subdomain routing) | false |
[ssl] |
provider |
Certificate source: letsencrypt (automatic), manual (your own), selfsigned (development) |
letsencrypt |
[ssl] |
staging |
Use Let's Encrypt staging | true |
[ssl] |
cert_cache_dir |
Directory for storing certificates | /etc/exposeme/certs |
[ssl.dns_provider] |
provider |
DNS provider name (digitalocean, cloudflare, azure, hetzner) |
- |
[auth] |
tokens |
Authentication tokens | ["dev"] |
[auth] |
admin_token |
Admin token for observability API | - |
[limits] |
max_tunnels |
Maximum concurrent tunnels | 50 |
[limits] |
request_timeout_secs |
HTTP request timeout in seconds | 30 |
| Section | Option | Description | Default |
|---|---|---|---|
[client] |
server_url |
WebSocket server URL (upgrade endpoint) | ws://localhost/tunnel-ws |
[client] |
auth_token |
Authentication token | dev |
[client] |
tunnel_id |
Unique tunnel identifier | test |
[client] |
local_target |
Local service URL | http://localhost:3300 |
[client] |
auto_reconnect |
Auto-reconnect on disconnect | true |
[client] |
insecure |
Skip TLS verification (for self-signed certificates) | false |
[client] |
websocket_cleanup_interval_secs |
Cleanup check interval | 60 |
[client] |
websocket_connection_timeout_secs |
Connection timeout | 10 |
[client] |
websocket_max_idle_secs |
Maximum idle time | 600 |
[client] |
websocket_monitoring_interval_secs |
Monitoring interval | 30 |
| Variable | Description | Example |
|---|---|---|
EXPOSEME_DOMAIN |
Your domain | example.com |
EXPOSEME_EMAIL |
Contact email for Let's Encrypt | admin@example.com |
EXPOSEME_STAGING |
Use staging certificates | false |
EXPOSEME_WILDCARD |
Enable wildcard certificates | true |
EXPOSEME_ROUTING_MODE |
Routing mode | both |
EXPOSEME_DNS_PROVIDER |
DNS provider (only for wildcard certificates) | digitalocean, cloudflare, azure, or hetzner |
EXPOSEME_AUTH_TOKEN |
Authentication token | secure_token |
EXPOSEME_ADMIN_TOKEN |
Admin token for observability API | admin_secure_token |
EXPOSEME_REQUEST_TIMEOUT |
HTTP request timeout in seconds | 30 |
RUST_LOG |
Logging level | info, debug |
TRACING_LOG |
Advanced file logging configuration | See below |
Configure file logging with rotation using the TRACING_LOG environment variable:
# Size-based rotation (10MB per file, keep 7 files)
export TRACING_LOG="level=info,file=/var/log/exposeme/server.log,max_size=10M,max_files=7"
# Time-based rotation (daily rotation)
export TRACING_LOG="level=debug,file=/var/log/exposeme/server.log,rotation=daily"
# Simple file logging (no rotation)
export TRACING_LOG="level=info,file=/var/log/exposeme/server.log"TRACING_LOG Options:
level- Log level:trace,debug,info,warn,errorfile- Log file pathmax_size- Max file size before rotation (supportsK,M,Gsuffixes)max_files- Number of rotated files to keep (required withmax_size)rotation- Time-based rotation:minutely,hourly,daily,neverappend- Append to existing file:true,false
Rotated file naming: Files rotate as app.log, app.log.1, app.log.2, etc.
Docker compose example:
environment:
- TRACING_LOG=level=info,file=/var/log/exposeme/server.log,max_size=10M,max_files=7
volumes:
- ./logs:/var/log/exposeme:rwPermissions:
mkdir -p logs && chmod 777 logs/Viewing logs:
tail -f logs/server.logExposeME provides REST API endpoints for health monitoring, certificate management, and observability.
Health Check - GET /api/health
curl https://your-domain.com/api/healthCertificate Status - GET /api/certificates
curl https://your-domain.com/api/certificatesResponse:
{
"domain": "your-domain.com",
"exists": true,
"expiry_date": "2024-08-15T10:30:00Z",
"days_until_expiry": 45,
"needs_renewal": false,
"auto_renewal": true,
"wildcard": true
}View Metrics - GET /api/metrics
curl https://your-domain.com/api/metricsReturns comprehensive server and per-tunnel statistics:
{
"server": {
"uptime_seconds": 3600,
"active_tunnels": 2,
"total_requests": 1250,
"total_bytes_in": 450000,
"total_bytes_out": 890000,
"websocket_connections": 3,
"websocket_bytes_in": 12000,
"websocket_bytes_out": 8500,
"error_count": 5
},
"tunnels": [
{
"tunnel_id": "my-app",
"last_activity": 1699123456,
"requests_count": 850,
"bytes_in": 300000,
"bytes_out": 600000,
"websocket_connections": 2,
"websocket_bytes_in": 8000,
"websocket_bytes_out": 5000,
"error_count": 2
}
]
}Stream Metrics (SSE) - GET /api/metrics/stream
curl https://your-domain.com/api/metrics/streamStreams the same metrics data in real-time via Server-Sent Events.
Set the admin token via environment variable or TOML config:
Environment variable:
export EXPOSEME_ADMIN_TOKEN="your-secure-admin-token"Or in server.toml:
[auth]
admin_token = "your-secure-admin-token"Force Disconnect Tunnel - DELETE /admin/tunnels/<tunnel-id>
curl -X DELETE \
-H "Authorization: Bearer your-secure-admin-token" \
https://your-domain.com/admin/tunnels/my-appRenew SSL Certificate - POST /admin/ssl/renew
curl -X POST \
-H "Authorization: Bearer your-secure-admin-token" \
https://your-domain.com/admin/ssl/renew- Rust 1.88+
- Docker (optional)
- cargo-leptos (for UI builds)
# Clone repository
git clone https://github.com/arch7tect/exposeme.git
cd exposeme
# Basic build (no UI)
cargo build --release# Build images with UI included
docker build -t exposeme-server --target server .
docker build -t exposeme-client --target client .
# Or use the build script
./scripts/build-and-push.sh [version]DNS issues:
nslookup your-domain.com # Should resolve to your server IPPermission denied:
docker exec -it exposeme-server id # Check UID
sudo chown -R <uid>:<gid> ./certs
docker compose restartView logs:
docker compose logs -f exposeme-serverWhen using self-signed certificates for development, the client may fail to connect due to TLS verification errors. Use the insecure option to skip certificate verification:
Configuration:
[client]
server_url = "wss://your-domain.com/tunnel-ws"
insecure = true # Skip TLS verification for self-signed certificatesCommand line:
./exposeme-client --insecure --server-url wss://localhost/tunnel-wsWarning: The
insecureoption should only be used for development with self-signed certificates as it disables TLS certificate verification.
ExposeME uses a binary protocol for communication between client and server. Both client and server must be compatible versions:
- Major.Minor versions must match (e.g., 1.3.x client works with 1.3.x server)
- Patch versions are compatible within the same major.minor release
- Protocol changes require both client and server updates
Protocol Breaking Change: Version 1.4 includes connection management improvements that require both server and client to be updated together.
# Update server
docker compose pull
docker compose down
docker compose up -d
# Update client
docker pull ghcr.io/arch7tect/exposeme-client:latest
docker run -it --rm \
-v ./client.toml:/etc/exposeme/client.toml \
ghcr.io/arch7tect/exposeme-client:latestChanges
- Switched protocol serialization to bitcode (breaking)
Changes
- Added structured logging with contextual messages
New Features
- File Logging with rotation via TRACING_LOG
Fixes
- Stale connection handling with cancellation tokens
- Reliable tunnel cleanup on disconnect (RAII guards)
- WebSocket connection counter accuracy
- RWLock panic prevention (removed .unwrap() calls)
Improvements
- Tailwind build-time compilation (replaced CDN)
- WASM optimization (wasm-opt + compression)
GitHub Container Registry Migration
- Migrated Docker images from Docker Hub to GitHub Container Registry (ghcr.io)
- Automated CI/CD builds now publish directly to GitHub Packages
Modern Web UI Dashboard
- Modern Leptos WASM UI with TailwindCSS styling
- Real-time traffic visualization and metrics charts
- Enhanced certificate management with ACME renewal controls
Observability & Admin Features
- Built-in metrics collection (server stats, per-tunnel analytics)
- Admin API with Bearer token authentication (
/admin/tunnels/<id>,/admin/ssl/renew)
CLI-First Experience & Enhanced Reliability
- No config files required - run with command line arguments only
- Public test server - try ExposeME instantly without setup (exposeme.org)
- Faster network detection - 60s ping timeout detects connection issues sooner
- Better resource cleanup - improved tunnel and connection management
- Enhanced streaming - fixed client disconnection handling for large uploads
- Important: Requires both server and client to be v1.4+
Binary Protocol Implementation
- Replaced JSON protocol with efficient binary protocol using bitcode
- Reduced protocol overhead for better performance
- Maintained backward compatibility checking
- Important: Requires both server and client to be v1.3+
Enhanced Streaming Support
- Full HTTP request/response streaming without memory buffering
- Support for large file uploads and downloads
Real-Time Communication
- Native Server-Sent Events (SSE) support with proper headers and streaming
- Automatic reconnection handling for both SSE and WebSocket connections
Protocol Improvements
- Enhanced client-server protocol for streaming support
- Important: Requires both server and client to be v1.1+
MIT