Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

frp (Fast Reverse Proxy) — Administrator & Developer Guide

Generated May 2026 from fatedier/frp at e8dfd6e

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

frp is a high-performance reverse proxy built for exposing local servers behind NAT or firewalls to the public internet. It ships two binaries — frps (the server, runs on a public host) and frpc (the client, runs behind NAT) — and supports TCP, UDP, HTTP, HTTPS, STCP, XTCP, SUDP, and TCPMux proxy types. Written in Go with a TOML-first config model. Licensed Apache 2.0.

Important

TOML is the preferred configuration format as of current versions. The legacy INI format is deprecated and will be removed in a future release. Migrate any INI configs to TOML immediately.


1. Architecture Overview

1.1 High-Level Design

frp operates on a persistent control channel model. The client (frpc) establishes a long-lived TCP (or KCP/QUIC/WebSocket) connection to the server (frps). Over this control channel, both sides exchange structured protocol messages to coordinate proxy creation, work connection dispatch, and heartbeating. No inbound connectivity is required on the client side — all initiation flows from server to client over the pre-established channel.

┌──────────────────────────────────────────────────────────────────────┐
│  Public Internet                                                      │
│                                                                       │
│   External User ──→ frps (public IP) ──→ frpc ──→ Local Service     │
│                         │                                             │
│                         └─ Dashboard (web UI + API)                  │
└──────────────────────────────────────────────────────────────────────┘

1.2 Binary Entrypoints

Binary Entrypoint Description
frps cmd/frps/main.go Server process. Runs on the public-facing host. Binds ports, manages client sessions, proxies inbound traffic to clients. CLI via Cobra: flags -c (config path), -v (version), --strict_config, --allow-unsafe.
frpc cmd/frpc/main.go → cmd/frpc/sub/ Client process. Runs behind NAT. Connects outbound to frps. Sub-commands live in cmd/frpc/sub/ (verify, nathole discover, nathole testing, etc.).

1.3 Server-Side Component Map

Component Source Role
server/service.go Service struct Root server service. Initializes all sub-systems, starts listeners, manages lifecycle.
server/control.go Control struct Per-client session manager. Handles message dispatch, proxy registration, heartbeat tracking, work connection pool management. One Control per connected frpc.
server/proxy/ Proxy type implementations TCP, UDP, HTTP, HTTPS, STCP, XTCP, SUDP, TCPMux proxy types. Each type implements Proxy interface with Run() and Close().
server/group/ Group managers Load balancing across multiple frpc clients for HTTP, HTTPS, TCP, and TCPMux proxy types.
server/registry/ ClientRegistry Tracks all connected clients. Key–value store for client lookup.
server/visitor/ Visitor manager Manages visitor-side connections for STCP/XTCP peer-to-peer tunnels.
server/http/controller.go Dashboard API REST controller backing the web dashboard. Exposes proxy stats, traffic metrics, client list.
server/ports/ports.go Port manager Allocates and releases ports for TCP/UDP proxies. Enforces allowPorts policy.

1.4 Client-Side Component Map

Component Source Role
client/service.go Service struct Root client service. Reads config, manages lifecycle, spawns Control.
client/control.go Control struct Manages proxy managers and visitor managers. Processes server messages. Drives the proxy lifecycle.
client/connector.go Connector Handles the physical connection to frps. Supports TCP, KCP, QUIC, WebSocket, WSS transports with optional TLS. Reconnects on failure.
client/proxy/ Proxy managers Client-side proxy logic. Receives work connection requests from server, connects to local service, bridges traffic.
client/visitor/ Visitor implementations STCP, SUDP, XTCP visitors. XTCP uses KCP or QUIC tunnel sessions for hole-punched traffic.
client/http/controller.go Admin API REST controller for frpc admin interface. Config reload, status, proxy/visitor CRUD.
client/config_manager.go Config manager Loads, validates, and hot-manages proxy/visitor configs. Implements Store API.

1.5 Shared Packages

Package Source Role
pkg/config/ Config loading TOML/YAML/JSON/INI parsing, template rendering, flag registration, config source abstraction.
pkg/config/v1/ Config structs ServerConfig, ClientCommonConfig, all proxy and visitor config types, plugin configs.
pkg/config/v1/validation/ Validators Validates server and client configs; returns structured errors.
pkg/config/source/ Source abstraction ConfigSource, StoreSource, Aggregator — allows composing config from file + dynamic store.
pkg/auth/ Authentication Token auth and OIDC auth (with token caching and retry).
pkg/transport/ Transport layer TLS config builder, MessageTransporter (framed message R/W over any io.ReadWriteCloser).
pkg/msg/ Protocol messages All wire-format message types. Uses JSON encoding.
pkg/nathole/ NAT traversal STUN-based NAT type discovery, NAT classification, hole-punch coordination, analysis data.
pkg/ssh/ SSH gateway SSH tunnel gateway for frps; allows ssh -R style tunneling through frp.
pkg/vnet/ Virtual network Experimental TUN-based virtual network using WireGuard/netlink. Feature-gated.
pkg/plugin/client/ Client plugins 10 built-in client-side plugins (http_proxy, socks5, static_file, unix_domain_socket, etc.).
pkg/plugin/server/ Server plugins HTTP plugin system. frps POSTs operation events to external HTTP endpoints.
pkg/metrics/ Metrics In-memory counters, Prometheus exporter, aggregate metrics.
pkg/virtual/ Virtual client Used by SSH tunnel gateway to inject virtual frpc connections into the server.

1.6 Data Flow — Connection Lifecycle

