Why frp is the reverse proxy you should know about
You’re building a service locally. You need to expose it to the internet. Maybe for a webhook test. Maybe for a demo. Maybe for remote access to your home lab. The problem? You’re behind a NAT or firewall.
frp (Fast Reverse Proxy) solves this. It’s written in Go, it’s fast, and it handles everything from simple HTTP proxies to P2P connections.
What makes frp different?
Unlike services like ngrok, frp is self-hosted. You control the server. You control the data. No third-party involved.
The architecture is simple. You run frps (server) on a machine with a public IP. You run frpc (client) on your local machine. The client establishes a connection through your firewall. Traffic flows through the tunnel.
What’s impressive is the protocol support. TCP, UDP, HTTP, HTTPS, STCP (secret TCP), and even P2P connections. All from one tool.
Setting up a basic HTTP proxy
Let’s expose a local web server. First, the server configuration (frps.toml):
bindPort = 7000
vhostHTTPPort = 80
Start the server:
./frps -c frps.toml
Now the client configuration (frpc.toml):
serverAddr = "your-public-server.com"
serverPort = 7000
[[proxies]]
name = "web"
type = "http"
localPort = 3000
customDomains = ["demo.your-domain.com"]
Start the client:
./frpc -c frpc.toml
Your local port 3000 is now accessible at demo.your-domain.com. Simple.
Performance tuning for production
frp performs well out of the box. But there are tweaks worth knowing.
Connection pooling
Opening new connections is expensive. frp can maintain a pool of connections ready to use:
serverAddr = "your-public-server.com"
serverPort = 7000
transport.poolCount = 5
[[proxies]]
name = "web"
type = "http"
localPort = 3000
customDomains = ["demo.your-domain.com"]
The poolCount setting keeps 5 connections warm. Requests get served faster because the handshake is already done.
TCP Mux
Multiple proxies can share a single TCP connection. This reduces overhead:
serverAddr = "your-public-server.com"
serverPort = 7000
transport.tcpMux = true
[[proxies]]
name = "web"
type = "http"
localPort = 3000
customDomains = ["demo.your-domain.com"]
[[proxies]]
name = "api"
type = "http"
localPort = 8080
customDomains = ["api.your-domain.com"]
Both proxies now share one underlying connection. Less memory. Fewer file descriptors.
Bandwidth limiting
Production environments often need traffic controls. frp supports bandwidth limits per proxy:
[[proxies]]
name = "web"
type = "http"
localPort = 3000
customDomains = ["demo.your-domain.com"]
transport.bandwidthLimit = "10MB"
transport.bandwidthLimitMode = "server"
This caps the proxy at 10MB/s. The bandwidthLimitMode can be client or server, depending on where you want to enforce the limit.
P2P mode for low latency
Normal mode routes all traffic through your server. P2P mode connects clients directly:
[[proxies]]
name = "p2p-ssh"
type = "xtcp"
secretKey = "your-secret-key"
localIP = "127.0.0.1"
localPort = 22
The visitor configuration:
[[visitors]]
name = "p2p-ssh-visitor"
type = "xtcp"
serverName = "p2p-ssh"
secretKey = "your-secret-key"
bindAddr = "127.0.0.1"
bindPort = 2222
Traffic now flows directly between peers. The server only handles the initial handshake. This is great when both machines can establish a direct connection.
Monitoring your setup
Understanding what’s happening is crucial. If you’re building Go services that need monitoring, check out how to use context properly - it’s essential for request tracing and cancellation.
frp exposes a dashboard. Enable it in the server config:
bindPort = 7000
webServer.addr = "0.0.0.0"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "secure-password"
Access it at http://your-server:7500. You’ll see connected clients, active proxies, and traffic stats.
When to use frp
frp shines when you need:
- Development environments that need public access
- Home lab services exposed securely
- IoT devices behind restrictive networks
- Backup access to internal services
It’s not a replacement for proper infrastructure. For production services, consider a real load balancer. But for exposing services through a NAT or firewall? frp does the job well.
The Go implementation keeps it lightweight and fast. One binary. No runtime dependencies. That’s the Go way.