frp — API Surface#

API types#

frp exposes four distinct API surfaces:

  1. CLI — Cobra-based command-line interface for both frps and frpc binaries
  2. REST/HTTP — Two embedded HTTP servers: frpc’s admin API and frps’s dashboard API
  3. Server-side Webhook Plugin system — HTTP callback hooks for server-side lifecycle events
  4. Library/Programmaticpkg/virtual, pkg/sdk/client, and the client.ServiceOptions.ConnectorCreator injection 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 (subRouter off the root Router)
  • Route registration: client.registerRouteHandlers in client/api_router.go
  • Middleware chain:
    1. helper.AuthMiddleware — HTTP Basic Auth (user/password from webServer.user/webServer.password config)
    2. httppkg.NewRequestLogger — request logging
    3. Gzip for /static/ assets
  • Authentication: HTTP Basic Auth on all /api/* routes; /healthz is unauthenticated

Key endpoints:

MethodPathDescription
GET/healthzLiveness check — always 200, no auth
GET/api/reloadHot-reload frpc config from disk (optional ?strictConfig=true)
POST/api/stopGraceful stop of the running frpc process
GET/api/statusAll proxy statuses grouped by type
GET/api/configGet current running config as YAML/TOML/JSON text
PUT/api/configUpdate and reload config from request body
GET/api/proxy/{name}/configGet config for a named proxy
GET/api/visitor/{name}/configGet config for a named visitor
GET/api/store/proxiesList dynamically-stored proxies (requires store config)
POST/api/store/proxiesCreate 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/visitorsList stored visitors
POST/api/store/visitorsCreate 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.registerRouteHandlers in server/api_router.go
  • Middleware chain:
    1. helper.AuthMiddleware — HTTP Basic Auth
    2. httppkg.NewRequestLogger — request logging
    3. Optional promhttp.Handler() for Prometheus metrics
  • Authentication: HTTP Basic Auth; /healthz is unauthenticated

Key endpoints:

MethodPathDescription
GET/healthzLiveness check
GET/metricsPrometheus metrics (only when enablePrometheus: true)
GET/api/serverinfoServer 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/clientsList of connected frpc clients
GET/api/clients/{key}Detail for a specific frpc client session
DELETE/api/proxiesRemove offline proxies
GET/static/Embedded Vue.js web dashboard

CLI#

  • Framework: Cobra (github.com/spf13/cobra)

frps commands#

CommandDescription
frps (root)Run the frp server; loads config from -c frps.toml
frps verifyValidate server config file without starting the server

Global flags: -c/--config (config file path), -v/--version

frpc commands#

CommandDescription
frpc (root)Run the frp client; loads config from -c frpc.ini
frpc verifyValidate client config file
frpc reloadHot-reload running frpc config via admin API
frpc statusPrint proxy status table via admin API
frpc stopStop the running frpc process via admin API
frpc nathole discoverSTUN-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 specifies addr, path, ops (which events to receive), and optional TLS settings
  • Lifecycle events (ops):
    • Login — frpc client connects
    • NewProxy — proxy is being registered
    • CloseProxy — proxy is being closed
    • Ping — heartbeat received
    • NewWorkConn — a work connection is opened
    • NewUserConn — 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}; header X-Frp-Reqid for tracing
  • Response format: {reject: bool, reject_reason: string, unchange: bool, content: {...}}; setting reject: true causes 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: httpPlugin is the only concrete implementation (HTTP/HTTPS webhook); the Plugin interface 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 creators map by name at program init
  • Registration: plugin.Register(name, CreatorFn) — called in each plugin’s init(), 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:
PluginFileDescription
http2httphttp2http.goHTTP reverse proxy to local HTTP
http2httpshttp2https.goHTTP reverse proxy to local HTTPS
https2httphttps2http.goTLS termination → local HTTP
https2httpshttps2https.goTLS termination → local HTTPS
http_proxyhttp_proxy.goHTTP CONNECT proxy
socks5socks5.goSOCKS5 proxy server
static_filestatic_file.goServe a local directory over HTTP (with optional Basic Auth)
tls2rawtls2raw.goTLS termination to plaintext TCP
unix_domain_socketunix_domain_socket.goForward to Unix socket
virtual_netvirtual_net.goForward to the in-process WireGuard vnet
  • Extension point: Third-party code can call plugin.Register before client.NewService to 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/vnet for 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/stop CLI 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#

  1. 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.

  2. 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-Reqid header enables distributed tracing across the frps→plugin call.

  3. 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.

  4. The store API (/api/store/*) enables runtime-dynamic proxy management. When a file-backed store is 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.

  5. pkg/virtual is the undocumented but architecturally significant embed path. By injecting a ConnectorCreator that uses net.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.