← Back to DevBytes

FRP Reverse Proxy: Complete Setup and Configuration Guide

What is FRP?

FRP (Fast Reverse Proxy) is an open-source reverse proxy application written in Go that enables you to expose local servers behind NAT or firewalls to the public internet. It works by establishing long-lived tunnel connections between a publicly accessible server and a client machine sitting behind restrictive networks. FRP supports multiple protocols including TCP, HTTP, HTTPS, UDP, and even custom protocols through its flexible plugin system.

Unlike traditional reverse proxies like Nginx or HAProxy that sit at the edge of your infrastructure, FRP is specifically designed to penetrate NAT boundaries. It creates a secure tunnel from the inside out, allowing your local development server, IoT device, or internal enterprise service to be accessed from anywhere in the world without modifying firewall rules or router configurations.

Why FRP Matters

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

In modern development and operations workflows, the need to expose local services is ubiquitous. Developers need to share work-in-progress APIs with remote teammates, demonstrate web applications to clients without deploying to staging, or test webhook integrations from third-party services like Stripe or GitHub. Operations teams need secure remote access to internal dashboards, database admin panels, and SSH endpoints without opening inbound ports on production networks.

FRP solves these challenges elegantly. It requires zero changes to network infrastructure, works across all major operating systems, and provides enterprise-grade features like TLS encryption, authentication tokens, load balancing, and bandwidth compression. With a single static binary per platform and a straightforward TOML configuration, FRP has become the go-to tool for thousands of developers and DevOps engineers who need reliable, performant reverse tunneling.

Core Architecture Overview

FRP follows a client-server model with two distinct components that work in tandem. Understanding this architecture is essential before diving into configuration and deployment.

FRP Server (frps)

The FRP server is the publicly accessible component deployed on a machine with a public IP address — typically a cloud VPS from providers like DigitalOcean, Linode, AWS EC2, or any server with inbound internet connectivity. The server listens for incoming connections from FRP clients and also listens for requests from end users. When an end user connects to the server on a specific port, the server forwards that traffic through the established tunnel to the appropriate client.

The server configuration defines global settings like binding addresses, authentication requirements, dashboard availability, and port ranges for various proxy types. A minimal server configuration can be as simple as specifying a binding port, but production deployments typically include token-based authentication, TLS encryption, and monitoring dashboards.

FRP Client (frpc)

The FRP client runs on the machine behind NAT or firewall — your local development laptop, a Raspberry Pi in a home network, or a server inside a corporate LAN. The client initiates an outbound connection to the FRP server and maintains a persistent tunnel. It registers one or more proxy configurations that tell the server how to route incoming traffic back to local services.

Each proxy definition maps a remote port on the server to a local service running on the client machine. The client handles multiplexing multiple proxies over a single control connection and manages health checks, reconnection logic, and traffic forwarding. The client configuration is declarative — you specify what local services to expose and how they should be mapped on the server side.

Installation and Setup

Downloading FRP

FRP distributes precompiled binaries for Linux, macOS, and Windows across multiple architectures including amd64, arm64, and armv7. Download the latest release from the official GitHub repository at github.com/fatedier/frp. Each release includes both server and client binaries along with sample configuration files.

# On the server (public VPS), download and extract FRP
wget https://github.com/fatedier/frp/releases/download/v0.58.1/frp_0.58.1_linux_amd64.tar.gz
tar -xzf frp_0.58.1_linux_amd64.tar.gz
cd frp_0.58.1_linux_amd64/

# The archive contains:
# frps       - server binary
# frpc       - client binary
# frps.toml  - server configuration template
# frpc.toml  - client configuration template

# On the client (local machine behind NAT), download the same archive
# For macOS (Apple Silicon):
curl -LO https://github.com/fatedier/frp/releases/download/v0.58.1/frp_0.58.1_darwin_arm64.tar.gz
tar -xzf frp_0.58.1_darwin_arm64.tar.gz

Server Configuration (frps.toml)

Create the server configuration file on your public VPS. This file defines how the FRP server binds to network interfaces, authenticates clients, and serves the monitoring dashboard. The configuration uses TOML format, which is more readable and less error-prone than JSON or YAML for this type of structured configuration.

# /etc/frp/frps.toml
# FRP Server Configuration - Production Ready

# Bind the server to all interfaces on port 7000 for client connections
bindPort = 7000

# Optional: bind to a specific IP address
# bindAddr = "0.0.0.0"

# Authentication - REQUIRED for production
# Clients must provide this token to establish a connection
auth.method = "token"
auth.token = "your-secure-token-at-least-32-characters-long"

# Transport layer security
# Enable TLS for the communication channel between server and clients
transport.tls.enable = true
transport.tls.certFile = "/etc/frp/server.crt"
transport.tls.keyFile = "/etc/frp/server.key"

