Skip to content

Repository files navigation

Proxacloud Tunnel

Proxacloud Tunnel is a self-hosted secure tunnel platform for publishing private HTTP and TCP services through outbound agent connections. It lets a private server or Kubernetes cluster expose selected services through a public gateway without opening inbound firewall ports on the private network.

Use it when you want a managed entrypoint for private apps, SSH endpoints, internal dashboards, or Kubernetes services while keeping route configuration, agent tokens, audit logs, and operational visibility under your control.

Architecture

Internet client
  -> public gateway
  -> authenticated tunnel session
  -> private agent
  -> private HTTP or TCP target

The gateway is the public network entrypoint. The agent runs inside the private network and dials out to the gateway over a persistent WebSocket connection. HTTP requests are routed by Host header, serialized across the tunnel, and replayed by the agent against the configured private target. TCP routes use public gateway listener ports and stream bytes over the same authenticated agent connection model.

The repository also includes a control-plane API, React dashboard, PostgreSQL migrations, Helm chart, Linux systemd installer, Docker Compose development stack, and observability starter configuration.

For a complete project description, architecture, workflows, data model, and deployment guidance, read docs/architecture.md.

Components

  • cmd/gateway: public entrypoint that accepts internet HTTP traffic and agent tunnel connections.
  • cmd/agent: private-network client that dials out to the gateway and forwards HTTP requests plus TCP streams to internal targets.
  • cmd/api: control-plane API for auth, RBAC, tunnel management, route management, tokens, sessions, audit logs, and installer delivery.
  • internal/tunnel: shared protocol, route parsing, and configuration helpers.
  • web/dashboard: React + TypeScript + Vite dashboard.
  • deploy/helm/proxacloud-tunnel: Helm chart for gateway, API, dashboard, and agent.
  • deploy/observability: Prometheus, Grafana, Loki/OpenSearch guidance.

Request Workflow

HTTP route:

1. A client sends a request to the public gateway with a route hostname.
2. The gateway checks route policy, limits, WAF rules, and connected agents.
3. The gateway forwards the request through the selected agent session.
4. The agent sends the request to the private target URL.
5. The private service response returns through the agent and gateway.

TCP route:

1. A client connects to a configured public gateway port.
2. The gateway maps that port to a tunnel and private host:port target.
3. The gateway opens a stream over the connected agent session.
4. The agent opens a private TCP connection and relays bytes both ways.

Client IP forwarding:

  • The gateway forwards the public client IP to private HTTP targets through X-Forwarded-For and X-Real-IP.
  • Private, loopback, link-local, multicast, and unspecified addresses are filtered from the forwarded client identity.
  • Your private web server must be configured to trust and log one of those headers if you want its access log to show the original public client IP. See docs/client-ip-logging.md.

Local Development

Install Go 1.25+.

go test ./...
go build -buildvcs=false -o bin/gateway ./cmd/gateway
go build -buildvcs=false -o bin/agent ./cmd/agent
go build -buildvcs=false -o bin/api ./cmd/api

With Docker Compose installed:

docker compose up --build
curl -H "Host: app.local.test" http://localhost:8080

Apply the Phase 1 control-plane schema:

make migrate-up

On Windows PowerShell without make:

docker compose exec -T postgres psql -U proxacloud -d proxacloud -f /migrations/000001_control_plane.up.sql

Expected response:

hello from private service through proxacloud tunnel

Local service URLs:

  • Gateway: http://localhost:8080
  • API: http://localhost:8081
  • Dashboard: http://localhost:3000
  • Prometheus: http://localhost:9090
  • Grafana: http://localhost:3001, login admin/admin
  • Loki: http://localhost:3100

Default seeded admin for local development:

Email:    admin@example.com
Password: admin123456

Auth endpoints:

POST /api/v1/auth/login
POST /api/v1/auth/logout
POST /api/v1/auth/refresh
GET  /api/v1/auth/me

RBAC-protected endpoints:

GET /api/v1/system   requires metrics.read
GET /api/v1/tunnels  requires tunnels.read
GET /api/v1/users    requires users.read
GET /api/v1/roles    requires roles.read
GET /api/v1/audit-logs requires audit.read

Tunnel management endpoints:

GET    /api/v1/tunnels
POST   /api/v1/tunnels
DELETE /api/v1/tunnels/{id}
GET    /api/v1/tunnels/{id}/routes
POST   /api/v1/tunnels/{id}/routes
DELETE /api/v1/routes/{id}
GET    /api/v1/tunnels/{id}/tokens
POST   /api/v1/tunnels/{id}/tokens
DELETE /api/v1/tokens/{id}
GET    /api/v1/tunnels/{id}/sessions
GET    /api/v1/tunnels/{id}/tcp-routes
POST   /api/v1/tunnels/{id}/tcp-routes
DELETE /api/v1/tcp-routes/{id}
GET    /api/v1/caddy/domains
POST   /api/v1/caddy/domains
DELETE /api/v1/caddy/domains/{id}

