How FRP works
The public server runs frps. The home server runs frpc and opens an outbound connection to it.
Internet → public server (frps) → encrypted/authenticated FRP session → home client (frpc) → local service
1. Prepare the public server
Use a small Linux server with a public IP. Open only the ports you need in both the cloud firewall and the server firewall:
- The FRP control port, such as
7000/tcp. - Each public service port, such as
18096/tcpfor a web service. - UDP ports only when the service actually uses UDP.
Download the matching FRP release for the server architecture from the official release page. Keep frps on the public server and frpc on the home server.
2. Configure frps
Create /etc/frp/frps.toml on the public server:
bindPort = 7000
auth.method = "token"
auth.token = "REPLACE_WITH_A_LONG_RANDOM_TOKEN"
Generate a token rather than inventing a short password:
openssl rand -hex 32
Install the server binary and lock down the configuration:
sudo install -m 0755 frps /usr/local/bin/frps
sudo mkdir -p /etc/frp
sudo chmod 700 /etc/frp
sudo chmod 600 /etc/frp/frps.toml
3. Run frps with systemd
Create /etc/systemd/system/frps.service:
[Unit]
Description=FRP server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Enable it and verify the listening port:
sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo systemctl status frps --no-pager
sudo ss -tulpn | grep 7000
4. Configure frpc at home
Create /etc/frp/frpc.toml on the home server. This example sends public port 18096 to a web service listening on the home server at 8096.
serverAddr = "PUBLIC_SERVER_IP"
serverPort = 7000
auth.method = "token"
auth.token = "THE_SAME_LONG_TOKEN"
[[proxies]]
name = "jellyfin-web"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8096
remotePort = 18096
Use 127.0.0.1 when the service is on the same home machine. Use a LAN address only when the FRP client must reach another device.
5. Run frpc with systemd
Create /etc/systemd/system/frpc.service:
[Unit]
Description=FRP client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=always
RestartSec=5
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Start it and watch the logs:
sudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo journalctl -u frpc -n 80 --no-pager
A successful client connection should show that the proxy was added. On the public server, confirm the remote port is listening:
sudo ss -tulpn | grep 18096
curl -I http://127.0.0.1:18096
6. Put Caddy in front of the tunnel
Instead of asking users to visit a high-numbered port, point a domain at the public server and let Caddy handle HTTPS:
jellyfin.example.com {
reverse_proxy 127.0.0.1:18096
}
Then validate and reload:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://jellyfin.example.com
7. Security rules that matter
- Use a long random FRP token and protect both TOML files with mode
600. - Do not publish database ports, SSH, admin panels, or Docker APIs unless you have a specific security plan.
- Use HTTPS for web applications and strong application-level login credentials.
- Restrict the public firewall to the control port and the exact service ports you need.
- Keep FRP updated on both ends and use the same compatible release.
8. Troubleshooting order
- Can the public server reach the home service through the tunnel with
curl 127.0.0.1:REMOTE_PORT? - Is
frpcconnected and is the proxy listed in its logs? - Is the cloud firewall open for the remote port?
- Is the public server’s local firewall open?
- Does Caddy point to the remote FRP port rather than the home service’s original port?