What it is
There is no public IP at home, and the ISP won't provide one, but I want to be able to open services such as Navidrome, PhotoPrism, Memos, and Homer whenever I'm outside. This project uses frp to add a VPS hop in the middle and map internal services to public domain names: the VPS runs frps, and the Debian 13 machine at home, xiejun-web (192.168.50.164), runs frpc. The configuration files are located at projects/vps/frp/frps.toml and acme-frpc.toml, respectively. In addition, a SakuraFrp container natfrp-service is attached as a backup channel.
Why this approach
Doing port mapping directly on the router has two problems: first, there is no public IP; second, exposing services directly to the public internet doesn't feel safe. The advantage of frp is that inbound traffic enters through only one port on the VPS, while the home side actively connects outward, so NAT and dynamic IPs don't matter. In front, another layer of Nginx Proxy Manager handles domain routing and certificates; externally, only the domain name matters, not which container or port is behind it.
Current status
Running and in daily use. HTTP services are accessed directly via domain names; for Minecraft Bedrock Edition, the port is 19132/udp, not the Java Edition's 25565, so the protocol must be selected correctly when mapping. The Windows machine (192.168.50.155) currently only uses the LAN; its Tailscale network interface has not yet joined the tailnet, so it is not part of this tunnel for now.
Features
- VPS 自建 frps + 家里 frpc,绕过无公网 IP 的限制
- 用 NPM 统一做域名路由和证书,后端容器端口不外露
- acme 配置独立维护(acme-frpc.toml),证书走 SNI 区分域名
- SakuraFrp(natfrp-service 容器)作为备用隧道通道
- 覆盖 HTTP 与 UDP 服务,Minecraft 基岩版 19132/udp 也在范围内
Stack: frp · SakuraFrp · Nginx Proxy Manager · Docker · Debian · TLS/SNI · Tailscale
Deployment Steps
1. Install frps on the VPS
Pull the frp release package for the corresponding architecture, and write frps.toml: bindPort is the port that frpc connects to, and you generate a long random value yourself for auth.token. If you need to route by domain, also enable vhostHTTPPort / vhostHTTPSPort. Manage it with systemd so it starts automatically on boot.
2. Run frpc at home
Place acme-frpc.toml on xiejun-web: set serverAddr to the VPS, and make auth.token match frps. Write one proxy section for each service, set localIP to 192.168.50.164, and point localPort to NPM's 80/443, or directly to the container port.
3. Certificates
Issue certificates on the VPS side with acme, either via TLS-ALPN or DNS; mount the certificates on frps's vhostHTTPSPort. Multiple domains are distinguished by SNI, so you do not need to open a separate port for each domain.
4. Add rules in Nginx Proxy Manager
Open the NPM panel and add Proxy Hosts: domain → container name plus port, for example one each for navidrome, photoprism, and memos; enable SSL and force HTTPS. This way the tunnel only needs one entry to NPM, and adding a new service only requires changing NPM, without touching the frp configuration.
5. Verification
First use curl -I https://your-domain to check the response headers, then actually open it once from the external network (phone 4G). Use systemctl status frpc or docker logs natfrp-service to check whether the connection is stable. If it drops, first check the token and the VPS security group.
6. Notes
- Minecraft Bedrock Edition uses
19132/udp; the frp proxy type must be set to udp, and do not write the port as 25565. - Do not map the same domain on both the self-hosted frp and SakuraFrp channels; they will conflict.
- The home side only needs outbound access; do not open ports on the router just to save trouble.