1.  frpc starts → Connector dials frps (TCP/KCP/QUIC/WS/WSS, optional TLS)
2.  frpc sends Login{version, hostname, os, arch, user, auth_token, pool_count, client_id}
3.  frps validates auth → registers Control in ControlManager + ClientRegistry
4.  frps sends LoginResp{run_id, server_udp_port}
5.  frpc sends NewProxy{proxy_name, proxy_type, ...config} for each proxy
6.  frps validates proxy config → creates proxy instance → binds port/vhost
7.  frps sends NewProxyResp{proxy_name, remote_addr, error}
8.  External user connects to frps on proxy port/vhost
9.  frps sends ReqWorkConn to frpc
10. frpc dials frps work conn port → sends StartWorkConn{proxy_name}
11. frps bridges: external conn ↔ work conn
12. frpc bridges: work conn ↔ local service
13. frpc sends Ping (heartbeat) → frps responds Pong
14. On disconnect: frps closes proxies, removes Control

1.7 Web Frontend

Frontend Source Stack
frps dashboard web/frps/ Vue 3 + Element Plus + Vite
frpc admin UI web/frpc/ Vue 3 + Element Plus + Pinia + Vite

2. System Requirements

2.1 Runtime Requirements

Requirement frps frpc
OS Linux, macOS, Windows, FreeBSD Linux, macOS, Windows, FreeBSD
Architecture amd64, arm64, arm, 386, mips amd64, arm64, arm, 386, mips
Network Public IP or port-forwarded host Outbound TCP to frps port (default 7000)
Firewall Open bindPort (7000), vhostHTTPPort (80), vhostHTTPSPort (443), dashboard port No inbound ports required
Memory ~30 MB base + ~2 MB/connected client ~20 MB base
Disk Minimal (log files only) Minimal

2.2 Build Requirements

Tool Version Purpose
Go 1.25+ Build both binaries
Node.js 18+ Build web dashboards
make Any Makefile targets
# Build all
make build

# Build server only
go build -o frps ./cmd/frps

# Build client only
go build -o frpc ./cmd/frpc

# Build web assets (required for embedded dashboard)
cd web/frps && npm install && npm run build
cd web/frpc && npm install && npm run build

2.3 Docker Images

Pre-built Docker images are available. Custom Dockerfiles live in dockerfiles/:

File Target
dockerfiles/Dockerfile-for-frpc frpc container image
dockerfiles/Dockerfile-for-frps frps container image
# Build frps image
docker build -f dockerfiles/Dockerfile-for-frps -t frps:local .

# Build frpc image
docker build -f dockerfiles/Dockerfile-for-frpc -t frpc:local .

Note

GitHub issue #5288 documents Docker build failures on certain base images. Ensure the Go toolchain version matches what is specified in go.mod.


3. Configuration Reference

3.1 Config File Format

frp supports TOML (preferred), YAML, JSON, and INI (deprecated). The config format is auto-detected from the file extension. Template rendering is supported: {{ .Envs.VARIABLE }} expands environment variables in config values.

# Start with config file
frps -c /etc/frp/frps.toml
frpc -c /etc/frp/frpc.toml

# Strict config (fail on unknown keys)
frps -c frps.toml --strict_config

# Allow unsafe config options
frps -c frps.toml --allow-unsafe

3.2 Server Configuration (frps.toml) — Complete Reference

All fields from ServerConfig struct in pkg/config/v1/server.go:

Bind Settings

bindAddr = "0.0.0.0"          # IP to bind control port (default: "0.0.0.0")
bindPort = 7000                # Control plane TCP port (default: 7000)
kcpBindPort = 7000             # KCP UDP port (0 = disabled)
quicBindPort = 7002            # QUIC UDP port (0 = disabled)
proxyBindAddr = "0.0.0.0"     # IP for proxy listener binds (default: same as bindAddr)

Virtual Host Settings

vhostHTTPPort = 80             # HTTP vhost reverse proxy port (0 = disabled)
vhostHTTPSPort = 443           # HTTPS vhost reverse proxy port (0 = disabled)
vhostHTTPTimeout = 60          # Backend HTTP response timeout in seconds (default: 60)
tcpmuxHTTPConnectPort = 0      # TCPMux HTTP CONNECT port (0 = disabled)
tcpmuxPassthrough = false      # Pass CONNECT request to backend unchanged
subDomainHost = ""             # Base domain for subdomain-based HTTP proxies (e.g. "example.com")

Web Dashboard / Admin API

[webServer]
addr = "127.0.0.1"            # Dashboard bind address
port = 7500                    # Dashboard port
user = "admin"                 # HTTP basic auth username
password = "secret"            # HTTP basic auth password
assetsDir = ""                 # Override embedded assets with filesystem path
pprofEnable = false            # Enable pprof at /debug/pprof/
[webServer.tls]
certFile = ""
keyFile = ""

Metrics

enablePrometheus = false       # Expose /metrics endpoint (Prometheus format)

Logging

[log]
to = "console"                 # Log destination: "console", or a file path
level = "info"                 # Log level: trace, debug, info, warn, error
maxDays = 3                    # Days to keep log files (0 = no rotation)
disablePrintColor = false      # Disable ANSI color in console output

Transport

[transport]
maxPoolCount = 5               # Max pre-allocated work connections per client (default: 5)
tcpMux = true                  # Enable yamux TCP multiplexing (default: true)
tcpMuxKeepaliveInterval = 60   # Yamux keepalive interval seconds
tcpKeepalive = 7200            # TCP keepalive interval seconds (-1 = disable)
heartbeatTimeout = 90          # Client heartbeat timeout seconds (default: 90, -1 = disable)

[transport.tls]
force = false                  # Require TLS for all client connections
certFile = ""                  # TLS certificate file path
keyFile = ""                   # TLS private key file path
trustedCaFile = ""             # CA file for mutual TLS client verification

[transport.quic]
keepalivePeriod = 10           # QUIC keepalive period seconds
maxIdleTimeout = 30            # QUIC max idle timeout seconds
maxIncomingStreams = 100000     # QUIC max concurrent streams

Warning

GitHub issue #5131 documents that transport.tls.force = false may not behave as expected in all configurations. Test TLS behavior explicitly in your environment before production deployment.

Authentication

[auth]
method = "token"               # Auth method: "token" or "oidc"
additionalScopes = []          # Additional auth scopes: ["NewProxy", "NewWorkConn"]

