Your home server, NAS or development machine sits behind a router, perhaps behind carrier-grade NAT, with no public IP. frp (fast reverse proxy) solves this with a VPS you control. The VPS runs the frp server (frps) with a public IP. The machine at home runs the frp client (frpc) and keeps an outbound connection open. Traffic to the VPS is forwarded through that connection to your local service. It’s a self-hosted alternative to ngrok or Cloudflare Tunnel.
Internet ──▶ VPS (frps, public IP) ◀── outbound connection ── home machine (frpc) ──▶ localhost:8080
When to use frp
- Self-hosting at home (Home Assistant, Jellyfin, Nextcloud) without a public IP or port forwarding.
- Exposing a development server or webhook receiver.
- SSH or RDP into machines behind NAT, through your own server.
- You want no third party in the middle. Traffic passes only through your VPS.
A small VPS is plenty: frps uses very little CPU and RAM. Bandwidth through the tunnel counts against the VPS’s traffic allowance, and latency is the home → VPS → visitor path, so pick a VPS location near you and your users. See choosing a VPS location.
Download
frp is a single Go binary for each side. Get the latest release for your architecture from the project’s GitHub releases page:
wget https://github.com/fatedier/frp/releases/download/v0.61.0/frp_0.61.0_linux_amd64.tar.gz
tar xzf frp_*_linux_amd64.tar.gz && cd frp_*_linux_amd64
sudo install -m 755 frps frpc /usr/local/bin/
(Use the current version number.)
Server side (VPS): frps
/etc/frp/frps.toml:
bindPort = 7000
# HTTP(S) virtual hosts
vhostHTTPPort = 80
vhostHTTPSPort = 443
auth.method = "token"
auth.token = "a-long-random-secret"
# restrict which remote ports clients may open for TCP proxies
allowPorts = [ { start = 6000, end = 6100 } ]
# optional dashboard, bound to localhost only
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "another-secret"
systemd unit:
# /etc/systemd/system/frps.service
[Unit]
Description=frp server
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
DynamicUser=yes
AmbientCapabilities=CAP_NET_BIND_SERVICE
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now frps
sudo ufw allow 7000/tcp
sudo ufw allow 80,443/tcp
sudo ufw allow 6000:6100/tcp # only if you use TCP proxies
Generate tokens with openssl rand -hex 32. If Nginx already uses ports 80/443 on the VPS, either let Nginx proxy to frps’s vhost ports (e.g. 8080), or run frps on a separate VPS.
Client side (home machine): frpc
/etc/frp/frpc.toml:
serverAddr = "203.0.113.10" # your VPS
serverPort = 7000
auth.method = "token"
auth.token = "a-long-random-secret"
transport.tls.enable = true
[[proxies]]
name = "homeassistant"
type = "http"
localIP = "127.0.0.1"
localPort = 8123
customDomains = ["ha.example.com"]
[[proxies]]
name = "home-ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6022
Point the DNS A record for ha.example.com at the VPS IP. Run frpc as a systemd service, like frps. Then:
http://ha.example.comreaches Home Assistant at home;ssh -p 6022 [email protected]reaches the home machine’s SSH.
HTTPS for your services
Two common approaches:
- Terminate TLS on the VPS. Run Nginx or Caddy on the VPS with Let’s Encrypt certificates, and proxy to frps’s HTTP vhost port. This gives automatic certificates and central logging. See Caddy reverse proxy.
- Use the
httpsproxy type with TLS terminated at home (the certificate lives on the home machine). frps routes by SNI.
Also keep transport.tls.enable = true (the default in recent versions), so the frpc ↔ frps control and data channel is encrypted, too.
Security
You’re publishing home services on the internet. Treat them like any public server:
- Strong
auth.token, and TLS between client and server. - Limit
allowPorts. Don’t let clients open arbitrary ports on your VPS. - Protect apps with authentication. For admin tools, add basic auth or SSO on the VPS proxy, or use frp’s
stcp/xtcp(secret) types, which require a visitor-side frpc with the shared key and aren’t publicly exposed at all. - Keep frp and the exposed apps updated.
- Don’t expose SSH with password authentication. Use keys. See key-only SSH.
frp vs alternatives
| Tool | Infrastructure | Notes |
|---|---|---|
| frp | Your own VPS | Full control, any TCP/UDP/HTTP |
| Cloudflare Tunnel | Cloudflare | No VPS needed, adds WAF and Access. See Cloudflare Tunnel |
| Tailscale / WireGuard | Mesh / your VPS | Private access rather than public exposure. See WireGuard |
| ngrok and similar | Hosted service | Quickest for development. See ngrok alternatives |
| SSH reverse tunnel | Any SSH server | ssh -R, simple for one port. See SSH tunnels |
Quick reference
| Task | Command / setting |
|---|---|
| Server config | /etc/frp/frps.toml (bindPort, auth.token) |
| Client config | /etc/frp/frpc.toml (serverAddr, [[proxies]]) |
| HTTP by domain | type = "http", customDomains |
| Raw TCP | type = "tcp", remotePort |
| Private only | type = "stcp" + visitor |
| Logs | journalctl -u frps / -u frpc |
Related: port forwarding explained and NAT explained.