frp — API Surface#
API types#
frp exposes four distinct API surfaces:
- CLI — Cobra-based command-line interface for both
frpsandfrpcbinaries - REST/HTTP — Two embedded HTTP servers: frpc’s admin API and frps’s dashboard API
- Server-side Webhook Plugin system — HTTP callback hooks for server-side lifecycle events
- Library/Programmatic —
pkg/virtual,pkg/sdk/client, and theclient.ServiceOptions.ConnectorCreatorinjection point
There is no gRPC API and no proto files in the repository.
REST/HTTP API#
frpc Admin API (default port 7400)#
- Router: gorilla/mux (
subRouteroff the rootRouter) - Route registration:
client.registerRouteHandlersinclient/api_router.go - Middleware chain:
helper.AuthMiddleware— HTTP Basic Auth (user/password fromwebServer.user/webServer.passwordconfig)httppkg.NewRequestLogger— request logging- Gzip for
/static/assets
- Authentication: HTTP Basic Auth on all
/api/*routes;/healthzis unauthenticated
Key endpoints:
| Method | Path | Description |
|---|---|---|
GET | /healthz | Liveness check — always 200, no auth |
GET | /api/reload | Hot-reload frpc config from disk (optional ?strictConfig=true) |
POST | /api/stop | Graceful stop of the running frpc process |
GET | /api/status | All proxy statuses grouped by type |
GET | /api/config | Get current running config as YAML/TOML/JSON text |
PUT | /api/config | Update and reload config from request body |
GET | /api/proxy/{name}/config | Get config for a named proxy |
GET | /api/visitor/{name}/config | Get config for a named visitor |
GET | /api/store/proxies | List dynamically-stored proxies (requires store config) |
POST | /api/store/proxies | Create a new dynamic proxy |
GET | /api/store/proxies/{name} | Get a dynamic proxy by name |
PUT | /api/store/proxies/{name} | Update a dynamic proxy |
DELETE | /api/store/proxies/{name} | Delete a dynamic proxy |
GET | /api/store/visitors | List stored visitors |
POST | /api/store/visitors | Create a stored visitor |
GET | /api/store/visitors/{name} | Get a visitor by name |
PUT | /api/store/visitors/{name} | Update a stored visitor |
DELETE | /api/store/visitors/{name} | Delete a stored visitor |
GET | /static/ | Embedded Vue.js web dashboard (gzip-served) |
The store endpoints are only registered when svr.storeSource != nil (i.e., a file-backed store is configured via common.store.path).
frps Dashboard API (default port 7500)#
- Router: gorilla/mux
- Route registration:
server.registerRouteHandlersinserver/api_router.go - Middleware chain:
helper.AuthMiddleware— HTTP Basic Authhttppkg.NewRequestLogger— request logging- Optional
promhttp.Handler()for Prometheus metrics
- Authentication: HTTP Basic Auth;
/healthzis unauthenticated
Key endpoints:
| Method | Path | Description |
|---|---|---|
GET | /healthz | Liveness check |
GET | /metrics | Prometheus metrics (only when enablePrometheus: true) |
GET | /api/serverinfo | Server build info, total connections, traffic stats |
GET | /api/proxy/{type} | All proxies of a type (tcp, udp, http, https, stcp, xtcp) |
GET | /api/proxy/{type}/{name} | Single proxy by type and name |
GET | /api/proxies/{name} | Single proxy by name (type-agnostic) |
GET | /api/traffic/{name} | Traffic statistics for a named proxy |
GET | /api/clients | List of connected frpc clients |
GET | /api/clients/{key} | Detail for a specific frpc client session |
DELETE | /api/proxies | Remove offline proxies |
GET | /static/ | Embedded Vue.js web dashboard |
CLI#
- Framework: Cobra (
github.com/spf13/cobra)
frps commands#
| Command | Description |
|---|---|
frps (root) | Run the frp server; loads config from -c frps.toml |
frps verify | Validate server config file without starting the server |
Global flags: -c/--config (config file path), -v/--version
frpc commands#
| Command | Description |
|---|---|
frpc (root) | Run the frp client; loads config from -c frpc.ini |
frpc verify | Validate client config file |
frpc reload | Hot-reload running frpc config via admin API |
frpc status | Print proxy status table via admin API |
frpc stop | Stop the running frpc process via admin API |
frpc nathole discover | STUN-based NAT type discovery (prints NAT type, external IP) |
Global flags: -c/--config, --config_dir (run one frpc per file in a directory), -v/--version, --strict_config, --allow-unsafe (enable experimental features)
Admin command flags: --api-timeout (default 30s) for reload/status/stop
The frpc proxy and frpc visitor command factories (NewProxyCommand, NewVisitorCommand in cmd/frpc/sub/proxy.go) exist to support running a single proxy type from the CLI without a config file, though they are not advertised as main entry points.
Flag normalization: rootCmd.SetGlobalNormalizationFunc(config.WordSepNormalizeFunc) normalizes _/- in flag names, so --strict-config and --strict_config are equivalent.
Plugin / Extension System#
frp has two distinct plugin systems — one server-side and one client-side — with very different mechanisms.
Server-side Webhook Plugins (pkg/plugin/server)#
- Mechanism: HTTP webhook — frps POSTs JSON to a user-provided HTTP endpoint at each lifecycle event
- Configuration: Declared in server config under
subServer.httpPlugins; each plugin entry specifiesaddr,path,ops(which events to receive), and optional TLS settings - Lifecycle events (ops):
Login— frpc client connectsNewProxy— proxy is being registeredCloseProxy— proxy is being closedPing— heartbeat receivedNewWorkConn— a work connection is openedNewUserConn— an end-user connects to a proxied port
- Request format:
POST {addr}{path}?version=0.1.0&op={Op}with JSON body{version, op, content}; headerX-Frp-Reqidfor tracing - Response format:
{reject: bool, reject_reason: string, unchange: bool, content: {...}}; settingreject: truecauses frps to refuse the operation - Plugin interface (
pkg/plugin/server/plugin.go):type Plugin interface { Name() string IsSupport(op string) bool Handle(ctx context.Context, op string, content any) (res *Response, retContent any, err error) } - Implementation:
httpPluginis the only concrete implementation (HTTP/HTTPS webhook); thePlugininterface is exported for potential in-process plugins - Manager (
pkg/plugin/server/manager.go): maintains per-op plugin slices; calls them sequentially — each plugin can modify the content (chain of responsibility)
Client-side Connection Plugins (pkg/plugin/client)#
- Mechanism: In-process connection interceptors; registered in a global
creatorsmap by name at program init - Registration:
plugin.Register(name, CreatorFn)— called in each plugin’sinit(), e.g.,static_file.go,https2http.go - Plugin interface (
pkg/plugin/client/plugin.go):type Plugin interface { Name() string Handle(ctx context.Context, connInfo *ConnectionInfo) Close() error } - Built-in plugins:
| Plugin | File | Description |
|---|---|---|
http2http | http2http.go | HTTP reverse proxy to local HTTP |
http2https | http2https.go | HTTP reverse proxy to local HTTPS |
https2http | https2http.go | TLS termination → local HTTP |
https2https | https2https.go | TLS termination → local HTTPS |
http_proxy | http_proxy.go | HTTP CONNECT proxy |
socks5 | socks5.go | SOCKS5 proxy server |
static_file | static_file.go | Serve a local directory over HTTP (with optional Basic Auth) |
tls2raw | tls2raw.go | TLS termination to plaintext TCP |
unix_domain_socket | unix_domain_socket.go | Forward to Unix socket |
virtual_net | virtual_net.go | Forward to the in-process WireGuard vnet |
- Extension point: Third-party code can call
plugin.Registerbeforeclient.NewServiceto add new plugin types. This is the only explicit extension point for frpc.
Library API (pkg/virtual, pkg/sdk/client, client)#
pkg/virtual — In-process VirtualClient#
- Purpose: Embed a fully functional frpc client inside another Go process, communicating with a co-located frps via
net.Pipe(no network sockets) - Public API:
type Client struct { ... } func NewClient(options ClientOptions) (*Client, error) func (c *Client) PeerListener() net.Listener func (c *Client) UpdateProxyConfigurer(proxyCfgs []v1.ProxyConfigurer) func (c *Client) Run(ctx context.Context) error func (c *Client) Service() *client.Service func (c *Client) Close() - Key option:
ClientOptions.HandleWorkConnCb— callback invoked for each work connection, enabling callers to intercept tunneled traffic programmatically - Used by:
pkg/vnetfor the WireGuard overlay feature
pkg/sdk/client — Go SDK for frpc admin API#
- Purpose: Thin Go wrapper around the frpc admin HTTP API; used by
frpc reload/status/stopCLI commands - Public API:
func New(host string, port int) *Client func (c *Client) SetAuth(user, pwd string) func (c *Client) GetProxyStatus(ctx, name) (*model.ProxyStatusResp, error) func (c *Client) GetAllProxyStatus(ctx) (model.StatusResp, error) func (c *Client) Reload(ctx, strictMode bool) error func (c *Client) Stop(ctx) error func (c *Client) GetConfig(ctx) (string, error) func (c *Client) UpdateConfig(ctx, content string) error
client.ServiceOptions — Injection surface for programmatic embedding#
type ServiceOptions struct {
Common *v1.ClientCommonConfig
ConfigSourceAggregator *source.Aggregator
ClientSpec *msg.ClientSpec
ConnectorCreator ConnectorCreator // injectable transport factory
HandleWorkConnCb func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
UnsafeFeatures *security.UnsafeFeatures
ConfigFilePath string
}ConnectorCreator is the key injection point: it lets callers swap the physical transport (TCP, KCP, QUIC) for anything that satisfies client.Connector — including net.Pipe for in-process use.
Notable API design observations#
No gRPC — purely HTTP JSON and a custom binary protocol. The frpc↔frps control plane uses its own framed JSON-over-TCP protocol (
pkg/msg), not gRPC or REST. The management APIs (dashboard, admin) are thin REST HTTP endpoints, not a separately versioned API contract.Server-side plugins are HTTP webhooks, not Go plugins. This is an unusual but pragmatic choice: webhook plugins are language-agnostic, require no shared linking, and can be implemented in any stack. The
X-Frp-Reqidheader enables distributed tracing across the frps→plugin call.Admin API is a thin mirror of internal state, not a full management plane. The frpc admin API was added post-launch; it covers the operational cases (reload, stop, status) but is not designed as a stable versioned contract. The Go SDK (
pkg/sdk/client) consumes it internally, which gives some implicit stability guarantee.The store API (
/api/store/*) enables runtime-dynamic proxy management. When a file-backedstoreis configured, proxies can be created and deleted via the admin HTTP API without editing config files. This is the closest frp comes to a proper management plane, enabling headless automation.pkg/virtualis the undocumented but architecturally significant embed path. By injecting aConnectorCreatorthat usesnet.Pipe, the entire frp client stack can run in-process. This is used for the WireGuard overlay feature and is the mechanism that makes frp embeddable as a library without forking or monkey-patching.