# Token auth
token = "your-secret-token"

# OIDC auth
[auth.oidc]
issuer = ""                    # OIDC issuer URL
audience = ""                  # Expected audience claim
skipExpiryCheck = false
skipIssuerCheck = false

Access Control

detailedErrorsToClient = true  # Send detailed error messages to frpc (default: true)
userConnTimeout = 10           # Seconds to wait for user connection to work conn (default: 10)
maxPortsPerClient = 0          # Max ports per client (0 = unlimited)
custom404Page = ""             # Path to custom 404 HTML page for vhost proxies

# Port allowlist (restricts TCP/UDP proxy ports)
[[allowPorts]]
start = 2000
end = 3000

[[allowPorts]]
start = 4000
end = 4100

[[allowPorts]]
single = 7000                  # Single port allowance

UDP

udpPacketSize = 1500           # Max UDP packet size bytes (default: 1500; match frpc setting)

NAT Hole Analysis

natholeAnalysisDataReserveHours = 168   # Hours to retain NAT analysis data (default: 168 = 7 days)

SSH Tunnel Gateway

[sshTunnelGateway]
bindPort = 2222                          # SSH gateway listen port (0 = disabled)
privateKeyFile = ""                      # Server private key path
autoGenPrivateKeyPath = ""               # Auto-generate key and save to this path
authorizedKeysFile = ""                  # Authorized public keys file (OpenSSH format)

HTTP Plugins

[[httpPlugins]]
name = "my-plugin"
addr = "127.0.0.1:9000"        # Plugin HTTP server address
path = "/handler"              # Plugin endpoint path
ops = ["Login", "NewProxy"]   # Operations to forward to plugin
tlsVerify = false              # Verify plugin TLS certificate

3.3 Client Configuration (frpc.toml) — Complete Reference

All fields from ClientCommonConfig in pkg/config/v1/client.go:

Server Connection

serverAddr = "0.0.0.0"        # frps host/IP (default: "0.0.0.0")
serverPort = 7000             # frps control port (default: 7000)
natHoleStunServer = "stun.easyvoip.com:3478"  # STUN server for XTCP
dnsServer = ""                # Override DNS server (e.g. "8.8.8.8")

Identity

user = ""                     # User prefix for proxy names (namespacing)
clientID = ""                 # Client ID (auto-generated if empty)

Auth (same structure as server)

[auth]
method = "token"
token = "your-secret-token"

Behavior

loginFailExit = true          # Exit if login fails (default: true); false = keep retrying
start = []                    # Only start these proxy names (empty = all)

Logging (same structure as server)

[log]
to = "console"
level = "info"
maxDays = 3
disablePrintColor = false

Admin Web Interface

[webServer]
addr = "127.0.0.1"
port = 7400
user = "admin"
password = "secret"
pprofEnable = false

Transport

[transport]
protocol = "tcp"              # "tcp", "kcp", "quic", "websocket", "wss"
dialServerTimeout = 10        # Seconds to wait for initial connection
dialServerKeepalive = 7200    # TCP keepalive seconds
connectServerLocalIP = ""     # Bind local IP for connection to server
proxyURL = ""                 # HTTP/SOCKS5 proxy for connecting to server
poolCount = 1                 # Pre-allocated work connections
heartbeatInterval = 30        # Heartbeat send interval seconds
heartbeatTimeout = 90         # Heartbeat timeout seconds
tcpMux = true
tcpMuxKeepaliveInterval = 60
tls.enable = false
tls.disableCustomTLSFirstByte = false
tls.certFile = ""
tls.keyFile = ""
tls.trustedCaFile = ""
tls.serverName = ""

[transport.quic]
keepalivePeriod = 10
maxIdleTimeout = 30
maxIncomingStreams = 100000

Virtual Network (Experimental)

[virtualNet]
address = ""                  # TUN interface CIDR (e.g. "10.0.0.1/24")

Feature Gates

[featureGates]
VirtualNet = false            # Enable experimental vnet (TUN device)

Other

udpPacketSize = 1500
metadatas = {key = "value"}  # Arbitrary metadata sent to server/plugins
includes = ["./proxies/*.toml"]  # Include additional config files

Store (Dynamic Config)

[store]
enabled = false               # Enable dynamic proxy/visitor store
dataDir = ""                  # Directory for persisted store data

3.4 Proxy Type Configuration

TCP Proxy

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000             # Port on frps to expose
[proxies.transport]
bandwidthLimit = "1MB"        # e.g. "1MB", "1KB"
bandwidthLimitMode = "client" # "client" or "server"
useEncryption = false
useCompression = false

UDP Proxy

[[proxies]]
name = "dns"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 6053

HTTP Proxy

[[proxies]]
name = "web"
type = "http"
localIP = "127.0.0.1"
localPort = 8080
customDomains = ["www.example.com"]
subdomain = "web"             # Requires subDomainHost on server
locations = ["/"]
httpUser = ""                 # HTTP basic auth
httpPassword = ""
hostHeaderRewrite = ""        # Rewrite Host header
requestHeaders.set = {X-From-Where = "frp"}
responseHeaders.set = {}
routeByHTTPUser = ""

Note

GitHub issue #5097: HTTP proxies require vhostHTTPPort to be configured on frps. If not set, you will receive: type [http] not supported when vhost http port is not set.

HTTPS Proxy

[[proxies]]
name = "web-secure"
type = "https"
localIP = "127.0.0.1"
localPort = 8443
customDomains = ["www.example.com"]
subdomain = "web"
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8080"

STCP (Secret TCP — peer-to-peer via frps relay)

# Proxy side (frpc that has the service)
[[proxies]]
name = "secret-ssh"
type = "stcp"
secretKey = "shared-secret"
localIP = "127.0.0.1"
localPort = 22

# Visitor side (frpc on the accessing machine)
[[visitors]]
name = "secret-ssh-visitor"
type = "stcp"
serverName = "secret-ssh"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6000

XTCP (Peer-to-peer with NAT hole punching)

# Proxy side
[[proxies]]
name = "p2p-ssh"
type = "xtcp"
secretKey = "shared-secret"
localIP = "127.0.0.1"
localPort = 22

# Visitor side
[[visitors]]
name = "p2p-ssh-visitor"
type = "xtcp"
serverName = "p2p-ssh"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6000
fallbackTo = "secret-ssh-visitor"   # Fallback if hole punch fails
fallbackTimeoutMs = 200

Note

GitHub issue #5281: VirtualNet (XTCP) has lower throughput than standard XTCP. Use standard XTCP for performance-sensitive tunnels. The VirtualNet feature gate enables TUN-based routing but is experimental.

SUDP (Secret UDP)

[[proxies]]
name = "secret-udp"
type = "sudp"
secretKey = "shared-secret"
localIP = "127.0.0.1"
localPort = 9999

[[visitors]]
name = "secret-udp-visitor"
type = "sudp"
serverName = "secret-udp"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 9999

TCPMux (Multiple TCP streams on one connection via HTTP CONNECT)

[[proxies]]
name = "tcpmux"
type = "tcpmux"
multiplexer = "httpconnect"
localIP = "127.0.0.1"
localPort = 22
customDomains = ["tunnel.example.com"]
httpUser = ""
httpPassword = ""
routeByHTTPUser = ""

3.5 Client Plugin Configuration

Client-side plugins intercept local traffic before forwarding. 10 built-in plugins:

Plugin Type Key Purpose
HTTP Proxy http_proxy Local HTTP proxy server
SOCKS5 socks5 Local SOCKS5 proxy server
Static File static_file Serve local directory over HTTP
Unix Domain Socket unix_domain_socket Forward to local UDS
HTTP2HTTPS http2https Convert HTTP to HTTPS for local backend
HTTPS2HTTP https2http Terminate TLS before forwarding
HTTPS2HTTPS https2https TLS passthrough/conversion
HTTP Header Modifier http_header Modify request/response headers
Tls2Raw tls2raw Strip TLS before local backend
[[proxies]]
name = "socks5-tunnel"
type = "tcp"
remotePort = 6005
[proxies.plugin]
type = "socks5"
username = "user"
password = "pass"

4. Deployment & Scaling

4.1 Bare Metal / VM Deployment

# Install frps as systemd service
sudo cp frps /usr/local/bin/
sudo mkdir -p /etc/frp
sudo cp frps.toml /etc/frp/

cat > /etc/systemd/system/frps.service << 'EOF'
[Unit]
Description=frp Server
After=network.target

[Service]
Type=simple
User=nobody
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now frps
# Install frpc as systemd service
sudo cp frpc /usr/local/bin/
sudo mkdir -p /etc/frp
sudo cp frpc.toml /etc/frp/

cat > /etc/systemd/system/frpc.service << 'EOF'
[Unit]
Description=frp Client
After=network.target

[Service]
Type=simple
User=nobody
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now frpc

4.2 Docker Compose Deployment

# docker-compose.yml for frps
version: "3"
services:
  frps:
    image: fatedier/frps:latest
    volumes:
      - ./frps.toml:/etc/frp/frps.toml
    ports:
      - "7000:7000"     # Control port
      - "7500:7500"     # Dashboard
      - "80:80"         # HTTP vhost
      - "443:443"       # HTTPS vhost
      - "2000-3000:2000-3000"  # Proxy port range
    restart: unless-stopped
    command: -c /etc/frp/frps.toml
# docker-compose.yml for frpc
version: "3"
services:
  frpc:
    image: fatedier/frpc:latest
    network_mode: host          # Use host network to reach local services
    volumes:
      - ./frpc.toml:/etc/frp/frpc.toml
    restart: unless-stopped
    command: -c /etc/frp/frpc.toml

4.3 Kubernetes Deployment

Deploy frps as a LoadBalancer or NodePort service. frpc runs as a DaemonSet or Deployment in the cluster with config via ConfigMap.

apiVersion: v1
kind: ConfigMap
metadata:
  name: frps-config
data:
  frps.toml: |
    bindPort = 7000
    [auth]
    token = "changeme"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frps
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frps
  template:
    spec:
      containers:
      - name: frps
        image: fatedier/frps:latest
        args: ["-c", "/etc/frp/frps.toml"]
        volumeMounts:
        - name: config
          mountPath: /etc/frp
      volumes:
      - name: config
        configMap:
          name: frps-config

Note

GitHub issue #4800 documents that frps clustering is a feature request and is not natively supported. For high-availability, use a single frps behind a TCP load balancer (HAProxy/nginx stream), or partition clients across multiple frps instances with different ports.

4.4 Scaling Considerations

Concern Guidance
Concurrent clients Each Control goroutine is lightweight. Tested at thousands of concurrent clients. Monitor goroutine count via pprof.
Port ranges Use allowPorts to pre-define the port range. Keep ranges narrow and document them.
Work connection pool transport.maxPoolCount (server) and transport.poolCount (client) control pre-warmed connections. Increase for high-traffic proxies.
KCP vs TCP KCP (kcpBindPort) is UDP-based and better for lossy networks. Has higher CPU overhead than TCP.
QUIC quicBindPort provides multiplexing without head-of-line blocking. Recommended for high-concurrency scenarios over lossy links.
Bandwidth throttling Per-proxy bandwidthLimit is enforced on the client side (bandwidthLimitMode = "client") or server side ("server"). Server-side requires --allow-unsafe flag on frps.

5. Operations

5.1 frps API Endpoints

All dashboard API endpoints require HTTP Basic Auth (configured via webServer.user/webServer.password).

Caution

GitHub issue #5290 notes that /healthz endpoints are accessible without dashboard authentication. Do not expose the dashboard port publicly without additional network-level controls.

