# frp (Fast Reverse Proxy) — Administrator & Developer Guide

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 |

```bash
# 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 |

```bash
# 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.

```bash
# 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

```toml
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

```toml
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

```toml
[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

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

#### Logging

```toml
[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

```toml
[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

```toml
[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

```toml
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

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

#### NAT Hole Analysis

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

#### SSH Tunnel Gateway

```toml
[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

```toml
[[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

```toml
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

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

#### Auth (same structure as server)

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

#### Behavior

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

#### Logging (same structure as server)

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

#### Admin Web Interface

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

#### Transport

```toml
[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)

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

#### Feature Gates

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

#### Other

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

#### Store (Dynamic Config)

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

### 3.4 Proxy Type Configuration

#### TCP Proxy

```toml
[[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

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

#### HTTP Proxy

```toml
[[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

```toml
[[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)

```toml
# 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)

```toml
# 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)

```toml
[[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)

```toml
[[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 |

```toml
[[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

```bash
# 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
```

```bash
# 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

```yaml
# 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
```

```yaml
# 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.

```yaml
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:

```bash
# 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`):

```bash
# 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

```toml
# 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

```toml
[[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

```toml
# 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)

```toml
# 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

```toml
# 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

```toml
# 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

```toml
# 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

```toml
[[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:

```toml
[sshTunnelGateway]
bindPort = 2222
autoGenPrivateKeyPath = "/etc/frp/ssh_host_key"
authorizedKeysFile = "/etc/frp/.authorized_keys"
```

```bash
# 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

```toml
[[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:
```json
{
  "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:

```go
// 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:

```bash
# 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

```go
// 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

```yaml
# 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:

```promql
# 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:

```nginx
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)

```bash
# 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

```python
# 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

```toml
# 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:

```bash
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.

---

_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. Unofficial; not affiliated with or reviewed by the project._