Gateway dynamic routing:

  • If DATABASE_URL is set, gateway loads enabled routes from PostgreSQL.
  • Routes refresh every 10 seconds.
  • ROUTES env values still work as fallback defaults.
  • Dashboard-generated agent tokens can authenticate agents to the gateway.

Managed tunnel test flow:

1. Open dashboard and create a tunnel.
2. Add route app.local.test -> http://demo:9000.
3. Generate an agent token.
4. Start an agent with TUNNEL_ID=<dashboard tunnel UUID> and TUNNEL_TOKEN=<generated token>.
5. Wait up to 10 seconds for gateway route refresh.
6. curl -H "Host: app.local.test" http://localhost:8080

Linux agent systemd install docs are in docs/linux-agent.md.

Hetzner AlmaLinux VPS deployment docs are in docs/hetzner-deployment.md.

Managed HTTP and TCP/SSH routes are configured from the dashboard. Routes can be enabled, disabled, or deleted without editing server files. A typical SSH route is public port 2222 to private target 127.0.0.1:22, then connect with:

ssh -p 2222 user@tunnel.example.com

Agent tokens can be created with an expiry window, revoked from the dashboard, and rotated by generating a replacement token before revoking the old one.

The Domains page checks DNS resolution and TLS certificate status. Set PUBLIC_IP in the production environment so the dashboard can detect hostnames that do not point to the expected VPS.

Security hardening notes are in docs/security.md.

Observability docs and Grafana provisioning live in deploy/observability.

Gateway Configuration

Environment variables:

Variable Default Description
GATEWAY_ADDR :8080 Gateway listen address
TUNNEL_TOKEN dev-token Shared bearer token for agent authentication
ROUTES *=dev Comma-separated host mapping, such as app.example.com=prod,*=dev
GEOIP_DB_PATH unset Optional MaxMind-compatible country database path, such as /etc/proxacloud/GeoLite2-Country.mmdb, used by route country blocking when no trusted country header is present

Agent Configuration

Environment variables:

Variable Default Description
TUNNEL_ID dev Tunnel identity registered at the gateway
TUNNEL_TOKEN dev-token Shared bearer token
GATEWAY_URL ws://localhost:8080/__agent/connect Public gateway WebSocket URL
TARGET_URL http://localhost:9000 Private upstream target, such as http://traefik.kube-system.svc.cluster.local
AGENT_METRICS_ADDR :9091 Agent metrics and health listen address

Component Design

Area Choice
Language Go
Tunnel v1 WebSocket, HTTP/2-ready architecture
Future tunnel QUIC
Gateway Go reverse proxy
Agent Go Kubernetes deployment
Database PostgreSQL via DATABASE_URL
Cache Redis via REDIS_URL, optional
Frontend React + TypeScript + Vite
K8s packaging Helm
CI/CD GitHub Actions
Registry GHCR by default, private registry supported through Helm values
TLS Let's Encrypt ACME with cert-manager annotations
Metrics Prometheus + Grafana
Logs Structured stdout, Loki or OpenSearch downstream

Kubernetes Direction

Deploy the gateway in a public environment with ports 80 and 443 exposed by a LoadBalancer or ingress. Deploy the agent inside the private Kubernetes cluster with only outbound network access to the gateway.

Typical private-cluster target:

TARGET_URL=http://traefik.kube-system.svc.cluster.local

Install chart:

helm upgrade --install proxacloud deploy/helm/proxacloud-tunnel \
  --set global.imageRegistry=ghcr.io/YOUR_ORG/YOUR_REPO \
  --set global.imageTag=YOUR_COMMIT_SHA \
  --set gateway.ingress.enabled=true \
  --set gateway.ingress.host=tunnel.example.com

The Helm chart requires immutable image tags. Use the commit SHA tag pushed by CI, not latest.

Agent-only Kubernetes install docs are in docs/kubernetes-agent.md.

Future production steps:

  • Replace shared token auth with per-agent mTLS credentials.
  • Add TLS automation with ACME on the gateway.
  • Add PostgreSQL-backed control plane for tunnels and routes.
  • Add Helm charts and CRDs for Kubernetes-native route management.
  • Add Prometheus metrics and structured audit logging.
  • Add HTTP/2 or QUIC transport for higher concurrency and lower latency.

Roadmap

The detailed implementation roadmap lives in docs/phase-roadmap.md.

Phase 1 database migrations are in migrations.

About

proxacloud-tunnel

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages