tunnelctl
Concepts

The FRP transport

How the embedded FRP client carries traffic, and why tunnelctl forks frp.

Under the hood, tunnelctl moves traffic with FRP (fast reverse proxy). What's unusual is that the FRP client is embedded in the CLI — there's no separate frpc process to install or configure, and no .toml file is written to disk. The CLI builds the client config in memory and runs the FRP service in-process.

The path of a request

The embedded FRP client opens an encrypted control channel to the tunnel edge (FRP server) — by default over wss on port 443.
It registers your public hostname (<slug>.tunnelctl.eu) using the tunnel's connection token.
A server-side plugin validates the token with the control plane (signature, serial, slug) before allowing the proxy.
Public HTTP/HTTPS requests to the hostname are routed down the control channel and forwarded to your local host:port.

Tuning

A couple of up flags map onto FRP behaviour:

  • --pool-count pre-allocates work connections so the first requests don't pay a connection-setup cost.
  • --host-header-rewrite rewrites the outgoing Host header, which matters for local apps that route by virtual host.

Why a fork

Upstream frpc exposes no signal for control-channel connect/disconnect events. tunnelctl maintains a small fork that adds a login-state callback, so the per-tunnel daemon can detect when the control channel drops and transition to Reconnecting without polling. The fork tracks upstream closely and is published under the git.nubeo.eu/microservices/frp module path.

Daemon state from FRP

The connect/disconnect callback is what drives the ConnectedReconnecting states you see in tunnelctl status.

On this page