Method Path Auth Description
GET /healthz None Health check. Returns 200 if server is running.
GET /metrics None Prometheus metrics (requires enablePrometheus = true).
GET /api/serverinfo Basic Server version, Go version, arch, OS, bind info.
GET /api/proxy/{type} Basic List all proxies of type (tcp, udp, http, https, stcp, xtcp, sudp, tcpmux).
GET /api/proxy/{type}/{name} Basic Get proxy detail by type and name.
GET /api/proxies/{name} Basic Get proxy detail by name (type-agnostic).
GET /api/traffic/{name} Basic Traffic stats for a proxy (bytes in/out).
GET /api/clients Basic List all connected clients with metadata.
GET /api/clients/{key} Basic Get specific client by run ID.
DELETE /api/proxies Basic Force-close all proxies (use for cleanup).

5.2 frpc Admin API Endpoints

frpc admin API requires Basic Auth configured via webServer.

Method Path Auth Description
GET /healthz None Client health check.
GET /api/reload Basic Reload config file and apply changes.
POST /api/stop Basic Stop frpc process.
GET /api/status Basic Status of all proxies and visitors.
GET /api/config Basic Get current config.
PUT /api/config Basic Replace config (triggers reload).
GET /api/proxy/{name}/config Basic Get individual proxy config.
GET /api/visitor/{name}/config Basic Get individual visitor config.
GET /api/store/proxies Basic List dynamically stored proxies.
POST /api/store/proxies Basic Add a proxy to store.
GET /api/store/proxies/{name} Basic Get stored proxy config.
PUT /api/store/proxies/{name} Basic Update stored proxy config.
DELETE /api/store/proxies/{name} Basic Remove stored proxy.
GET /api/store/visitors Basic List dynamically stored visitors.
POST /api/store/visitors Basic Add visitor to store.
GET /api/store/visitors/{name} Basic Get stored visitor.
PUT /api/store/visitors/{name} Basic Update stored visitor.
DELETE /api/store/visitors/{name} Basic Remove stored visitor.
GET /debug/pprof/ Basic pprof index (requires pprofEnable = true).
GET /debug/pprof/cmdline Basic Command line.
GET /debug/pprof/profile Basic CPU profile (30s).
GET /debug/pprof/symbol Basic Symbol lookup.
GET /debug/pprof/trace Basic Execution trace.

5.3 Hot Reload

Config changes can be applied without restarting frpc:

# Via API
curl -u admin:secret http://127.0.0.1:7400/api/reload

# Via frpc subcommand
frpc reload -c /etc/frp/frpc.toml

Note

GitHub issue #2946 (hot-reloading TLS certificates): TLS certificate hot-reload is a known limitation. Changing TLS certificates requires a full frps restart.

5.4 Monitoring

Prometheus metrics (requires enablePrometheus = true on frps):

frp_server_client_counts             # Current connected client count
frp_server_proxy_counts{type}        # Proxy count by type
frp_server_connection_counts         # Current connection count
frp_server_traffic_in_bytes_total    # Total inbound traffic bytes
frp_server_traffic_out_bytes_total   # Total outbound traffic bytes

Note

GitHub issue #5282: Traffic metrics may not update in real-time under high-frequency connection scenarios. Use Prometheus scrape intervals of 15s or longer.

pprof profiling (requires pprofEnable = true):

# CPU profile (30 seconds)
curl http://127.0.0.1:7500/debug/pprof/profile?seconds=30 > cpu.prof
go tool pprof cpu.prof

# Goroutine dump
curl http://127.0.0.1:7500/debug/pprof/goroutine?debug=2

5.5 Log Analysis

Key log patterns to monitor:

Pattern Meaning
login to server success frpc connected successfully
proxy added: [name] Proxy successfully registered
heartbeat timeout Client lost connectivity; will reconnect
port already in use Requested remote port is occupied
port not allowed Port outside allowPorts range
exceed the max_ports_per_client Client hit port quota
authentication failed Token/OIDC mismatch
EOF on work connection Normal connection close
i/o timeout Network timeout between frpc and local service

6. Performance

6.1 Transport Selection

Transport Protocol Use Case Tradeoff
TCP TCP Default, all environments Reliable; head-of-line blocking
KCP UDP Lossy/high-latency networks Higher CPU; better throughput on packet loss
QUIC UDP Modern; multiplexed Best for high-concurrency; requires UDP passthrough
WebSocket TCP Environments that block non-HTTP HTTP overhead
WSS TLS/TCP WebSocket over TLS HTTP overhead + TLS overhead

6.2 Connection Pool Tuning

# frps
[transport]
maxPoolCount = 5        # Increase for high-traffic proxies (e.g. 20-50)

# frpc
[transport]
poolCount = 5           # Should match or exceed server maxPoolCount

Pre-warmed connections eliminate the latency of establishing new connections on each user request. Increase these values for proxies that handle many short-lived connections.

6.3 TCP Multiplexing

tcpMux = true (default) uses yamux to multiplex multiple streams over one TCP connection. This dramatically reduces the number of TCP connections needed for high-concurrency workloads. Disable only if debugging mux-specific issues.

6.4 Bandwidth Limiting

[[proxies]]
name = "limited-web"
type = "tcp"
[proxies.transport]
bandwidthLimit = "10MB"
bandwidthLimitMode = "server"  # Enforced server-side (requires --allow-unsafe on frps)

6.5 UDP Packet Size

# Must match on both frps and frpc
udpPacketSize = 1500    # Increase for jumbo frames (e.g. 9000 on trusted LANs)

Warning

GitHub issue #5289: UDP proxies may not forward large packets reliably if udpPacketSize is smaller than the actual packet MTU. Increase udpPacketSize to 4096 or higher for DNS-over-UDP and similar protocols that may exceed 1500 bytes.


7. Security

7.1 Authentication Methods

Token Authentication (Default)

# frps
[auth]
method = "token"
token = "use-a-long-random-secret-here"

# frpc
[auth]
method = "token"
token = "use-a-long-random-secret-here"

Token is transmitted in the Login message and compared server-side. Use a minimum 32-character random token in production.

OIDC Authentication