# Dashboard for monitoring active connections and proxies
# Accessible via browser at http://your-server-ip:7500
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "secure-dashboard-password"

# Allow privileged ports (ports below 1024) for proxies
# Requires running frps with appropriate system capabilities
allowPorts = [
  { start = 80, end = 80 },
  { start = 443, end = 443 },
  { start = 6000, end = 6100 }
]

# Maximum number of connections per client
maxPoolCount = 50

# Heartbeat configuration to detect dead clients
# Server sends heartbeat every 30 seconds, timeout after 90 seconds
heartbeatInterval = 30
heartbeatTimeout = 90

# Log configuration
log.to = "/var/log/frp/frps.log"
log.level = "info"
log.maxDays = 7

Start the FRP server using the configuration file. For production deployments, create a systemd service to ensure the server runs continuously and restarts on failure.

# Start the server directly for testing
./frps -c /etc/frp/frps.toml

# Create a systemd service for persistence
sudo tee /etc/systemd/system/frps.service << 'EOF'
[Unit]
Description=FRP Server Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=always
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable frps
sudo systemctl start frps
sudo systemctl status frps

Client Configuration (frpc.toml)

The client configuration resides on your local machine. It specifies the server address and authentication details, followed by one or more proxy definitions that map remote ports to local services. Each proxy gets a unique name used for identification in logs and on the dashboard.

# /etc/frp/frpc.toml
# FRP Client Configuration

# Connection to the FRP server
serverAddr = "203.0.113.50"   # Replace with your server's public IP
serverPort = 7000

# Authentication must match the server's auth.token
auth.method = "token"
auth.token = "your-secure-token-at-least-32-characters-long"

# TLS must be enabled if the server requires it
transport.tls.enable = true
# If using self-signed certs on the server, specify the CA
# transport.tls.caFile = "/etc/frp/ca.crt"

# Heartbeat configuration
heartbeatInterval = 30
heartbeatTimeout = 90

# Logging
log.to = "/var/log/frp/frpc.log"
log.level = "info"

# ---- PROXY DEFINITIONS ----

# Example 1: Expose a local HTTP development server
[[proxies]]
name = "web-app"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 8080

# Example 2: Expose local SSH on a custom remote port
[[proxies]]
name = "ssh-access"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6022

# Example 3: Expose a local HTTPS service
[[proxies]]
name = "secure-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8443
remotePort = 443

# Example 4: Expose a UDP-based service (like DNS or gaming)
[[proxies]]
name = "dns-resolver"
type = "udp"
localIP = "127.0.0.1"
localPort = 53
remotePort = 6053

Start the client and verify connectivity. The client logs will indicate successful tunnel establishment and proxy registration.

# Start the client
./frpc -c /etc/frp/frpc.toml

# Expected log output on successful connection:
# [INFO] [client] frpc started successfully
# [INFO] [client] [web-app] proxy added
# [INFO] [client] [ssh-access] proxy added
# [INFO] [client] [secure-api] proxy added
# [INFO] [client] [dns-resolver] proxy added
# [INFO] [client] start to connect to server...
# [INFO] [client] connected to server, start to register proxies...

Common Use Cases with Examples

Exposing a Local Web Server (HTTP)

The most frequent use case is sharing a development web server running on localhost. With the proxy configured, colleagues or webhook providers can reach your local server via the FRP server's public IP and the mapped remote port.

# Client-side proxy configuration for HTTP service
[[proxies]]
name = "nextjs-dev"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 8080

# After starting frpc, access the local Next.js app at:
# http://203.0.113.50:8080

# Test connectivity from any external machine:
curl -I http://203.0.113.50:8080

Exposing SSH Service (TCP)

Secure remote access to machines behind firewalls is a critical operational requirement. FRP tunnels SSH traffic transparently, allowing you to connect to a remote machine as if it were directly accessible.

# Client-side proxy configuration for SSH
[[proxies]]
name = "ssh-access"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6022

# From anywhere, connect via SSH through the FRP server:
ssh -o "Port=6022" user@203.0.113.50

# You can simplify this with an SSH config entry:
# ~/.ssh/config
Host my-remote-machine
    HostName 203.0.113.50
    Port 6022
    User remote-username
    IdentityFile ~/.ssh/id_ed25519

# Then connect simply with:
ssh my-remote-machine

Exposing HTTPS with TLS

When exposing production services, encrypting traffic end-to-end is non-negotiable. FRP supports TLS termination both at the proxy level and through the underlying transport. For HTTPS services, you can configure TLS directly on your local service and tunnel the encrypted traffic through FRP as raw TCP.

