Port-based TCP proxy for Cloudflare Access tunnels. It listens on the configured public port range, asks the tunnel manager for the origin hostname assigned to the accepted port, starts or reuses cloudflared access tcp, and forwards raw TCP bytes end-to-end.
- Listens on every public port in
PUBLIC_PORT_RANGE_START-PUBLIC_PORT_RANGE_END(30000-30499by default). - Listens on
HEALTH_PORT(29999by default) and returnssuccesswithout route lookup or tunnel startup. - Resolves routes through local tunnel manager
get_tcp_route(public_port)first, then falls back to the public tunnel manager API. - Caches successful route lookups in memory for the process lifetime.
- Starts one local
cloudflared access tcp --hostname <origin>process per origin hostname. - Full-duplex raw TCP piping with no TLS, SNI, PROXY, or protocol pre-parsing.
- Ensure
cloudflaredis installed and on PATH. - Ensure tunnel manager is running and has ChainStore TCP route entries for the public ports.
- Build/run:
go run ./cmd/tcp_tunnel_proxy # or go build -o tcp-tunnel-proxy ./cmd/tcp_tunnel_proxy && ./tcp-tunnel-proxy
- Clients connect directly to the allocated public endpoint, for example
tcp.ratio1.link:30001.
- Download and install the release binary (pick the right arch):
Replace
curl -L https://github.com/Ratio1/tcp-tunnel-proxy/releases/latest/download/tcp-tunnel-proxy-linux-amd64.tar.gz -o /tmp/tcp-tunnel-proxy.tar.gz sudo tar -xzf /tmp/tcp-tunnel-proxy.tar.gz -C /usr/local/bin sudo mv /usr/local/bin/tcp-tunnel-proxy-linux-amd64 /usr/local/bin/tcp-tunnel-proxy sudo chmod +x /usr/local/bin/tcp-tunnel-proxy
amd64witharm64if needed. - Create
/etc/systemd/system/tcp-tunnel-proxy.serviceand place environment overrides directly in the unit:[Unit] Description=TCP Tunnel Proxy After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/local/bin/tcp-tunnel-proxy Environment=LISTEN_HOST= Environment=HEALTH_PORT=29999 Environment=PUBLIC_PORT_RANGE_START=30000 Environment=PUBLIC_PORT_RANGE_END=30499 Environment=LOCAL_TUNNEL_MANAGER_BASE_URL=http://127.0.0.1:29998 Environment=TUNNEL_MANAGER_BASE_URL=https://tunnels-manager.ratio1.ai Environment=LOCAL_PORT_RANGE_START=20000 Environment=LOCAL_PORT_RANGE_END=20100 Environment=LOG_FORMAT=plain Restart=on-failure RestartSec=2s LimitNOFILE=65536 [Install] WantedBy=multi-user.target
- Reload systemd and start:
sudo systemctl daemon-reload sudo systemctl enable --now tcp-tunnel-proxy.service sudo systemctl status tcp-tunnel-proxy.service
- Route source of truth: tunnel manager owns the ChainStore port registry. This proxy only calls
get_tcp_route(public_port). - Route lookup order: the proxy tries
LOCAL_TUNNEL_MANAGER_BASE_URLfirst, thenTUNNEL_MANAGER_BASE_URL. SetLOCAL_TUNNEL_MANAGER_BASE_URLto an empty value to disable local lookup. - Health checks: connections to
HEALTH_PORTreceivesuccessand close; the health port must not overlap the public port range. - Cache behavior: successful port lookups are cached indefinitely for this process. Route deletion or port reuse can require a proxy restart until invalidation exists.
- Cloudflared lifecycle: starts on first connection per origin hostname, waits for local port readiness, increments refcounts, and tears down idle tunnels after
IDLE_TIMEOUT. - Crashes: if cloudflared exits while connections are active, the manager attempts restart.
LISTEN_HOST: host/interface to bind (empty means all interfaces).HEALTH_PORT: TCP health-check port that returnssuccesswithout route lookup or tunnel startup (default29999).PUBLIC_PORT_RANGE_START/PUBLIC_PORT_RANGE_END: public ports to listen on.LOCAL_TUNNEL_MANAGER_BASE_URL: local tunnel manager API base URL tried first (defaulthttp://127.0.0.1:29998).TUNNEL_MANAGER_BASE_URL: public tunnel manager API fallback base URL.ROUTE_LOOKUP_TIMEOUT: timeout forget_tcp_routerequests (default5s).IDLE_TIMEOUT: duration before idle tunnels are torn down (default300s).STARTUP_TIMEOUT: how long to wait forcloudflaredto become ready (default15s).LOCAL_PORT_RANGE_START/LOCAL_PORT_RANGE_END: dynamic local port pool forcloudflared.LOG_FORMAT:plain(default) orjsonlogging.RESTART_BACKOFF: base delay between restart attempts when cloudflared exits (default2s).MAX_RESTARTS: maximum restart attempts while connections are active (default3).
- No persistence/log rotation; relies on stdout logging.
- No route invalidation yet; successful lookups remain cached until process restart.