# frps
[auth]
method = "oidc"
[auth.oidc]
issuer = "https://accounts.google.com"
audience = "my-frp-service"
skipExpiryCheck = false
skipIssuerCheck = false

# frpc
[auth]
method = "oidc"
# Token source config for acquiring OIDC tokens goes here

OIDC tokens are cached and refreshed automatically in pkg/auth/. Scopes can be extended via additionalScopes to include NewProxy and NewWorkConn operations.

7.2 TLS Encryption

# frps — require TLS from all clients
[transport.tls]
force = true
certFile = "/etc/frp/server.crt"
keyFile = "/etc/frp/server.key"
trustedCaFile = "/etc/frp/ca.crt"   # For mutual TLS

# frpc — connect with TLS
[transport]
protocol = "tcp"
[transport.tls]
enable = true
certFile = "/etc/frp/client.crt"
keyFile = "/etc/frp/client.key"
trustedCaFile = "/etc/frp/ca.crt"
serverName = "frps.example.com"

Important

Without TLS, all traffic between frps and frpc (including auth tokens and proxied data) is transmitted in plaintext. Always enable TLS in production deployments, especially when frps is reachable from the public internet.

7.3 Port Access Control

# Restrict which ports frpc can use
[[allowPorts]]
start = 2000
end = 3000

[[allowPorts]]
single = 443

maxPortsPerClient = 10    # Prevent port exhaustion by a single client

7.4 Per-Proxy HTTP Auth

[[proxies]]
name = "protected-web"
type = "http"
localPort = 8080
customDomains = ["app.example.com"]
httpUser = "admin"
httpPassword = "secret"

frps injects an HTTP Basic Auth challenge before forwarding to the backend. The backend itself does not need to implement auth.

7.5 Dashboard Security

  • Always set webServer.user and webServer.password
  • Bind dashboard to 127.0.0.1 (not 0.0.0.0) unless remote access is required
  • Enable webServer.tls for HTTPS dashboard
  • Consider firewall rules to restrict dashboard port access

Caution