# Client-side proxy for an HTTPS service with TLS handled locally
[[proxies]]
name = "production-api"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8443    # Local service already configured with TLS certificates
remotePort = 443

# Your local service (e.g., Nginx, Caddy, or a Go app) handles TLS termination
# FRP tunnels the encrypted TCP stream transparently
# Users access: https://203.0.113.50 (standard HTTPS port)

# Verify the TLS handshake reaches your local service correctly:
openssl s_client -connect 203.0.113.50:443 -servername api.example.com

Dashboard and Monitoring

The FRP server includes a built-in web dashboard that provides real-time visibility into active connections, registered proxies, traffic statistics, and client health. This is invaluable for operational monitoring and debugging.

# Dashboard is configured in frps.toml:
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "secure-dashboard-password"

# Access the dashboard at:
# http://203.0.113.50:7500

# The dashboard displays:
# - Connected clients with IP addresses and connection duration
# - Each proxy with type, remote port, and traffic counters
# - Real-time bandwidth graphs
# - Client heartbeat status and last activity timestamps
# - Ability to force-close stale connections

# For production, consider protecting the dashboard with a TLS reverse proxy
# or restrict access via firewall rules to trusted IP ranges only

Load Balancing

FRP supports load balancing across multiple clients for high-traffic services. When multiple clients register proxies with the same group name, the server distributes incoming connections among them. This enables horizontal scaling of tunneled services across different machines.

# On multiple client machines, configure identical proxy group settings:
# Client A (machine-a behind NAT)
[[proxies]]
name = "web-cluster"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 8080

# Add load balancing metadata
[proxies.metadata]
group = "web-backend"
groupKey = "shared-secret-key"

# Client B (machine-b behind different NAT)
[[proxies]]
name = "web-cluster-2"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000
remotePort = 8080

[proxies.metadata]
group = "web-backend"
groupKey = "shared-secret-key"

# The FRP server now distributes requests across both clients
# Supports round-robin by default; weighted and least-connection strategies available

Custom Domains and Subdomains

FRP's HTTP/HTTPS proxy type provides advanced routing based on the Host header, allowing you to expose multiple local services on a single remote port using virtual hosting. This is similar to how Nginx or Caddy handle name-based virtual hosts.

# Server configuration - enable virtual hosting on port 80
# frps.toml
vhostHTTPPort = 80

# Client A - expose a blog on a custom domain
[[proxies]]
name = "blog-service"
type = "http"
localIP = "127.0.0.1"
localPort = 4000
customDomains = ["blog.example.com"]
subdomain = "blog"

# Client B - expose an admin panel on a subdomain
[[proxies]]
name = "admin-panel"
type = "http"
localIP = "127.0.0.1"
localPort = 5000
customDomains = ["admin.example.com"]

# Client C - expose a docs site
[[proxies]]
name = "docs-site"
type = "http"
localIP = "127.0.0.1"
localPort = 6000
subdomain = "docs"

# DNS configuration required:
# Point *.example.com or specific subdomains to the FRP server's IP
# blog.example.com   A   203.0.113.50
# admin.example.com  A   203.0.113.50
# docs.example.com   A   203.0.113.50

# All services are accessible on port 80 with appropriate Host headers
curl -H "Host: blog.example.com" http://203.0.113.50

Best Practices

Security Considerations

Security is paramount when exposing internal services to the public internet. FRP provides several mechanisms to harden your deployment, but they must be configured correctly. Never expose services without authentication and encryption in production environments.

# 1. ALWAYS use token-based authentication
auth.method = "token"
auth.token = "generate-a-strong-random-token-using-this-command"
# Generate a strong token:
# openssl rand -base64 32

# 2. ALWAYS enable TLS for the control channel
transport.tls.enable = true
# Use Let's Encrypt certificates in production
# transport.tls.certFile = "/etc/letsencrypt/live/example.com/fullchain.pem"
# transport.tls.keyFile = "/etc/letsencrypt/live/example.com/privkey.pem"

# 3. Restrict which ports can be used for proxies
allowPorts = [
  { start = 8080, end = 8090 },   # Only allow specific ranges
  { start = 443, end = 443 }
]

# 4. Use firewall rules to restrict access to the FRP server
# Only allow client connections from expected IP ranges if possible
# iptables -A INPUT -p tcp --dport 7000 -s 192.168.0.0/24 -j ACCEPT
# iptables -A INPUT -p tcp --dport 7000 -j DROP

# 5. Rotate authentication tokens periodically
# 6. Monitor the dashboard for unauthorized proxy registrations
# 7. Use separate tokens for different clients when possible

Performance Tuning

FRP is built on Go's efficient networking primitives and performs well out of the box, but several tuning options can optimize throughput and latency for demanding workloads.

# Server-side performance tuning in frps.toml

