Outbound is a HTTP tunneling system that exposes local services through a public edge server using a long-lived gRPC stream.
How it works
[HTTP client] ── HTTPS ──▶ [edge :443 via proxy] ── gRPC ──▶ [agent] ── HTTP ──▶ [local service]
- The edge runs on a public server. It accepts HTTP requests and dispatches them over gRPC to the appropriate agent.
- The agent runs on your local machine. It connects to the edge, registers named services, and proxies inbound requests to local ports.
- Routing is header-based — callers include
X-Outbound-AgentandX-Outbound-Serviceon every request.
Quickstart
1. Build
go build -o outbound-edge ./cmd/edge go build -o outbound-agent ./cmd/agent
2. Run the edge (on your public server)
./outbound-edge --addr :8080 --auth-secret mysecret
3. Run the agent (on your local machine)
Expose a local service running on port 3000 as the service named web:
./outbound-agent \ --id my-machine \ --service web=3000 \ --token mysecret \ --edge edge.example.com:443
Pass --insecure for local development when the edge has no TLS:
./outbound-agent --id my-machine --service web=3000 --token mysecret --edge localhost:8080 --insecure
4. Make a request
curl \ -H "X-Outbound-Agent: my-machine" \ -H "X-Outbound-Service: web" \ https://edge.example.com/
5. Multiple services
./outbound-agent \ --id my-machine \ --service web=3000 \ --service api=8000 \ --service metrics=9090 \ --edge edge.example.com:443
curl -H "X-Outbound-Agent: my-machine" -H "X-Outbound-Service: api" https://edge.example.com/v1/users curl -H "X-Outbound-Agent: my-machine" -H "X-Outbound-Service: metrics" https://edge.example.com/metrics
HTTPS and custom domains
TLS is not handled inside the edge binary. Put Caddy (or any reverse proxy) in front of the edge and terminate TLS there. The edge listens on a single port and demultiplexes HTTP and gRPC traffic automatically.
[HTTP client] ─── HTTPS (443) ───┐
[Caddy] ─── plain HTTP+gRPC ──▶ edge :8080
[agent] ─── gRPC+TLS (443) ───┘
Caddy
edge.example.com { reverse_proxy h2c://127.0.0.1:8080 }
Keep the port bound to 127.0.0.1 so only the proxy can reach it.
Flags reference
edge
| Flag | Default | Description |
|---|---|---|
--addr |
:8080 |
Listen address for both HTTP and gRPC (demultiplexed via cmux) |
--auth-secret |
(none) | Shared secret required for agent registration; if unset all connections are accepted |
--request-timeout |
30s |
Timeout for proxied HTTP requests |
--keepalive-interval |
15s |
Interval between keepalive pings sent to agents |
--keepalive-timeout |
5s |
Time to wait for a pong before dropping the session |
--shutdown-timeout |
30s |
Graceful shutdown timeout |
agent
| Flag | Default | Description |
|---|---|---|
--id |
(auto-generated) | Agent identifier |
--edge |
localhost:8081 |
Edge address (host:port) |
--token |
(none) | Shared auth token for edge registration |
--service |
(required, repeatable) | Service mapping name=port |
--insecure |
false |
Disable TLS on the gRPC connection (local dev only) |