The /healthz endpoint on both frps and frpc dashboards is accessible without authentication (GitHub issue #5290). This exposes service uptime status. If your threat model requires hiding service existence, firewall the dashboard port entirely.

7.6 SSH Tunnel Gateway

The SSH tunnel gateway (sshTunnelGateway) allows standard ssh -R usage to create frp tunnels:

[sshTunnelGateway]
bindPort = 2222
autoGenPrivateKeyPath = "/etc/frp/ssh_host_key"
authorizedKeysFile = "/etc/frp/.authorized_keys"
# Client usage — expose local port 8080 as remote port 8080
ssh -p 2222 -R 8080:localhost:8080 [email removed]

The authorizedKeysFile controls which SSH public keys can create tunnels. The virtual client path goes through pkg/virtual/ and injects an in-process frpc-equivalent into the server.

7.7 Server Plugins for Custom Auth/Authz

[[httpPlugins]]
name = "auth-service"
addr = "http://127.0.0.1:9001"
path = "/frp/handler"
ops = ["Login", "NewProxy", "CloseProxy", "Ping", "NewWorkConn"]
tlsVerify = true

Plugins receive JSON POST requests for each operation. Returning HTTP 200 allows the operation; any other status rejects it. This enables full custom authorization logic (LDAP, database, ACL systems).

Plugin request format:

{
  "version": "0.1.0",
  "op": "Login",
  "content": {
    "version": "0.60.0",
    "hostname": "client-host",
    "os": "linux",
    "arch": "amd64",
    "user": "user1",
    "timestamp": 1234567890,
    "privilege_key": "...",
    "run_id": "...",
    "pool_count": 5,
    "metas": {},
    "client_address": "1.2.3.4:12345"
  }
}

7.8 Network Security Recommendations

Control Recommendation
Firewall Restrict bindPort to trusted source IPs if all clients are known
Reverse proxy Put frps dashboard behind nginx/Traefik with additional auth
Secrets Never commit tokens/TLS keys to version control
Audit Enable file logging and ship to centralized log system
Monitoring Alert on authentication failed spikes (brute force indicator)
detailedErrorsToClient Set to false in production to limit information disclosure

8. Developer Internals

8.1 Protocol Message Layer (pkg/msg/)

All communication between frps and frpc uses framed JSON messages over the control connection. Messages are length-prefixed. The MessageTransporter in pkg/transport/ handles framing.

Message Direction Purpose
Login C→S Initial client handshake. Contains version, auth, client metadata, pool count.
LoginResp S→C Login result. Contains run_id (session ID), server_udp_port, error.
NewProxy C→S Register a new proxy. Contains all proxy config fields.
NewProxyResp S→C Proxy registration result. Contains remote_addr or error.
CloseProxy C→S Unregister a proxy.
NewWorkConn C→S Client opening a new work connection.
ReqWorkConn S→C Server requesting a work connection for a new user connection.
StartWorkConn S→C Server assigning work connection to a proxy/visitor.
NewVisitorConn C→S Visitor connecting to an STCP/XTCP proxy.
NewVisitorConnResp S→C Visitor connection result.
Ping C→S Heartbeat from client. Contains timestamp.
Pong S→C Heartbeat acknowledgment.
UDPPacket both UDP payload envelope for UDP proxy type.
NatHoleVisitor C→S Visitor initiating NAT hole punch.
NatHoleClient C→S Proxy side registering for NAT hole punch.
NatHoleResp S→C NAT hole punch response with peer address info.
NatHoleSid both Session ID for NAT hole punch coordination.
NatHoleReport C→S Client reporting NAT hole punch outcome.

8.2 Server Control Lifecycle (server/control.go)

The Control struct is the core server-side session object, one per connected frpc:

frpc connects
    → newControl() creates Control
    → runReaderWriter() starts message loop
    → handleLogin() validates auth
    → registerControl() adds to ControlManager + ClientRegistry
    → message dispatch loop:
        NewProxy      → handleNewProxy()     → server/proxy/
        CloseProxy    → handleCloseProxy()
        Ping          → respond with Pong, update lastPing
        NewWorkConn   → handleNewWorkConn()  → add to work conn pool
        NewVisitorConn → handleNewVisitorConn() → server/visitor/
    → heartbeat monitor goroutine watches lastPing vs heartbeatTimeout
frpc disconnects / timeout
    → control.Close() → closes all proxies, removes from registry

8.3 Server Proxy Types (server/proxy/)

Each proxy type implements the Proxy interface. The factory dispatches by proxy_type:

Type Implementation Bind behavior
tcp TCPProxy Acquires port from port manager, TCP listener
udp UDPProxy Acquires port from port manager, UDP listener
http HTTPProxy Registers vhost route with HTTP reverse proxy controller
https HTTPSProxy Registers vhost route with HTTPS reverse proxy controller (SNI-based)
stcp STCPProxy Registers in visitor manager keyed by secret_key
xtcp XTCPProxy Registers NAT hole listener; coordinates via nathole package
sudp SUDPProxy UDP variant of STCP
tcpmux TCPMuxProxy Registers with TCPMux HTTP CONNECT handler

8.4 Client Connector (client/connector.go)

The connector manages the physical transport connection to frps and reconnection logic:

// Transport selection logic (simplified)
switch config.Transport.Protocol {
case "kcp":
    conn = dialKCP(serverAddr, kcpConfig)
case "quic":
    conn = dialQUIC(serverAddr, quicConfig)
case "websocket":
    conn = dialWebSocket(serverAddr, false)
case "wss":
    conn = dialWebSocket(serverAddr, true)  // TLS
default: // "tcp"
    conn = dialTCP(serverAddr)
}
if config.Transport.TLS.Enable {
    conn = tlsWrap(conn, tlsConfig)
}

8.5 NAT Hole Punching (pkg/nathole/)

The XTCP NAT traversal system uses STUN for discovery and a coordination protocol through frps:

  1. NAT Classification: Each side performs STUN queries to classify NAT type (full cone, restricted cone, symmetric). Classification stored in nathole/analysis.go.
  2. Coordination: frps acts as signaling server. Visitor sends NatHoleVisitor; client registers with NatHoleClient. Server sends NatHoleResp to both with peer address hints.
  3. Hole Punch: Both sides simultaneously send UDP packets to each other's predicted external address. Pion STUN library handles STUN binding requests/responses.
  4. Session: After hole punch, XTCP uses KCP or QUIC over UDP for the actual tunnel (client/visitor/xtcp.go).
  5. Fallback: If hole punch fails within fallbackTimeoutMs, visitor falls back to the fallbackTo proxy name (relay via frps).
  6. Analysis data: Outcomes stored for natholeAnalysisDataReserveHours hours to improve future punch strategies.

8.6 Virtual Network (pkg/vnet/)

Experimental feature gated behind featureGates.VirtualNet = true:

  • Creates a TUN network interface using github.com/songgao/water
  • Assigns CIDR address from virtualNet.address config
  • Uses github.com/vishvananda/netlink for Linux routing table management
  • WireGuard (golang.zx2c4.com/wireguard) for encrypted peer-to-peer tunneling
  • Traffic through XTCP tunnel, enabling full layer-3 virtual network
  • Linux only — TUN/netlink are not supported on Windows or macOS

8.7 Config Loading Pipeline (pkg/config/)

1. Raw bytes from file / CLI flags / environment
2. Format detection by file extension (.toml/.yaml/.json/.ini)
3. Template rendering: {{ .Envs.KEY }} expanded
4. Unmarshal into v1 config structs
5. Default value population
6. Validation via pkg/config/v1/validation/
7. Config source aggregation (file + store merge via Aggregator)

The StoreSource in pkg/config/source/ provides dynamic proxy/visitor config that can be modified at runtime via the store API without touching the config file.

8.8 Metrics Architecture (pkg/metrics/)

Three metric backends:

Backend Type Usage
In-memory ServerMetrics interface Default; powers dashboard API /api/traffic/
Prometheus prometheus.Metrics Enabled via enablePrometheus = true; scrape at /metrics
Aggregate AggregateMetrics Wraps both; writes to both simultaneously

Counters tracked per proxy: connections (current), traffic in bytes, traffic out bytes. Prometheus gauge and counter families follow standard Prometheus naming (frp_server_*).

8.9 Authentication Internals (pkg/auth/)

Verifier (server-side):
  - TokenAuthVerifier: constant-time compare of token field
  - OIDCAuthVerifier: go-oidc provider, token parsing, audience/issuer checks
    - Token cache: avoids redundant OIDC provider fetches
    - Retry logic for transient OIDC provider failures

Setter (client-side):
  - TokenAuthSetter: injects token into Login/Ping messages
  - OIDCAuthSetter: acquires token from configured token source, injects
    - Caches token, refreshes before expiry

8.10 E2E Test Framework (test/e2e/)

Tests use Ginkgo v2 + Gomega. Test coverage areas:

Test Suite Location What it tests
Basic proxying test/e2e/basic/ TCP, UDP, HTTP, HTTPS proxy types end-to-end
Plugins test/e2e/plugin/ All 10 client plugins
Features test/e2e/features/ Bandwidth limits, heartbeat, load balancing groups, real IP headers, chaos, XTCP, SSH tunnel

Test helpers spin up real frps/frpc binaries, mock HTTP/stream/OIDC servers, and verify against live connections. Run with:

# Run all e2e tests
go test ./test/e2e/... -v

# Run specific suite
go test ./test/e2e/basic/... -v -ginkgo.focus="TCP"

8.11 Key Interfaces

// Proxy interface (server/proxy/)
type Proxy interface {
    Run() (remoteAddr string, err error)
    Close()
    GetLoginMsg() *msg.Login
    GetWorkConnFromPool(ctx context.Context) (net.Conn, error)
    RegisterWorkConn(conn net.Conn)
    GetUsedPortsNum() int
    GetStatus() *ProxyStatus
}

// Auth interfaces (pkg/auth/)
type Verifier interface {
    VerifyLogin(m *msg.Login) error
    VerifyPing(m *msg.Ping) error
    VerifyNewWorkConn(m *msg.NewWorkConn) error
}

type Setter interface {
    SetLogin(m *msg.Login) error
    SetPing(m *msg.Ping) error
    SetNewWorkConn(m *msg.NewWorkConn) error
}

// Config source (pkg/config/source/)
type ConfigSource interface {
    GetProxies() ([]*v1.ProxyConfigurer, error)
    GetVisitors() ([]*v1.VisitorConfigurer, error)
}

8.12 Dependency Inventory

Dependency Purpose
github.com/spf13/cobra CLI framework for both binaries
github.com/gorilla/mux HTTP routing for dashboard/admin APIs
github.com/gorilla/websocket WebSocket transport implementation
github.com/fatedier/yamux TCP stream multiplexing (fork of hashicorp/yamux)
github.com/quic-go/quic-go QUIC transport
github.com/xtaci/kcp-go/v5 KCP UDP transport
github.com/pion/stun/v3 STUN protocol for NAT traversal
github.com/prometheus/client_golang Prometheus metrics
github.com/coreos/go-oidc/v3 OIDC authentication
github.com/pelletier/go-toml/v2 TOML config parsing
github.com/songgao/water TUN device (vnet)
github.com/vishvananda/netlink Linux network routing (vnet)
golang.org/x/crypto SSH protocol
golang.zx2c4.com/wireguard WireGuard encryption (vnet)
github.com/pion/ice/v2 ICE framework for NAT traversal
k8s.io/apimachinery Kubernetes API types (minimal usage in deps)
k8s.io/client-go Kubernetes client (minimal usage in deps)

9. Integration Points

9.1 Prometheus Integration

# prometheus.yml scrape config
scrape_configs:
  - job_name: 'frps'
    static_configs:
      - targets: ['frps-host:7500']
    basic_auth:
      username: admin
      password: secret
    metrics_path: /metrics

9.2 Grafana Dashboard

Key metrics to visualize:

# Active clients
frp_server_client_counts

# Proxy count by type
sum by (type) (frp_server_proxy_counts)

# Inbound traffic rate (bytes/s)
rate(frp_server_traffic_in_bytes_total[1m])

# Connection rate
rate(frp_server_connection_counts[1m])

9.3 Reverse Proxy Integration (nginx)

Put the frps dashboard behind nginx for additional auth and TLS:

server {
    listen 443 ssl;
    server_name frps-dashboard.example.com;

    ssl_certificate /etc/ssl/certs/frps.crt;
    ssl_certificate_key /etc/ssl/private/frps.key;

    location / {
        proxy_pass http://127.0.0.1:7500;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

9.4 SSH Tunnel Integration (OpenSSH compatible)

# Standard SSH reverse tunnel through frp SSH gateway
ssh -p 2222 \
    -o StrictHostKeyChecking=no \
    -R 8080:localhost:8080 \
    [email removed]

# The "user" field before @ is the frp user namespace (any value accepted)
# The -R flag creates a TCP proxy on frps port 8080

9.5 Custom Server Plugin Example

# Minimal Python plugin server
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/frp/handler', methods=['POST'])
def handler():
    data = request.json
    op = data.get('op')
    content = data.get('content', {})

    if op == 'Login':
        user = content.get('user', '')
        if not is_authorized(user):
            return jsonify({'reject': True, 'reject_reason': 'Unauthorized'}), 200

    if op == 'NewProxy':
        proxy_name = content.get('proxy_name', '')
        # ACL check for proxy creation
        pass

    # Allow the operation
    return jsonify({}), 200

if __name__ == '__main__':
    app.run(port=9001)

9.6 Config Template Variables

# frpc.toml using environment variable substitution
serverAddr = "{{ .Envs.FRP_SERVER_ADDR }}"
serverPort = {{ .Envs.FRP_SERVER_PORT }}

[auth]
token = "{{ .Envs.FRP_AUTH_TOKEN }}"

[[proxies]]
name = "{{ .Envs.HOSTNAME }}-ssh"
type = "tcp"
localPort = 22
remotePort = {{ .Envs.REMOTE_PORT }}

Run with environment variables:

FRP_SERVER_ADDR=frps.example.com \
FRP_AUTH_TOKEN=mysecret \
HOSTNAME=$(hostname) \
REMOTE_PORT=6022 \
frpc -c frpc.toml

Troubleshooting Quick Reference

Symptom Likely Cause Resolution
type [http] not supported vhostHTTPPort not set on frps Add vhostHTTPPort = 80 to frps config
port already in use Another proxy or service holds the port Change remotePort or free the port
port not allowed Port outside allowPorts range Add port to allowPorts or adjust range
authentication failed Token mismatch Verify auth.token matches on frps and frpc
exceed max_ports_per_client Client hit maxPortsPerClient limit Increase limit or reduce proxy count
heartbeat timeout Network interruption or frps unreachable Check network; frpc will auto-reconnect
TLS handshake error frps has TLS forced, frpc not configured Enable TLS on frpc transport.tls config
XTCP fails, falls back to relay NAT type incompatible (both symmetric) Use STCP relay; XTCP cannot traverse double-symmetric NAT
UDP packets truncated udpPacketSize too small Increase udpPacketSize on both sides to 4096+
Dashboard inaccessible Wrong IP binding Ensure webServer.addr is reachable; use 0.0.0.0 for remote access
Metrics always zero enablePrometheus not set Add enablePrometheus = true to frps config
Docker build fails Go toolchain version mismatch Match Go version in go.mod (issue #5288)

Tip

Enable debug logging (log.level = "debug") temporarily during troubleshooting to see detailed message-level activity. Restore "info" in production to avoid log volume issues.

Tip

Use frpc verify -c frpc.toml to validate client config before deploying. This runs the config parser and validator without connecting to frps.