# Increase connection pool size for high-concurrency scenarios
maxPoolCount = 200

# Enable TCP stream multiplexing to reduce connection overhead
# Multiple logical streams share a single TCP connection
transport.tcpMux = true

# Adjust heartbeat intervals for latency-sensitive applications
heartbeatInterval = 10    # More frequent heartbeats detect failures faster
heartbeatTimeout = 30     # Quicker failover

# Client-side performance tuning in frpc.toml

# Enable compression for bandwidth-constrained environments
# Particularly useful for HTTP traffic and database replication
transport.useEncryption = true
transport.useCompression = true

# Increase the number of concurrent proxy connections
maxPoolCount = 100

# For UDP proxies, adjust buffer sizes
[[proxies]]
name = "game-server"
type = "udp"
localIP = "127.0.0.1"
localPort = 27015
remotePort = 27015
# Larger UDP buffers reduce packet loss under load

High Availability Setup

For production deployments that require high availability, deploy multiple FRP servers behind a load balancer and configure clients with failover capabilities. This ensures tunnel continuity even if one server instance fails.

# High Availability Architecture:

# 1. Deploy 2+ FRP servers in different availability zones
# Server A: 203.0.113.50
# Server B: 203.0.113.51

# 2. Use a TCP load balancer (HAProxy, AWS NLB) to distribute
#    client connections across FRP servers
#    Load balancer listens on port 7000, forwards to backend servers

# 3. Client configuration with multiple server endpoints
# frpc.toml - the client will try servers in order
serverAddr = "203.0.113.50"    # Primary
# If primary fails, manually switch or use a floating IP

# 4. For automatic failover, use a DNS-based approach:
#    Create an A record pointing to both server IPs
#    frp.example.com -> 203.0.113.50, 203.0.113.51

# 5. Alternatively, run multiple frpc instances with different server addresses
#    and use a local reverse proxy (Nginx) to route to active tunnels

# 6. For the dashboard, deploy a separate HA setup:
#    - Use a reverse proxy with sticky sessions
#    - Store session data in Redis for dashboard authentication

Troubleshooting Common Issues

When FRP connections fail or traffic doesn't flow as expected, systematic troubleshooting quickly identifies the root cause. The most common issues fall into a few categories that are straightforward to diagnose and resolve.

# Issue 1: Client cannot connect to server
# Symptoms: "connection refused" or "i/o timeout" in client logs

# Diagnostic steps:
# - Verify the server is running: systemctl status frps
# - Test port reachability: nc -zv server-ip 7000
# - Check firewall rules on the server: iptables -L -n
# - Ensure bindAddr is not restricted to an unreachable interface
# - Verify TLS certificates are valid if TLS is enabled

# Issue 2: Authentication failure
# Symptoms: "auth failed" or "token mismatch"

# Diagnostic steps:
# - Confirm auth.token matches EXACTLY between server and client
# - Check for trailing whitespace in configuration files
# - Verify the auth.method is consistent (both using "token")
# - Regenerate and redistribute tokens if necessary

# Issue 3: Proxy not working (port unreachable)
# Symptoms: connection to remotePort times out

# Diagnostic steps:
# - Check the dashboard to confirm the proxy is registered
# - Verify localPort is actually listening: ss -tlnp | grep localPort
# - Ensure localIP is correct (127.0.0.1 vs 0.0.0.0)
# - Test the local service directly: curl http://127.0.0.1:localPort
# - Check allowPorts on the server includes the remotePort

# Issue 4: Intermittent disconnections
# Symptoms: tunnels drop and reconnect periodically

# Diagnostic steps:
# - Adjust heartbeatInterval and heartbeatTimeout for your network
# - Check for NAT timeout on the client's network (common at 5-30 minutes)
# - Enable TCP keepalive on the underlying connection
# - Consider using a VPN or more stable network if possible

Conclusion

FRP has earned its place as an essential tool in the modern developer's toolkit by solving a deceptively simple but pervasive problem: making local services accessible across network boundaries without infrastructure changes. Its Go-based implementation delivers exceptional performance, its TOML configuration strikes the right balance between simplicity and expressiveness, and its feature set — spanning TCP, HTTP, HTTPS, UDP, load balancing, virtual hosting, and comprehensive monitoring — rivals commercial tunneling solutions while remaining completely open source.

Whether you're a solo developer sharing a local prototype with a remote client, a DevOps engineer managing a fleet of IoT devices behind cellular NAT, or an enterprise architect designing a secure remote access strategy, FRP provides a battle-tested, production-ready foundation. By following the configuration patterns and security practices outlined in this guide — always using token authentication, enabling TLS, restricting port ranges, and monitoring via the dashboard — you can deploy FRP with confidence in any environment from development laptops to mission-critical production infrastructure.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles