Use FRP when you cannot port-forward

Use a public relay server when your home connection cannot accept forwarded ports.

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
Good use case: your ISP uses carrier-grade NAT, the router cannot forward ports, or you do not control the upstream network.

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/tcp for 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.
A tunnel bypasses the home router’s inbound block. That is the point, but it also means the public service must be treated like any other internet-facing server.

8. Troubleshooting order

  1. Can the public server reach the home service through the tunnel with curl 127.0.0.1:REMOTE_PORT?
  2. Is frpc connected and is the proxy listed in its logs?
  3. Is the cloud firewall open for the remote port?
  4. Is the public server’s local firewall open?
  5. Does Caddy point to the remote FRP port rather than the home service’s original port?

Official source