# frp (Fast Reverse Proxy) — User Guide

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](#1-quick-start)
2. [Installation](#2-installation)
3. [Configuration](#3-configuration)
4. [Core Usage](#4-core-usage)
5. [Common Tasks](#5-common-tasks)
6. [Troubleshooting](#6-troubleshooting)
7. [FAQ](#7-faq)

---

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

```toml
bindPort = 7000
```

Start the server:

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

```toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
```

Start the client:

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

```bash
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.token` in production. The minimal config above has no authentication — anyone can connect to your frps and register proxies. See [Authentication](#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](https://github.com/fatedier/frp/releases).

=== "Linux (amd64)"

```bash
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)"

```bash
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)"

```bash
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)"

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

```bash
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:**

```bash
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:**

```bash
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 host` for frpc when it needs to reach services on `127.0.0.1` of the Docker host. Without it, `localIP = "127.0.0.1"` points inside the container, not the host.

### 2.4 Verify Installation

```bash
./frps -v
./frpc -v
```

### 2.5 Validate a Config File

Before starting, verify your config syntax:

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

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

```bash
sudo systemctl enable --now frps
sudo systemctl status frps
```

**Linux (systemd) — frpc:**

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

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

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

```bash
./frps -c frps.toml --strict_config
./frpc -c frpc.toml --strict_config
```

> [!WARNING]
> `--strict_config` will 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

```toml
bindPort = 7000
```

#### Full reference

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

```toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "my-proxy"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8080
remotePort = 9090
```

#### Full reference

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

```toml
# frps.toml
[auth]
method = "token"
token = "strong-random-secret"
```

```toml
# frpc.toml
[auth]
method = "token"
token = "strong-random-secret"
```

**Token from file or command:**

```toml
# frpc.toml
[auth]
method = "token"
[auth.tokenSource]
type = "file"
path = "/run/secrets/frp-token"
```

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

```toml
# frpc.toml
[auth]
method = "oidc"
[auth.oidc]
clientID = "frp-client"
clientSecret = "secret"
audience = "frp-server"
scope = "openid"
tokenEndpointURL = "https://idp.example.com/oauth/token"
```

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

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

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

```toml
# 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 = false` on the server while clients use TLS has produced unexpected behavior (GitHub issue #5131). If you want plaintext connections, explicitly set `tls.enable = false` on 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 |

```toml
# frpc.toml — use KCP
[transport]
protocol = "kcp"
```

```toml
# frps.toml — open a KCP port
kcpBindPort = 7000
```

For QUIC:

```toml
# frpc.toml
[transport]
protocol = "quic"
```

```toml
# frps.toml
quicBindPort = 7002
```

QUIC defaults: keepalivePeriod=10s, maxIdleTimeout=30s, maxIncomingStreams=100000.

> [!TIP]
> Set `QUIC_GO_DISABLE_ECN=1` in 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:

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

```toml
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000
```

Connect:

```bash
ssh -p 6000 user@frps-server
```

> [!TIP]
> You can expose a database (e.g., PostgreSQL on port 5432) the same way — just change `localPort` and `remotePort`. For maximum security, use `stcp` instead of `tcp` to 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`.

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

Test:

```bash
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 = 1500` matches 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:**

```toml
bindPort = 7000
vhostHTTPPort = 80
```

**frpc.toml:**

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

```toml
# frps.toml
subdomainHost = "example.com"
```

```toml
# frpc.toml
[[proxies]]
name = "app"
type = "http"
localPort = 3000
subdomain = "myapp"  # accessible at myapp.example.com
```

**HTTP proxy advanced features:**

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

```toml
# frps.toml
vhostHTTPSPort = 443
```

```toml
# 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](#47-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.

```toml
# frps.toml
tcpmuxHTTPConnectPort = 443
```

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

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

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

```bash
ssh -p 6000 user@127.0.0.1
```

#### sudp — Secret UDP

Same pattern as stcp, for UDP services:

**frpc.toml (service side):**

```toml
[[proxies]]
name = "private-dns"
type = "sudp"
localIP = "127.0.0.1"
localPort = 53
secretKey = "shared-secret"
```

**frpc.toml (visitor side):**

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

```toml
[[proxies]]
name = "p2p-ssh"
type = "xtcp"
localIP = "127.0.0.1"
localPort = 22
secretKey = "shared-secret"
allowUsers = ["visitor-name"]
```

**frpc.toml (visitor side):**

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

```toml
[[proxies]]
name = "http-fwd"
type = "tcp"
remotePort = 8888
[proxies.plugin]
type = "http_proxy"
httpUser = "proxyuser"
httpPassword = "proxypass"
```

Use it:

```bash
curl -x http://proxyuser:proxypass@frps-server:8888 http://example.com
```

#### socks5 — SOCKS5 Proxy

```toml
[[proxies]]
name = "socks5-fwd"
type = "tcp"
remotePort = 1080
[proxies.plugin]
type = "socks5"
username = "user"
password = "pass"
```

Use it with any SOCKS5-compatible client:

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

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

```toml
[[proxies]]
name = "docker-sock"
type = "tcp"
remotePort = 2375
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/var/run/docker.sock"
```

> [!WARNING]
> Exposing `/var/run/docker.sock` publicly gives full Docker control to anyone who can connect. Always protect this with an `auth.token` and consider using `stcp` instead of `tcp` to avoid any public port exposure.

#### http2https — HTTP → HTTPS

Convert incoming plain HTTP to HTTPS for a local service:

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

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

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

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

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

```toml
[[proxies]]
name = "vnet-proxy"
type = "xtcp"
localIP = "127.0.0.1"
localPort = 0
[proxies.plugin]
type = "virtual_net"
```

> [!NOTE]
> `virtual_net` is 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:

```toml
# 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 to `127.0.0.1` and access via SSH tunnel: `ssh -L 7500:localhost:7500 root@frps-server`. Then open `http://localhost:7500`.

#### frpc Admin API

frpc exposes a local HTTP API for live management:

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

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

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

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

```toml
# 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`, and `tcpmux` proxy types. All proxies in a group must share the same `groupKey`.

### 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:**

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

```bash
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:**

```bash
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 `ssh` command, 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

```toml
# frps.toml (on public server)
bindPort = 7000
[auth]
token = "my-secret"
```

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

```bash
ssh -p 2222 [email removed]
```

---

#### Recipe 2: Expose Multiple Web Apps on One Server

```toml
# frps.toml
bindPort = 7000
vhostHTTPPort = 80
subdomainHost = "frp.example.com"
[auth]
token = "my-secret"
```

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

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

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

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

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

```toml
[[proxies]]
name = "pihole"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 5353
```

```bash
dig @my-vps.example.com -p 5353 google.com
```

---

#### Recipe 6: Access Home Network via SOCKS5 from Anywhere

```toml
# frpc.toml (on home router / server)
[[proxies]]
name = "socks5-home"
type = "tcp"
remotePort = 1080
[proxies.plugin]
type = "socks5"
username = "homeuser"
password = "homepass"
```

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

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

```toml
# On the Docker host
[[proxies]]
name = "docker"
type = "stcp"
secretKey = "docker-secret"
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/var/run/docker.sock"
```

```toml
# On the CI machine (visitor)
[[visitors]]
name = "docker-visitor"
type = "stcp"
serverName = "docker"
secretKey = "docker-secret"
bindAddr = "127.0.0.1"
bindPort = 2375
```

```bash
DOCKER_HOST=tcp://127.0.0.1:2375 docker ps
```

---

#### Recipe 9: Resilient P2P Tunnel with Automatic Fallback

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

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

```toml
# frps.toml
vhostHTTPSPort = 443
```

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

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

```toml
# frps.toml
kcpBindPort = 7001
```

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

```toml
[[proxies]]
name = "throttled-api"
type = "tcp"
localPort = 8080
remotePort = 9090
[proxies.transport]
bandwidthLimit = "500KB"
bandwidthLimitMode = "client"
```

---

#### Recipe 15: Multiple Proxies with Health Checks

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

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

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

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

```toml
# frpc.toml — when corporate firewall only allows HTTP/HTTPS
serverAddr = "my-vps.example.com"
serverPort = 80
[transport]
protocol = "websocket"
```

For HTTPS-only environments:

```toml
serverPort = 443
[transport]
protocol = "wss"
```

---

#### Recipe 20: Monitor All Connections via frps Dashboard Securely

```toml
# frps.toml — bind dashboard to localhost only
[webServer]
addr = "127.0.0.1"
port = 7500
user = "admin"
password = "strongadminpassword"
```

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

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

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

```toml
# frps.toml
maxPortsPerClient = 5
```

Each authenticated client can register at most 5 proxies.

---

#### Recipe 24: Load-Balanced HTTP with Health Checks

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

```toml
[[proxies]]
name = "jupyter"
type = "tcp"
remotePort = 8888
[proxies.plugin]
type = "http_proxy"
httpUser = "labuser"
httpPassword = "labpass"
```

Or directly (if Jupyter has its own auth):

```toml
[[proxies]]
name = "jupyter"
type = "tcp"
localPort = 8888
remotePort = 8888
```

---

#### Recipe 26: Route HTTP Traffic by Path

```toml
[[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:**
1. Verify frps is running: `systemctl status frps` or check the frps process.
2. Check the port is listening: `ss -tlnp | grep 7000` (or `netstat -an | grep 7000`).
3. Check firewall: `ufw status` or cloud security group rules — port 7000/tcp must be open inbound on the frps host.
4. Confirm `serverAddr` and `serverPort` in `frpc.toml` are 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`:

```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:**
1. Compare `[auth] token` values in both `frps.toml` and `frpc.toml` — they must be identical, including whitespace and case.
2. If using `tokenSource`, verify the file or command returns the exact expected value.
3. 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:**
1. Find the conflicting process: `ss -tlnp | grep :PORT` or `lsof -i :PORT`.
2. Either stop the conflicting process or choose a different `remotePort`.
3. Check `allowPorts` on 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:**
1. Set `transport.tls.trustedCaFile` to the CA that signed the server cert.
2. Set `transport.tls.serverName` to 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:**
1. Check frps logs for more detail.
2. Verify tokens match on both sides.
3. Ensure both `frps` and `frpc` are the same version (`./frps -v` and `./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:**

```bash
./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:**
1. Confirm DNS for the domain points to the frps server's IP.
2. Check that `customDomains` in `frpc.toml` matches the requested domain exactly (including `www.` prefix if applicable).
3. If using subdomains, confirm `subdomainHost` is set in `frps.toml` and the subdomain matches.

---

### Error: HTTP proxy returns `502 Bad Gateway`

**Cause:** frpc registered successfully but cannot reach the local service at `localIP:localPort`.

**Solution:**
1. Verify the local service is running: `curl http://127.0.0.1:localPort/`.
2. Check `localIP` — in Docker, `127.0.0.1` inside the frpc container is the container itself, not the Docker host. Use the host's container IP or `--network host`.
3. If a health check is configured with `maxFailed = 1` and 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:**
1. Use the `https2http` plugin on frpc with a real certificate to terminate TLS properly.
2. Ensure `customDomains` matches the exact domain in the certificate.
3. 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:**
1. Verify credentials are correct in `frpc.toml`.
2. Clear browser saved credentials and try again.
3. 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:

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

```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:**
1. Verify `natHoleStunServer` is reachable from both frpc instances: `nc -u stun.easyvoip.com 3478`.
2. Configure `fallbackTo` pointing to an stcp visitor as fallback.
3. 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:**
1. Reduce `transport.poolCount` (default is 1 — increase only when needed).
2. Increase `intervalSeconds` for 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:**
1. With `transport.tcpMux = true` (default), lower `tcpMuxKeepaliveInterval`:

```toml
[transport]
tcpMuxKeepaliveInterval = 15
```

2. If `tcpMux = false`, set `heartbeatInterval = 30` and `heartbeatTimeout = 90` in frpc.
3. 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:**
1. Check actual error in frpc logs — it's usually an auth failure or network issue.
2. 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:

```bash
export QUIC_GO_DISABLE_ECN=1
./frpc -c frpc.toml
```

Or in the systemd unit:

```ini
[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:**
1. Add your public key to the `authorizedKeysFile` path configured in frps.
2. Verify format: one `ssh-rsa` or `ssh-ed25519` entry per line, same format as `~/.ssh/authorized_keys`.
3. 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:**
1. Check `allowPorts` in `frps.toml`.
2. 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`:

```bash
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?**

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

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

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

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

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

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

```toml
hostHeaderRewrite = "localhost"
```

---

**Q: Does frp support IPv6?**
Yes. Set `bindAddr` and `localIP` to IPv6 addresses:

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

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

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

1. Validate configs: `./frps verify -c frps.toml` and `./frpc verify -c frpc.toml`.
2. Set both to `log.level = "debug"` temporarily.
3. Start frps first, watch logs.
4. Start frpc, watch logs for the login handshake.
5. Test the proxy from the public side: `curl` or `telnet frps-server remotePort`.
6. Check both sides simultaneously using `tail -f` on log files.
7. For TLS issues, use `openssl s_client -connect frps-server:7000` to 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.

---

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