frp is a high-performance reverse proxy application that exposes local servers behind NAT or firewalls to the public internet. It consists of two binaries: frps (the server, run on a public host) and frpc (the client, run on the machine behind NAT). Written in Go, frp supports TCP, UDP, HTTP, HTTPS, and several secret/P2P tunnel modes, with TOML as the preferred configuration format.
Note
This guide covers frp as of commit
e8dfd6e. Configuration format is TOML (preferred). The legacy INI format is still accepted but deprecated — migrate to TOML.
Table of Contents
1. Quick Start
This section gets frp working end-to-end in under five minutes: a server on a public host and a client on the machine behind NAT, exposing SSH.
Prerequisites
- A public server (VPS, cloud VM, etc.) with a reachable IP address or hostname.
- A local machine behind NAT or a firewall where the service you want to expose is running.
- Both machines must be able to reach each other on the frps bind port (default 7000).
Step 1 — Run frps on the public server
Create frps.toml:
bindPort = 7000
Start the server:
./frps -c frps.toml
Expected output:
2026/05/13 00:00:00 [I] [root.go:102] frps uses config file: frps.toml
2026/05/13 00:00:00 [I] [service.go:225] frps tcp listen on 0.0.0.0:7000
2026/05/13 00:00:00 [I] [root.go:123] frps started successfully
Step 2 — Run frpc on the local machine
Create frpc.toml (replace x.x.x.x with your server's public IP):
serverAddr = "x.x.x.x"
serverPort = 7000
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
Start the client:
./frpc -c frpc.toml
Expected output:
2026/05/13 00:00:00 [I] [root.go:142] frpc uses config file: frpc.toml
2026/05/13 00:00:00 [I] [service.go:287] try to connect to server...
2026/05/13 00:00:00 [I] [service.go:279] [ssh] start proxy success
Step 3 — Connect
ssh -p 6000 user@x.x.x.x
That's it. Traffic on the public server's port 6000 is now forwarded to port 22 on the local machine.
Tip
Always set an
auth.tokenin production. The minimal config above has no authentication — anyone can connect to your frps and register proxies. See Authentication for how to add a shared secret.
2. Installation
2.1 Download Pre-built Binaries
Pre-built binaries for Linux, macOS, Windows, and common architectures are published on the GitHub Releases page.
Linux (amd64)
wget https://github.com/fatedier/frp/releases/latest/download/frp_linux_amd64.tar.gz
tar -xzf frp_linux_amd64.tar.gz
cd frp_linux_amd64/
# Binaries: frps, frpc
Linux (arm64)
wget https://github.com/fatedier/frp/releases/latest/download/frp_linux_arm64.tar.gz
tar -xzf frp_linux_arm64.tar.gz
cd frp_linux_arm64/
macOS (arm64 / Apple Silicon)
wget https://github.com/fatedier/frp/releases/latest/download/frp_darwin_arm64.tar.gz
tar -xzf frp_darwin_arm64.tar.gz
cd frp_darwin_arm64/
macOS (amd64)
wget https://github.com/fatedier/frp/releases/latest/download/frp_darwin_amd64.tar.gz
tar -xzf frp_darwin_amd64.tar.gz
cd frp_darwin_amd64/
Windows (amd64)
Download frp_windows_amd64.zip from the releases page, extract, and run frps.exe / frpc.exe from a command prompt or PowerShell.
2.2 Build from Source
Requires Go 1.25+.
git clone https://github.com/fatedier/frp.git
cd frp
make
# Produces: bin/frps, bin/frpc
Note
Docker container build issues have been reported (GitHub issue #5288). Building from source on the host or using the official pre-built binaries is the most reliable approach.
2.3 Docker
Official images are published to Docker Hub as fatedier/frps and fatedier/frpc.
Server:
docker run -d \
--name frps \
-p 7000:7000 \
-v /path/to/frps.toml:/etc/frp/frps.toml \
fatedier/frps -c /etc/frp/frps.toml
Client:
docker run -d \
--name frpc \
--network host \
-v /path/to/frpc.toml:/etc/frp/frpc.toml \
fatedier/frpc -c /etc/frp/frpc.toml
Tip
Use
--network hostfor frpc when it needs to reach services on127.0.0.1of the Docker host. Without it,localIP = "127.0.0.1"points inside the container, not the host.
2.4 Verify Installation
./frps -v
./frpc -v
2.5 Validate a Config File
Before starting, verify your config syntax:
./frps verify -c frps.toml
./frpc verify -c frpc.toml
2.6 Running as a System Service
Linux (systemd) — frps:
Create /etc/systemd/system/frps.service:
[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=5
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now frps
sudo systemctl status frps
Linux (systemd) — frpc:
[Unit]
Description=frp client
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now frpc
3. Configuration
3.1 Config Format
frp uses TOML as the preferred format. Files are conventionally named frps.toml and frpc.toml. Legacy INI is still parsed but is deprecated — you should migrate.
Pass the config file with the -c flag:
./frps -c /etc/frp/frps.toml
./frpc -c /etc/frp/frpc.toml
3.2 Strict Mode
By default, unknown config keys are silently ignored. Enable strict mode to surface typos and misconfigurations:
./frps -c frps.toml --strict_config
./frpc -c frpc.toml --strict_config
Warning
--strict_configwill refuse to start if there are any unrecognized keys. Always use it during initial setup to catch typos.
3.3 Server Configuration (frps.toml)
Minimal
bindPort = 7000
Full reference
# Network
bindAddr = "0.0.0.0" # Interface to listen on (default: "0.0.0.0")
bindPort = 7000 # Main frp port (default: 7000)
kcpBindPort = 7000 # KCP UDP port (only if using KCP transport)
quicBindPort = 7002 # QUIC UDP port (only if using QUIC transport)
# Virtual Host (HTTP/HTTPS proxies require these)
vhostHTTPPort = 80 # Port for HTTP virtual hosting
vhostHTTPSPort = 443 # Port for HTTPS virtual hosting
vhostHTTPTimeout = 60 # HTTP response header timeout (seconds)
# Subdomain routing
subdomainHost = "frp.example.com" # Wildcard parent domain for subdomain routing
# TCP multiplexing
tcpmuxHTTPConnectPort = 443 # Port for tcpmux HTTP CONNECT proxies
# Authentication
[auth]
method = "token" # "token" or "oidc" (default: "token")
token = "your-secret-token"
additionalScopes = [] # ["HeartBeats", "NewWorkConns"]
# Logging
[log]
to = "console" # "console" or file path (default: "console")
level = "info" # "trace","debug","info","warn","error" (default: "info")
maxDays = 3 # Log file rotation (default: 3)
disablePrintColor = false
# Web Dashboard
[webServer]
addr = "0.0.0.0"
port = 7500 # Dashboard port (default: 7500)
user = "admin"
password = "admin"
enableHTTPS = false
# Transport
[transport]
tcpMux = true # HTTP/2 multiplexing over single TCP conn (default: true)
tcpMuxKeepaliveInterval = 30
maxPoolCount = 5 # Max pre-created connections per client (default: 5)
heartbeatTimeout = -1 # -1 = disabled when tcpMux=true (default: -1)
tls.force = false # Require clients to use TLS
# Proxy limits
maxPortsPerClient = 0 # 0 = unlimited
allowPorts = [] # e.g. [{single=6000},{range={start=8000,end=9000}}]
3.4 Client Configuration (frpc.toml)
Minimal
serverAddr = "x.x.x.x"
serverPort = 7000
[[proxies]]
name = "my-proxy"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8080
remotePort = 9090
Full reference
# Server connection
serverAddr = "x.x.x.x" # frps host (required)
serverPort = 7000 # frps port (default: 7000)
natHoleStunServer = "stun.easyvoip.com:3478" # STUN server for XTCP
# Authentication
[auth]
method = "token"
token = "your-secret-token"
# Logging
[log]
to = "console"
level = "info"
maxDays = 3
# Web Admin API
[webServer]
addr = "127.0.0.1"
port = 7400 # Admin API port (default: 7400)
user = "admin"
password = "admin"
# Transport
[transport]
protocol = "tcp" # "tcp","kcp","quic","websocket","wss" (default: "tcp")
dialServerTimeout = 10 # Connection timeout in seconds (default: 10)
dialServerKeepalive = 7200 # Keepalive interval in seconds (default: 7200)
poolCount = 1 # Pre-created connection pool size (default: 1)
tcpMux = true # Must match server setting (default: true)
tcpMuxKeepaliveInterval = 30
proxyURL = "" # HTTP proxy for frpc→frps connection
heartbeatInterval = -1 # -1 when tcpMux=true (default: -1)
heartbeatTimeout = -1
bandwidthLimit = "1MB" # Per-proxy bandwidth cap, e.g. "1MB", "500KB"
bandwidthLimitMode = "client" # "client" or "server" (default: "client")
tls.enable = true # TLS for frpc→frps channel (default: true since v0.50.0)
tls.disableCustomTLSFirstByte = true # (default: true since v0.50.0)
tls.serverName = ""
tls.certFile = ""
tls.keyFile = ""
tls.trustedCaFile = ""
# UDP packet buffer size (default: 1500)
udpPacketSize = 1500
# Login behavior
loginFailExit = true # Exit if initial login fails (default: true)
# Proxies and visitors are defined below as [[proxies]] and [[visitors]]
3.5 Authentication {#authentication}
Token (default)
The simplest method. Set the same token on both sides:
# frps.toml
[auth]
method = "token"
token = "strong-random-secret"
# frpc.toml
[auth]
method = "token"
token = "strong-random-secret"
Token from file or command:
# frpc.toml
[auth]
method = "token"
[auth.tokenSource]
type = "file"
path = "/run/secrets/frp-token"
[auth.tokenSource]
type = "exec"
command = "cat"
args = ["/run/secrets/frp-token"]
OIDC (OpenID Connect)
For enterprise or cloud setups where you manage identity through an IdP:
# frpc.toml
[auth]
method = "oidc"
[auth.oidc]
clientID = "frp-client"
clientSecret = "secret"
audience = "frp-server"
scope = "openid"
tokenEndpointURL = "https://idp.example.com/oauth/token"
# frps.toml
[auth]
method = "oidc"
[auth.oidc]
issuer = "https://idp.example.com"
audience = "frp-server"
Note
With OIDC you can also restrict which operations require a valid token via
additionalScopes = ["HeartBeats", "NewWorkConns"]on both sides.
3.6 Logging
[log]
to = "/var/log/frp/frps.log" # File path or "console"
level = "info" # trace / debug / info / warn / error
maxDays = 7 # Rotate and keep for N days
disablePrintColor = true # Disable ANSI colors (useful for log files)
3.7 TLS
Since v0.50.0, TLS between frpc and frps is enabled by default (transport.tls.enable = true). For custom certificates:
# frpc.toml
[transport.tls]
enable = true
certFile = "/etc/frp/client.crt"
keyFile = "/etc/frp/client.key"
trustedCaFile = "/etc/frp/ca.crt"
serverName = "frps.example.com"
# frps.toml — force clients to present a valid TLS cert
[transport.tls]
force = true
certFile = "/etc/frp/server.crt"
keyFile = "/etc/frp/server.key"
trustedCaFile = "/etc/frp/ca.crt"
Caution
Setting
transport.tls.force = falseon the server while clients use TLS has produced unexpected behavior (GitHub issue #5131). If you want plaintext connections, explicitly settls.enable = falseon the client side as well, and do not mix TLS and non-TLS clients on the same server.
3.8 Transport Protocols
The transport.protocol option on the client controls how frpc connects to frps:
| Protocol | Description | When to use |
|---|---|---|
tcp |
Default. Reliable TCP connection | General use |
kcp |
UDP-based, lower latency | High-latency / lossy networks |
quic |
QUIC (UDP), modern multiplexing | High-throughput, modern networks |
websocket |
TCP wrapped in WebSocket (ws://) | Behind HTTP proxies/firewalls |
wss |
TLS + WebSocket (wss://) | Behind HTTPS proxies/firewalls |
# frpc.toml — use KCP
[transport]
protocol = "kcp"
# frps.toml — open a KCP port
kcpBindPort = 7000
For QUIC:
# frpc.toml
[transport]
protocol = "quic"
# frps.toml
quicBindPort = 7002
QUIC defaults: keepalivePeriod=10s, maxIdleTimeout=30s, maxIncomingStreams=100000.
Tip
Set
QUIC_GO_DISABLE_ECN=1in the environment if your network drops ECN-marked packets, which can cause QUIC connections to stall.
3.9 Bandwidth Limiting
Limit bandwidth per proxy on the client side:
[[proxies]]
name = "limited-web"
type = "tcp"
localPort = 8080
remotePort = 9090
[proxies.transport]
bandwidthLimit = "2MB"
bandwidthLimitMode = "client" # or "server"
4. Core Usage
This section covers what you can actually do with frp, organized by intent.
4.1 Expose a TCP Service
Use type = "tcp" to forward any TCP port. The most common use cases are SSH, databases, and custom TCP servers. Requires remotePort.
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
Connect:
ssh -p 6000 user@frps-server
Tip
You can expose a database (e.g., PostgreSQL on port 5432) the same way — just change
localPortandremotePort. For maximum security, usestcpinstead oftcpto avoid opening any public port at all.
4.2 Expose a UDP Service
Use type = "udp" for DNS, game servers, VoIP, or any UDP-based service. Requires remotePort.
[[proxies]]
name = "dns"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 6053
Test:
dig @frps-server -p 6053 example.com
Note
UDP forwarding has known limitations with packet reassembly for very large payloads (GitHub issue #5289). The default
udpPacketSize = 1500matches typical MTU — increase it in both frpc and frps configs if your application sends larger UDP datagrams.
4.3 Expose an HTTP Application (Virtual Hosting)
Use type = "http" to expose web applications with virtual host routing. The frps server must have vhostHTTPPort set.
frps.toml:
bindPort = 7000
vhostHTTPPort = 80
frpc.toml:
[[proxies]]
name = "web"
type = "http"
localIP = "127.0.0.1"
localPort = 8080
customDomains = ["www.example.com"]
Now requests to http://www.example.com (hitting frps on port 80) are forwarded to your local app.
Using subdomains (requires subdomainHost on frps):
# frps.toml
subdomainHost = "example.com"
# frpc.toml
[[proxies]]
name = "app"
type = "http"
localPort = 3000
subdomain = "myapp" # accessible at myapp.example.com
HTTP proxy advanced features:
[[proxies]]
name = "web-advanced"
type = "http"
localPort = 8080
customDomains = ["app.example.com"]
locations = ["/api", "/static"] # Only proxy these path prefixes
httpUser = "basicuser" # Basic auth enforced by frps
httpPassword = "basicpass"
hostHeaderRewrite = "internal-hostname" # Rewrite Host header sent to local app
routeByHTTPUser = true # Route to this proxy based on HTTP basic auth user
[proxies.requestHeaders.set]
X-From-Frp = "true"
[proxies.responseHeaders.set]
X-Served-By = "frp"
4.4 Expose an HTTPS Application
Use type = "https" when your local app handles TLS itself, or when you want frps to terminate TLS. Requires vhostHTTPSPort on frps.
Pass-through (local app handles TLS):
# frps.toml
vhostHTTPSPort = 443
# frpc.toml
[[proxies]]
name = "secure-web"
type = "https"
localIP = "127.0.0.1"
localPort = 443
customDomains = ["secure.example.com"]
For TLS termination and protocol conversion, see the Client Plugins section — specifically https2http and https2https.
4.5 TCP Multiplexing (tcpmux)
type = "tcpmux" allows multiple services to share a single port via HTTP CONNECT tunneling. Useful when you have a limited number of public ports.
# frps.toml
tcpmuxHTTPConnectPort = 443
# frpc.toml
[[proxies]]
name = "service-a"
type = "tcpmux"
multiplexer = "httpconnect"
localIP = "127.0.0.1"
localPort = 8080
customDomains = ["service-a.example.com"]
[[proxies]]
name = "service-b"
type = "tcpmux"
multiplexer = "httpconnect"
localIP = "127.0.0.1"
localPort = 8081
customDomains = ["service-b.example.com"]
Clients connect using CONNECT service-a.example.com:443 HTTP/1.1 and frps routes to the correct backend.
4.6 Secret Proxies (No Public Port)
For cases where you want to expose a service to specific trusted clients only, without opening a public port on frps.
stcp — Secret TCP
The service registers a named proxy with a secretKey. Only visitors with the matching key can connect — no public port is opened on frps.
frpc.toml (on the machine with the service):
[[proxies]]
name = "private-ssh"
type = "stcp"
localIP = "127.0.0.1"
localPort = 22
secretKey = "strong-shared-secret"
allowUsers = ["visitor-client-name"] # Optional allowlist of frpc login names
frpc.toml (on the machine that wants to connect):
[[visitors]]
name = "ssh-visitor"
type = "stcp"
serverName = "private-ssh" # Must match proxy name above
secretKey = "strong-shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6000
Connect from the visitor machine:
ssh -p 6000 user@127.0.0.1
sudp — Secret UDP
Same pattern as stcp, for UDP services:
frpc.toml (service side):
[[proxies]]
name = "private-dns"
type = "sudp"
localIP = "127.0.0.1"
localPort = 53
secretKey = "shared-secret"
frpc.toml (visitor side):
[[visitors]]
name = "dns-visitor"
type = "sudp"
serverName = "private-dns"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6053
xtcp — P2P TCP via NAT Hole Punching
xtcp attempts a direct peer-to-peer connection between two clients behind NAT, using the frps server only for signaling. This maximizes throughput and minimizes server load.
frpc.toml (service side):
[[proxies]]
name = "p2p-ssh"
type = "xtcp"
localIP = "127.0.0.1"
localPort = 22
secretKey = "shared-secret"
allowUsers = ["visitor-name"]
frpc.toml (visitor side):
[[visitors]]
name = "p2p-ssh-visitor"
type = "xtcp"
serverName = "p2p-ssh"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6000
keepTunnelOpen = false # Keep tunnel alive even when idle
maxRetriesAnHour = 8 # Max hole-punch retries per hour
minRetryInterval = 90 # Minimum seconds between retries
fallbackTo = "stcp-fallback" # Fallback visitor name if hole punch fails
fallbackTimeoutMs = 2000 # Milliseconds before falling back
Note
xtcp throughput may be lower than standard TCP proxying in some NAT configurations (GitHub issue #5281). The STUN server (
natHoleStunServer) must be reachable from both clients for NAT hole punching to work. Default:stun.easyvoip.com:3478.
Tip
Always configure a
fallbackTostcp visitor so that if NAT hole punching fails (e.g., symmetric NAT on either side), traffic automatically routes through frps instead with no manual intervention.
4.7 Client Plugins {#47-client-plugins}
Client plugins intercept traffic between frpc and your local service, enabling protocol translation, authentication injection, TLS handling, and more. All plugins are configured with [proxies.plugin].
http_proxy — Forward HTTP Proxy
Turn frp into a shared HTTP/HTTPS forward proxy:
[[proxies]]
name = "http-fwd"
type = "tcp"
remotePort = 8888
[proxies.plugin]
type = "http_proxy"
httpUser = "proxyuser"
httpPassword = "proxypass"
Use it:
curl -x http://proxyuser:proxypass@frps-server:8888 http://example.com
socks5 — SOCKS5 Proxy
[[proxies]]
name = "socks5-fwd"
type = "tcp"
remotePort = 1080
[proxies.plugin]
type = "socks5"
username = "user"
password = "pass"
Use it with any SOCKS5-compatible client:
curl --socks5 user:pass@frps-server:1080 http://example.com
static_file — Serve Local Files Over HTTP
Serve a directory of files to the public:
[[proxies]]
name = "file-server"
type = "tcp"
remotePort = 8080
[proxies.plugin]
type = "static_file"
localPath = "/home/user/public"
stripPrefix = "static" # Strip /static prefix from URLs
httpUser = "viewer"
httpPassword = "secret"
Access: http://frps-server:8080/ (with basic auth credentials)
unix_domain_socket — Expose a Unix Domain Socket
[[proxies]]
name = "docker-sock"
type = "tcp"
remotePort = 2375
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/var/run/docker.sock"
Warning
Exposing
/var/run/docker.sockpublicly gives full Docker control to anyone who can connect. Always protect this with anauth.tokenand consider usingstcpinstead oftcpto avoid any public port exposure.
http2https — HTTP → HTTPS
Convert incoming plain HTTP to HTTPS for a local service:
[[proxies]]
name = "h2s"
type = "http"
customDomains = ["app.example.com"]
[proxies.plugin]
type = "http2https"
localAddr = "127.0.0.1:8443"
hostHeaderRewrite = "app.example.com"
[proxies.plugin.requestHeaders.set]
X-From-Frp = "true"
https2http — HTTPS → HTTP (TLS Termination)
frpc terminates TLS and forwards plain HTTP to the local service:
[[proxies]]
name = "s2h"
type = "https"
customDomains = ["app.example.com"]
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8080"
crtPath = "/etc/frp/server.crt"
keyPath = "/etc/frp/server.key"
hostHeaderRewrite = "app.example.com"
enableHTTP2 = true
https2https — HTTPS → HTTPS (Cert Swap)
Replace the certificate seen by the public while keeping the backend connection encrypted:
[[proxies]]
name = "cert-swap"
type = "https"
customDomains = ["app.example.com"]
[proxies.plugin]
type = "https2https"
localAddr = "127.0.0.1:8443"
crtPath = "/etc/frp/public.crt"
keyPath = "/etc/frp/public.key"
enableHTTP2 = true
http2http — HTTP → HTTP (Header Rewriting)
Transparently proxy HTTP while rewriting headers:
[[proxies]]
name = "http-rewrite"
type = "http"
customDomains = ["app.example.com"]
[proxies.plugin]
type = "http2http"
localAddr = "127.0.0.1:8080"
hostHeaderRewrite = "internal.app"
[proxies.plugin.requestHeaders.set]
X-Real-IP = "$remote_addr"
tls2raw — Strip TLS, Forward Raw
frpc terminates the TLS layer and forwards raw bytes to a local TCP service:
[[proxies]]
name = "tls-strip"
type = "tcp"
remotePort = 9000
[proxies.plugin]
type = "tls2raw"
localAddr = "127.0.0.1:9001"
crtPath = "/etc/frp/server.crt"
keyPath = "/etc/frp/server.key"
virtual_net — Virtual Network Interface
Enables a virtual network (layer 3) interface between frp nodes. Requires the VirtualNet feature gate.
[[proxies]]
name = "vnet-proxy"
type = "xtcp"
localIP = "127.0.0.1"
localPort = 0
[proxies.plugin]
type = "virtual_net"
Note
virtual_netis an advanced feature that enables IP-level routing between frp nodes. See the Admin Guide for architecture details and feature gate configuration.
4.8 Web Dashboard and Admin API
frps Dashboard
frps has a built-in web dashboard for monitoring connections:
# frps.toml
[webServer]
addr = "0.0.0.0"
port = 7500
user = "admin"
password = "admin"
Open http://frps-server:7500 in a browser. The dashboard shows:
- Connected clients
- Active proxies and their traffic statistics
- System status
Warning
The dashboard has no authentication if you omit
user/password. Always set credentials, or bind to127.0.0.1and access via SSH tunnel:ssh -L 7500:localhost:7500 root@frps-server. Then openhttp://localhost:7500.
frpc Admin API
frpc exposes a local HTTP API for live management:
# frpc.toml
[webServer]
addr = "127.0.0.1"
port = 7400
user = "admin"
password = "admin"
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/healthz |
GET | No | Health check |
/api/reload |
GET | Yes | Reload config file without restart |
/api/stop |
POST | Yes | Stop the frpc process |
/api/status |
GET | Yes | Current proxy status |
/api/config |
GET | Yes | Return current running config |
/api/config |
PUT | Yes | Replace running config |
/api/proxy/{name}/config |
GET | Yes | Config for a named proxy |
/api/visitor/{name}/config |
GET | Yes | Config for a named visitor |
Examples:
# Check health
curl http://admin:admin@127.0.0.1:7400/healthz
# Reload config after editing frpc.toml
curl http://admin:admin@127.0.0.1:7400/api/reload
# Check proxy status
curl http://admin:admin@127.0.0.1:7400/api/status | jq .
# Stop frpc gracefully
curl -X POST http://admin:admin@127.0.0.1:7400/api/stop
4.9 Health Checks
Configure health checks so frp automatically removes unhealthy proxies from the pool (especially useful with load balancing):
TCP health check:
[[proxies]]
name = "web-with-hc"
type = "tcp"
localPort = 8080
remotePort = 9090
[proxies.healthCheck]
type = "tcp"
timeoutSeconds = 3 # (default: 3)
maxFailed = 1 # Failed checks before removal (default: 1)
intervalSeconds = 10 # Check interval (default: 10)
HTTP health check:
[[proxies]]
name = "api-with-hc"
type = "http"
localPort = 8080
customDomains = ["api.example.com"]
[proxies.healthCheck]
type = "http"
path = "/healthz"
timeoutSeconds = 5
maxFailed = 3
intervalSeconds = 15
[proxies.healthCheck.httpHeaders]
X-Health-Check = "frp"
4.10 Load Balancing
Distribute traffic across multiple backend instances by assigning proxies to a group:
# On backend machine 1 (frpc instance A)
[[proxies]]
name = "web-1"
type = "http"
localPort = 8080
customDomains = ["lb.example.com"]
[proxies.loadBalancer]
group = "web-cluster"
groupKey = "shared-lb-secret"
# On backend machine 2 (frpc instance B)
[[proxies]]
name = "web-2"
type = "http"
localPort = 8080
customDomains = ["lb.example.com"]
[proxies.loadBalancer]
group = "web-cluster"
groupKey = "shared-lb-secret"
Note
Load balancing is available for
tcp,http, andtcpmuxproxy types. All proxies in a group must share the samegroupKey.
4.11 SSH Tunnel Gateway
frps supports a built-in SSH tunnel gateway that allows users to register proxies without running frpc — using only standard ssh commands. This is ideal for CI/CD pipelines, ephemeral tunnels, or environments where installing frpc is not practical.
Enable on frps:
# frps.toml
[sshTunnelGateway]
bindPort = 2200
privateKeyFile = "/etc/frp/ssh_host_rsa_key"
autoGenPrivateKeyPath = "./.autogen_ssh_key" # Auto-generate if privateKeyFile is missing
authorizedKeysFile = "/etc/frp/authorized_keys"
Create a TCP tunnel via SSH:
ssh -R :6000:localhost:22 v0@frps-server -p 2200
This registers a tcp proxy with remotePort=6000 forwarding to local port 22.
HTTP tunnel:
ssh -R myapp.example.com:80:localhost:3000 v0@frps-server -p 2200
Supported proxy types via SSH gateway: tcp, http, https, tcpmux, stcp, sudp
Tip
The SSH gateway is perfect for development workflows: expose a local dev server with a single
sshcommand, no frpc binary or config file required. The tunnel lives as long as the SSH session is open.
5. Common Tasks
5.1 Workflow Recipes
Recipe 1: Expose SSH from a Home Server
# frps.toml (on public server)
bindPort = 7000
[auth]
token = "my-secret"
# frpc.toml (on home server)
serverAddr = "my-vps.example.com"
serverPort = 7000
[auth]
token = "my-secret"
[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 2222
ssh -p 2222 [email removed]
Recipe 2: Expose Multiple Web Apps on One Server
# frps.toml
bindPort = 7000
vhostHTTPPort = 80
subdomainHost = "frp.example.com"
[auth]
token = "my-secret"
# frpc.toml
serverAddr = "my-vps.example.com"
serverPort = 7000
[auth]
token = "my-secret"
[[proxies]]
name = "blog"
type = "http"
localPort = 4000
subdomain = "blog"
[[proxies]]
name = "api"
type = "http"
localPort = 8080
subdomain = "api"
[[proxies]]
name = "app"
type = "http"
localPort = 3000
subdomain = "app"
Results: http://blog.frp.example.com, http://api.frp.example.com, http://app.frp.example.com
Recipe 3: Secure Database Access Without a Public Port
# On the database machine (stcp service side)
[[proxies]]
name = "postgres"
type = "stcp"
localIP = "127.0.0.1"
localPort = 5432
secretKey = "db-secret-key"
allowUsers = ["dev-machine"]
# On the developer machine (stcp visitor)
[[visitors]]
name = "postgres-visitor"
type = "stcp"
serverName = "postgres"
secretKey = "db-secret-key"
bindAddr = "127.0.0.1"
bindPort = 15432
psql -h 127.0.0.1 -p 15432 -U dbuser mydb
No public port is opened. The database is reachable only by visitors with the correct secretKey.
Recipe 4: Share a Local Dev Server with a Teammate in Real Time
[[proxies]]
name = "devserver"
type = "http"
localPort = 3000
customDomains = ["dev.example.com"]
The teammate opens http://dev.example.com and sees the developer's local app live.
Recipe 5: Expose a DNS Server
[[proxies]]
name = "pihole"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 5353
dig @my-vps.example.com -p 5353 google.com
Recipe 6: Access Home Network via SOCKS5 from Anywhere
# frpc.toml (on home router / server)
[[proxies]]
name = "socks5-home"
type = "tcp"
remotePort = 1080
[proxies.plugin]
type = "socks5"
username = "homeuser"
password = "homepass"
curl --socks5 homeuser:[email removed]:1080 http://192.168.1.100/
All traffic is routed through the home network.
Recipe 7: Serve a Local Directory Publicly
[[proxies]]
name = "files"
type = "tcp"
remotePort = 8080
[proxies.plugin]
type = "static_file"
localPath = "/home/user/share"
httpUser = "user"
httpPassword = "secret"
Access: http://my-vps.example.com:8080/ with credentials.
Recipe 8: Expose Docker Socket Securely via stcp
# On the Docker host
[[proxies]]
name = "docker"
type = "stcp"
secretKey = "docker-secret"
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/var/run/docker.sock"
# On the CI machine (visitor)
[[visitors]]
name = "docker-visitor"
type = "stcp"
serverName = "docker"
secretKey = "docker-secret"
bindAddr = "127.0.0.1"
bindPort = 2375
DOCKER_HOST=tcp://127.0.0.1:2375 docker ps
Recipe 9: Resilient P2P Tunnel with Automatic Fallback
# frpc.toml (visitor side)
# Primary: P2P via NAT hole punching
[[visitors]]
name = "p2p-ssh"
type = "xtcp"
serverName = "remote-ssh"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6000
fallbackTo = "fallback-ssh"
fallbackTimeoutMs = 3000
# Fallback: route through frps if P2P fails
[[visitors]]
name = "fallback-ssh"
type = "stcp"
serverName = "remote-ssh-fallback"
secretKey = "shared-secret"
bindAddr = "127.0.0.1"
bindPort = 6001
Recipe 10: HTTP Basic Auth for an Unauthenticated Local Service
Your local app has no authentication. Add it at the frp layer:
[[proxies]]
name = "protected-app"
type = "http"
localPort = 8080
customDomains = ["app.example.com"]
httpUser = "admin"
httpPassword = "strongpassword"
frps challenges incoming requests with HTTP 401 before forwarding. The local app never sees unauthenticated requests.
Recipe 11: TLS Termination for a Plain HTTP Local App
# frps.toml
vhostHTTPSPort = 443
# frpc.toml
[[proxies]]
name = "tls-term"
type = "https"
customDomains = ["secure.example.com"]
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8080"
crtPath = "/etc/ssl/secure.example.com.crt"
keyPath = "/etc/ssl/secure.example.com.key"
Public clients connect via HTTPS; frpc strips TLS and forwards HTTP to the local app.
Recipe 12: CI/CD Webhook Receiver
Expose a local webhook handler during development:
[[proxies]]
name = "webhook"
type = "http"
localPort = 3001
customDomains = ["hooks.example.com"]
Configure GitHub/GitLab/Stripe to send webhooks to http://hooks.example.com/webhook. Your local handler receives them in real time.
Recipe 13: KCP Transport for High-Latency Networks
# frps.toml
kcpBindPort = 7001
# frpc.toml
serverAddr = "my-vps.example.com"
serverPort = 7001
[transport]
protocol = "kcp"
All proxy definitions remain identical — only the transport changes.
Recipe 14: Bandwidth-Throttled Public Service
[[proxies]]
name = "throttled-api"
type = "tcp"
localPort = 8080
remotePort = 9090
[proxies.transport]
bandwidthLimit = "500KB"
bandwidthLimitMode = "client"
Recipe 15: Multiple Proxies with Health Checks
serverAddr = "x.x.x.x"
serverPort = 7000
[auth]
token = "secret"
[[proxies]]
name = "web"
type = "http"
localPort = 8080
customDomains = ["www.example.com"]
[proxies.healthCheck]
type = "http"
path = "/health"
intervalSeconds = 10
[[proxies]]
name = "api"
type = "tcp"
localPort = 9000
remotePort = 19000
[proxies.healthCheck]
type = "tcp"
intervalSeconds = 15
[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 2222
Recipe 16: Hot Reload Config Without Restart
# Edit frpc.toml while frpc is running, then:
curl http://admin:admin@127.0.0.1:7400/api/reload
New proxy definitions are applied immediately without dropping existing connections.
Recipe 17: Expose a Game Server (UDP + TCP)
[[proxies]]
name = "game-tcp"
type = "tcp"
localPort = 27015
remotePort = 27015
[[proxies]]
name = "game-udp"
type = "udp"
localPort = 27015
remotePort = 27015
Recipe 18: Restrict Which Remote Ports frps Allows
# frps.toml — only allow ports 6000-6010 and 8080
[[allowPorts]]
single = 8080
[[allowPorts]]
range.start = 6000
range.end = 6010
frps rejects any proxy registration requesting a port outside this list.
Recipe 19: WebSocket Transport (Behind an HTTP Corporate Proxy)
# frpc.toml — when corporate firewall only allows HTTP/HTTPS
serverAddr = "my-vps.example.com"
serverPort = 80
[transport]
protocol = "websocket"
For HTTPS-only environments:
serverPort = 443
[transport]
protocol = "wss"
Recipe 20: Monitor All Connections via frps Dashboard Securely
# frps.toml — bind dashboard to localhost only
[webServer]
addr = "127.0.0.1"
port = 7500
user = "admin"
password = "strongadminpassword"
# SSH tunnel to access it from your local machine
ssh -L 7500:localhost:7500 [email removed]
# Open http://localhost:7500 in browser
Recipe 21: Expose Minecraft Server
[[proxies]]
name = "minecraft"
type = "tcp"
localPort = 25565
remotePort = 25565
Players connect to my-vps.example.com:25565.
Recipe 22: SSH Tunnel Gateway — Ephemeral Dev Tunnel Without frpc Binary
# One command on any machine with SSH — no frpc installed
ssh -R :8080:localhost:3000 [email removed] -p 2200
# Share http://my-vps.example.com:8080 with teammates
# Tunnel closes when you close the SSH session
Recipe 23: Restrict per-Client Port Quota
# frps.toml
maxPortsPerClient = 5
Each authenticated client can register at most 5 proxies.
Recipe 24: Load-Balanced HTTP with Health Checks
# frpc instance 1
[[proxies]]
name = "app-1"
type = "http"
localPort = 8080
customDomains = ["app.example.com"]
[proxies.loadBalancer]
group = "app-pool"
groupKey = "lb-secret"
[proxies.healthCheck]
type = "http"
path = "/health"
# frpc instance 2
[[proxies]]
name = "app-2"
type = "http"
localPort = 8080
customDomains = ["app.example.com"]
[proxies.loadBalancer]
group = "app-pool"
groupKey = "lb-secret"
[proxies.healthCheck]
type = "http"
path = "/health"
Unhealthy instances are automatically removed from the pool.
Recipe 25: Expose a Jupyter Notebook
[[proxies]]
name = "jupyter"
type = "tcp"
remotePort = 8888
[proxies.plugin]
type = "http_proxy"
httpUser = "labuser"
httpPassword = "labpass"
Or directly (if Jupyter has its own auth):
[[proxies]]
name = "jupyter"
type = "tcp"
localPort = 8888
remotePort = 8888
Recipe 26: Route HTTP Traffic by Path
[[proxies]]
name = "api-routes"
type = "http"
localPort = 8080
customDomains = ["example.com"]
locations = ["/api/v1", "/api/v2"]
[[proxies]]
name = "frontend"
type = "http"
localPort = 3000
customDomains = ["example.com"]
locations = ["/"]
/api/* routes to port 8080; everything else routes to port 3000.
5.2 Proxy Type Quick Reference
| Type | Public Port | Direction | Use Case |
|---|---|---|---|
tcp |
Yes (remotePort) |
Server → Local | SSH, databases, raw TCP |
udp |
Yes (remotePort) |
Server → Local | DNS, games, VoIP |
http |
Shared (vhostHTTPPort) |
Server → Local | Web apps, virtual hosting |
https |
Shared (vhostHTTPSPort) |
Server → Local | TLS web apps |
tcpmux |
Shared (tcpmuxHTTPConnectPort) |
Server → Local | Multiple TCP on one port |
stcp |
None | Peer → Peer (via frps) | Secure private TCP |
xtcp |
None | Peer → Peer (direct) | P2P TCP, high throughput |
sudp |
None | Peer → Peer (via frps) | Secure private UDP |
5.3 Plugin Quick Reference
| Plugin | Primary Use | Key Options |
|---|---|---|
http_proxy |
HTTP/HTTPS forward proxy | httpUser, httpPassword |
socks5 |
SOCKS5 proxy | username, password |
static_file |
Serve local directory | localPath, stripPrefix, httpUser, httpPassword |
unix_domain_socket |
Expose UDS as TCP | unixPath |
http2https |
HTTP → HTTPS | localAddr, hostHeaderRewrite, requestHeaders |
https2http |
TLS termination | localAddr, crtPath, keyPath, enableHTTP2 |
https2https |
TLS cert replacement | localAddr, crtPath, keyPath, enableHTTP2 |
http2http |
HTTP header rewriting | localAddr, hostHeaderRewrite, requestHeaders |
tls2raw |
Strip TLS → raw TCP | localAddr, crtPath, keyPath |
virtual_net |
L3 virtual network | (no extra options; requires feature gate) |
6. Troubleshooting
6.1 Connection and Startup Errors
Error: dial tcp: connect: connection refused (frpc → frps)
Cause: frpc cannot reach frps on the configured serverAddr:serverPort.
Solution:
- Verify frps is running:
systemctl status frpsor check the frps process. - Check the port is listening:
ss -tlnp | grep 7000(ornetstat -an | grep 7000). - Check firewall:
ufw statusor cloud security group rules — port 7000/tcp must be open inbound on the frps host. - Confirm
serverAddrandserverPortinfrpc.tomlare correct.
Prevention: Use ./frpc verify -c frpc.toml and ./frps verify -c frps.toml before starting.
Error: type [http] not supported when vhost http port is not set
Cause: frpc is registering an http-type proxy but vhostHTTPPort is not configured in frps.toml. (GitHub issue #5097)
Solution: Add to frps.toml:
vhostHTTPPort = 80
Restart frps. For HTTPS proxies, also add vhostHTTPSPort = 443.
Prevention: Always configure vhostHTTPPort on frps when any client uses type = "http".
Error: login to server failed: auth failed or authorization failed
Cause: Token mismatch between frpc and frps.
Solution:
- Compare
[auth] tokenvalues in bothfrps.tomlandfrpc.toml— they must be identical, including whitespace and case. - If using
tokenSource, verify the file or command returns the exact expected value. - If using OIDC, confirm the token endpoint is reachable and credentials are correct.
Error: port already in use / bind: address already in use
Cause: Another process is already using remotePort or bindPort.
Solution:
- Find the conflicting process:
ss -tlnp | grep :PORTorlsof -i :PORT. - Either stop the conflicting process or choose a different
remotePort. - Check
allowPortson frps — if set, the requested port may be outside the allowed range.
Error: proxy name already exists
Cause: Two frpc instances are registering a proxy with the same name for the same authenticated user.
Solution: Ensure every proxy name is unique across all frpc instances connecting to the same frps.
Error: transport: authentication handshake failed: tls: certificate verify failed
Cause: TLS certificate mismatch. The client's trustedCaFile doesn't trust the server's certificate, or serverName doesn't match the cert's CN/SAN.
Solution:
- Set
transport.tls.trustedCaFileto the CA that signed the server cert. - Set
transport.tls.serverNameto match the certificate's Common Name or a Subject Alternative Name.
Error: transport.tls.force = false not working as expected
Cause: Known behavior issue (GitHub issue #5131). Mixed TLS/non-TLS behavior on frps does not always work as expected.
Solution: Be explicit — either force TLS on all connections (force = true on server + tls.enable = true on all clients), or explicitly disable TLS everywhere. Do not mix modes.
Error: failed to get workspace, maybe already shutdown
Cause: frps process has stopped or the connection was lost before the proxy registered.
Solution: Ensure frps is running and network connectivity is stable. Review frps logs for crash or shutdown reasons.
Error: Docker container build fails
Cause: Known issue with the Docker build configuration (GitHub issue #5288).
Solution: Build from source on the host (make) or use pre-built binaries. Alternatively, use the official Docker Hub images fatedier/frps and fatedier/frpc.
Error: EOF from frpc during startup
Cause: frps closed the connection immediately — usually due to auth failure or version mismatch.
Solution:
- Check frps logs for more detail.
- Verify tokens match on both sides.
- Ensure both
frpsandfrpcare the same version (./frps -vand./frpc -v).
Error: [W] [control.go] login from client error: [...] max ports per client
Cause: The frps maxPortsPerClient limit has been reached.
Solution: Reduce the number of proxies on the client, or increase maxPortsPerClient in frps.toml (0 = unlimited).
Error: Config parse error on startup
Cause: TOML syntax error or unrecognized config key.
Solution:
./frps verify -c frps.toml
./frpc verify -c frpc.toml
Use --strict_config for maximum validation. Check for typos in key names — TOML keys are camelCase in frp (e.g., serverAddr, not server_addr).
6.2 HTTP and HTTPS Proxy Issues
Error: 404 Not Found from frps for HTTP proxies
Cause: The Host header in the request doesn't match any registered customDomains or subdomain.
Solution:
- Confirm DNS for the domain points to the frps server's IP.
- Check that
customDomainsinfrpc.tomlmatches the requested domain exactly (includingwww.prefix if applicable). - If using subdomains, confirm
subdomainHostis set infrps.tomland the subdomain matches.
Error: HTTP proxy returns 502 Bad Gateway
Cause: frpc registered successfully but cannot reach the local service at localIP:localPort.
Solution:
- Verify the local service is running:
curl http://127.0.0.1:localPort/. - Check
localIP— in Docker,127.0.0.1inside the frpc container is the container itself, not the Docker host. Use the host's container IP or--network host. - If a health check is configured with
maxFailed = 1and the app is slow to start, the proxy may be removed before the app is ready.
Error: HSTS / mixed-content browser errors with HTTPS proxies
Cause: Protocol mismatch between what the browser expects and what frps delivers.
Solution:
- Use the
https2httpplugin on frpc with a real certificate to terminate TLS properly. - Ensure
customDomainsmatches the exact domain in the certificate. - For Let's Encrypt: provision and renew certs on the machine running frpc, not frps.
Error: HTTP basic auth prompt loops or always fails
Cause: httpUser/httpPassword in the proxy config does not match what the browser is sending, or credentials are cached incorrectly.
Solution:
- Verify credentials are correct in
frpc.toml. - Clear browser saved credentials and try again.
- Test with curl to rule out browser caching:
curl -u user:pass http://domain/.
Error: Request headers not being set on forwarded requests
Cause: requestHeaders.set may have wrong syntax in TOML.
Solution: Ensure headers are under the correct section:
[[proxies]]
name = "web"
type = "http"
localPort = 8080
customDomains = ["example.com"]
[proxies.requestHeaders.set]
X-Custom = "value"
6.3 UDP Issues
Error: UDP proxy drops large packets
Cause: udpPacketSize defaults to 1500 (Ethernet MTU). Packets larger than this are silently dropped (GitHub issue #5289).
Solution: Increase udpPacketSize in both frpc.toml and frps.toml:
udpPacketSize = 4096
Set it to match your application's maximum datagram size.
Error: UDP tunnel intermittently loses data
Cause: UDP is inherently unreliable; frp does not add reliability guarantees at the tunnel level.
Solution: For applications requiring reliability over UDP (e.g., QUIC-based apps), consider whether the application's own retry logic is sufficient, or use a TCP-based approach if possible.
6.4 Secret and P2P Proxy Issues
Error: stcp visitor can't connect — server not found
Cause: serverName in the visitor config doesn't match the name of the proxy.
Solution: serverName in [[visitors]] must exactly match name in [[proxies]]. These are case-sensitive.
Error: xtcp NAT hole punching fails — no direct connection established
Cause: Symmetric NAT on one or both sides prevents hole punching. The STUN server may also be unreachable.
Solution:
- Verify
natHoleStunServeris reachable from both frpc instances:nc -u stun.easyvoip.com 3478. - Configure
fallbackTopointing to an stcp visitor as fallback. - If behind symmetric NAT on both sides, xtcp will not work — use stcp instead.
Error: xtcp throughput lower than expected
Cause: Known limitation for some NAT/routing configurations (GitHub issue #5281). The direct P2P path may be suboptimal.
Solution: Compare performance with stcp for your specific network setup. Use stcp if throughput is a priority.
Error: stcp allowUsers is rejecting a valid visitor
Cause: The visitor's frpc login name doesn't match an entry in allowUsers.
Solution: Check the frpc login name (derived from authentication method and client identity). Use allowUsers = ["*"] to permit all authenticated visitors during debugging, then tighten.
6.5 Performance and Stability
Error: Traffic metrics not updating in real time on dashboard
Cause: Known display issue — some proxy types don't update traffic counters in real time (GitHub issue #5282).
Solution: Refresh the dashboard page. Metrics are eventually consistent. Actual traffic forwarding is unaffected.
Error: High CPU usage on frpc
Cause: Excessive transport.poolCount, frequent proxy open/close cycles, or many active health checks.
Solution:
- Reduce
transport.poolCount(default is 1 — increase only when needed). - Increase
intervalSecondsfor health checks if sub-second precision isn't required.
Error: Connection drops after idle period
Cause: Firewall or NAT device timed out the TCP connection before frp's keepalive fired.
Solution:
- With
transport.tcpMux = true(default), lowertcpMuxKeepaliveInterval:
[transport]
tcpMuxKeepaliveInterval = 15
- If
tcpMux = false, setheartbeatInterval = 30andheartbeatTimeout = 90in frpc. - Switch to
protocol = "kcp"which handles keepalives differently.
Error: frpc reconnects in a loop at startup
Cause: loginFailExit = true (default) causes frpc to exit on auth failure; systemd's Restart=on-failure restarts it immediately, creating a loop.
Solution:
- Check actual error in frpc logs — it's usually an auth failure or network issue.
- To allow frpc to retry indefinitely:
loginFailExit = false.
Error: QUIC connection fails or keeps dropping
Cause: ECN-marked packets being dropped by intermediate routers or NAT devices.
Solution: Disable ECN:
export QUIC_GO_DISABLE_ECN=1
./frpc -c frpc.toml
Or in the systemd unit:
[Service]
Environment=QUIC_GO_DISABLE_ECN=1
6.6 SSH Tunnel Gateway Issues
Error: Permission denied (publickey) when using SSH tunnel gateway
Cause: The SSH public key is not in the authorizedKeysFile, or it's in an unrecognized format.
Solution:
- Add your public key to the
authorizedKeysFilepath configured in frps. - Verify format: one
ssh-rsaorssh-ed25519entry per line, same format as~/.ssh/authorized_keys. - Validate key:
ssh-keygen -l -f ~/.ssh/id_ed25519.pub.
Error: SSH tunnel exits immediately with no output
Cause: The requested remote port is outside allowPorts on frps, or the requested proxy type is unsupported.
Solution:
- Check
allowPortsinfrps.toml. - Confirm the proxy type is supported by the gateway:
tcp,http,https,tcpmux,stcp,sudp.
Error: SSH gateway host key mismatch warning
Cause: The frps SSH gateway host key changed (e.g., frps was reinstalled or the key was regenerated).
Solution: Remove the old entry from ~/.ssh/known_hosts:
ssh-keygen -R [frps-server]:2200
Then reconnect and accept the new host key.
6.7 Config Reload Issues
Error: Config reload (/api/reload) has no effect for some changes
Cause: Server address, transport protocol, and authentication method cannot be hot-reloaded — they require a full restart.
Solution: Proxy-level additions, removals, and modifications can be reloaded. Restart frpc for transport or auth changes.
7. FAQ
Q: What is frp and what problem does it solve? frp is a reverse proxy that makes local services accessible from the public internet when your machine is behind NAT or a firewall. frpc (client) establishes an outbound connection to frps (server on a public host), and incoming traffic to frps is relayed through that tunnel to your local service. No inbound port forwarding on your router is needed.
Q: What is the difference between frps and frpc?
frps is the server — it runs on a public host with a reachable IP, listens for client connections and end-user traffic, and routes them together. frpc is the client — it runs on the machine behind NAT, connects outbound to frps, and declares which local services to expose.
Q: Do I need to open any ports on my local machine or router?
No. frpc makes an outbound connection to frps. No inbound ports need to be opened on the local machine or its NAT router. Only the frps host needs ports open inbound (bindPort 7000 by default, plus any remotePort values you configure).
Q: Which config format should I use — TOML or INI? Use TOML. INI is the legacy format and is deprecated. New configs should be TOML. Existing INI configs should be migrated.
Q: Can I run multiple frpc instances connecting to the same frps?
Yes. Multiple frpc instances can connect simultaneously. Each must use unique proxy names. If maxPortsPerClient is set, it applies independently per connected client.
Q: Can multiple proxies share the same remote port?
For tcp and udp type proxies, each proxy needs a unique remotePort. For http/https, multiple proxies share vhostHTTPPort/vhostHTTPSPort because routing uses the HTTP Host header. For tcpmux, proxies share tcpmuxHTTPConnectPort.
Q: What is stcp and when should I use it instead of tcp?
stcp exposes a service to specific trusted clients without opening any public port on frps. Both sides need a matching secretKey. Use stcp for databases, internal tools, or anything that shouldn't be publicly accessible. Use tcp only when the service is genuinely meant to be public.
Q: What is xtcp and when should I use it?
xtcp attempts a direct peer-to-peer connection via NAT hole punching — frps is only used for signaling. Once connected, traffic flows directly between the two frpc clients without passing through frps. Use it for high-throughput or latency-sensitive scenarios between two clients behind NAT. Note: it requires a reachable STUN server and doesn't work through symmetric NAT. Always configure a fallbackTo stcp visitor.
Q: What is a visitor in frp?
A visitor is a [[visitors]] block in frpc that allows that client to connect to an stcp, xtcp, or sudp proxy registered by another frpc instance. The visitor binds a local port; frp securely routes connections on that port to the remote service.
Q: How do I check if a proxy registered successfully?
# Via frpc admin API
curl http://admin:admin@127.0.0.1:7400/api/status | jq .
# Or watch frpc logs for:
# [proxy_name] start proxy success
Q: How do I reload frpc config without restarting the process?
curl http://admin:admin@127.0.0.1:7400/api/reload
This applies changes to [[proxies]] and [[visitors]]. Transport and server connection settings require a full restart.
Q: What is transport.poolCount and should I change it?
poolCount (default: 1) controls how many pre-created connections frpc maintains to frps, reducing latency when new proxy connections are established. Increase it (e.g., 5) if you have many proxies opening/closing frequently and observe connection latency. For steady, long-lived proxies, the default of 1 is fine.
Q: What does loginFailExit do?
When loginFailExit = true (default), frpc exits immediately if the initial connection or authentication to frps fails. Set it to false to make frpc retry indefinitely — useful when frps may not be running yet at frpc startup.
Q: Can frp handle multiple domains for one proxy?
Yes. customDomains is a list:
[[proxies]]
name = "multi-domain"
type = "http"
localPort = 8080
customDomains = ["example.com", "www.example.com", "alt.example.com"]
Q: What is subdomainHost and how does it differ from customDomains?
subdomainHost is a wildcard parent domain set once on frps (e.g., frp.example.com). Clients set subdomain = "myapp" to get myapp.frp.example.com. You only need one DNS wildcard record (*.frp.example.com → frps IP). customDomains requires each domain individually DNS-pointed to frps, but works for any domain name — not just subdomains of a parent.
Q: How does HTTP path routing work with locations?
[[proxies]]
name = "api-only"
type = "http"
localPort = 8080
customDomains = ["example.com"]
locations = ["/api", "/v2"]
Only requests whose paths begin with /api or /v2 are routed to this proxy. Other paths on the same domain can route to a different proxy.
Q: How do I add custom request or response headers?
[[proxies]]
name = "web"
type = "http"
localPort = 8080
customDomains = ["app.example.com"]
[proxies.requestHeaders.set]
X-Real-IP = "$remote_addr"
X-Forwarded-Proto = "https"
[proxies.responseHeaders.set]
Cache-Control = "no-store"
Q: Can I use frp with a wildcard TLS certificate?
Yes. Configure the https2http plugin on frpc with your wildcard cert (e.g., *.example.com), and use any matching subdomain in customDomains.
Q: What is routeByHTTPUser and how does it work?
When routeByHTTPUser = true, frps uses the HTTP basic auth username to decide which proxy handles the request. Different usernames can be routed to different backends on the same domain and path. This requires HTTP basic auth to be present on the request.
Q: How do I expose a service that only listens on a Unix domain socket?
[[proxies]]
name = "uds-service"
type = "tcp"
remotePort = 9000
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/var/run/myapp.sock"
Q: What does hostHeaderRewrite do?
It replaces the Host header sent to the local application. Useful when the local app expects a specific hostname different from the public domain. For example, if the public domain is app.example.com but the local app only responds to localhost:
hostHeaderRewrite = "localhost"
Q: Does frp support IPv6?
Yes. Set bindAddr and localIP to IPv6 addresses:
# frps.toml
bindAddr = "::"
# frpc.toml
serverAddr = "2001:db8::1"
Q: Can I use frp without any authentication?
Technically yes — omitting [auth] requires no token. This is acceptable only on private, trusted networks. For any public-facing frps, always set auth.token.
Q: What environment variables does frp respect?
| Variable | Effect |
|---|---|
http_proxy |
Used as transport.proxyURL if not set in config |
QUIC_GO_DISABLE_ECN |
Disable ECN marking for QUIC transport |
Q: Can frp be used as an outbound HTTP/HTTPS proxy for browsing?
Yes. Use the http_proxy or socks5 plugin. Configure your browser or system proxy to point to frps-server:remotePort. All HTTP/HTTPS traffic routes through the machine running frpc.
Q: Where is full configuration reference and architecture documentation? This guide covers user-facing usage. See the Admin Guide for complete configuration reference, architecture internals, deployment, security hardening, monitoring, and operational procedures.
Q: Does frpc reconnect automatically if the connection to frps drops?
Yes. frpc automatically reconnects when the connection is lost. The loginFailExit setting only affects the very first connection attempt.
Q: How do I know if xtcp NAT hole punching succeeded? Check frpc logs on the visitor side:
hole punch successful— P2P connection is active, traffic flows directly.falling back to [visitor-name]— P2P failed, using the configured stcp fallback.- If no fallback is configured and hole punching fails, the connection will not be established.
Q: Can I run frps behind a load balancer?
Yes, with caveats. For raw TCP/UDP proxies, frps must be the public endpoint (or use a TCP pass-through load balancer). For HTTP proxies, frps can sit behind nginx or HAProxy if vhostHTTPPort traffic is forwarded. See the Admin Guide for deployment topology details.
Q: How do I migrate from INI to TOML config? frp does not provide an automatic migration tool. Key differences:
# Old INI frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
token = secret
[ssh]
type = tcp
local_port = 22
remote_port = 6000
# New TOML frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000
[auth]
token = "secret"
[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 6000
Key changes: snake_case → camelCase, [common] → top-level keys, each [section] proxy → [[proxies]] with explicit name = "...".
Q: How do I debug frp connection problems step by step?
- Validate configs:
./frps verify -c frps.tomland./frpc verify -c frpc.toml. - Set both to
log.level = "debug"temporarily. - Start frps first, watch logs.
- Start frpc, watch logs for the login handshake.
- Test the proxy from the public side:
curlortelnet frps-server remotePort. - Check both sides simultaneously using
tail -fon log files. - For TLS issues, use
openssl s_client -connect frps-server:7000to inspect the certificate chain.
Q: What is the --allow-unsafe flag?
It permits certain configuration options considered unsafe for production (such as configs that expose overly broad access). Do not use this in production environments.
Q: Does frp support HTTP/2 to the backend?
Yes, when using the https2http or https2https plugins with enableHTTP2 = true. The control channel between frpc and frps uses its own multiplexing (tcpMux), independent of HTTP/2 to backends.
Q: Can two frpc instances run on the same machine and both connect to frps? Yes. Each can use the same token for auth but must have unique proxy names. They operate independently.