The problem every self-hoster hits
You built something. A local API. A Minecraft world for your friends. A self-hosted dashboard. An ERP running on the office machine. It works great — on your LAN.
The moment you want someone outside to reach it, the fun begins:
- Port forwarding? Good luck if you're behind CGNAT, a corporate firewall, or an ISP that doesn't give you a public IP.
- VPN? Now every person who wants access has to install a client, join a network, and stay connected. Overkill for "let me show you this one page."
- Cloud deploy? Now you're maintaining two environments, paying for a VPS you didn't need, and shipping data somewhere it doesn't have to live.
What most people actually want is simpler: take this one local port, give it a public address, done.
That's exactly what FRP does.
What FRP is
FRP (Fast Reverse Proxy) is an open-source tool by fatedier that exposes a local service behind a NAT or firewall to the public internet. It's battle-tested, written in Go, and has been the go-to answer in self-hosting communities for years.
The model is clean — two pieces:
-
frps(the server) — runs on a machine with a public IP (a $5 VPS is plenty). -
frpc(the client) — runs on your local machine, the one with the service you want to expose.
The client opens an outbound tunnel to the server. The server listens on a public port and forwards traffic back through the tunnel. NAT and firewalls don't matter because the connection is initiated from inside.
[Visitor] → [frps on public VPS:7000] ⇄ tunnel ⇄ [frpc on your laptop] → [localhost:8080]
That's the whole idea. It works for TCP, UDP, HTTP, HTTPS. People run Minecraft servers, remote desktops, internal dashboards, and dev previews through it every day.
The catch: config files
FRP works great. The friction isn't the protocol — it's the workflow.
To run frpc, you write a TOML/INI config file:
serverAddr = "203.0.113.10"
serverPort = 7000
auth.token = "your-secret-key"
[[proxies]]
name = "my-web"
type = "tcp"
localIP = "127.0.0.1"
localPort = 8080
remotePort = 8080
Then you run it from a terminal. If it crashes, you find out when someone texts you "is the server down?" If you want it to start on boot, you write a systemd unit. If you want to change one port, you edit the file and restart. If you're on Windows, you're juggling a console window or a scheduled task.
None of this is hard. But it adds up. And for users who aren't comfortable in a terminal — a small business owner running an ERP, a parent hosting Minecraft for their kid, a designer previewing a static site — it's a wall.
The piece I actually wanted to share
This is where I mention a project I think is worth knowing about, because it directly addresses that friction.
MoonProxy is an open-source desktop GUI for FRP. MIT licensed, built with Tauri v2 + Vue 3 + Rust, runs on macOS and Windows. The frpc binary is bundled — you don't install FRP separately.
What it does is take the config-file workflow above and turn it into a form:
- Add a tunnel by filling in fields (local port, remote port, type, key) — no text editor, no syntax to get wrong.
-
Start/stop with one button — a circular toggle with four visual states (stopped, connecting, connected, error). "Connected" is derived from real
frpcoutput, not an optimistic flag. - Health polling — it checks whether your local port is actually reachable every 3 seconds, so you find out your service is down before your users do.
- System tray resident — close the window, the tunnel keeps running. Launch-at-login means it comes back after a reboot.
- Scheduled connect — set weekdays and a time window; tunnels only stay open during working hours.
-
Engine self-update — pulls new
frpcreleases from the upstream FRP GitHub Releases, SHA256-verifies them, and atomically swaps the binary without reinstalling the app.
You still bring your own frps (self-hosted or a community-public one you trust) — MoonProxy is strictly the client side. It doesn't relay your traffic through any third-party infrastructure. The auth key stays with you.
It's independent of the upstream FRP project — FRP is fatedier's work, licensed and maintained by its original authors. MoonProxy is just a desktop client for it.
A quick walkthrough
Say you have a local dev server on localhost:5173 and an frps running on your VPS at 203.0.113.10:7000.
With plain FRP, you'd write the config, run ./frpc -c frpc.toml, and leave a terminal open.
With MoonProxy, you:
- Open the app.
- Click "Add tunnel."
- Fill in: name
vite-dev, typetcp, local127.0.0.1:5173, remote port5173, server address + your token. - Click the toggle.
The button goes connecting → connected. Your dev server is now reachable at 203.0.113.10:5173. Close the laptop lid, open it again — the tunnel comes back. You didn't write a config file or touch a terminal.
When to use what
-
Plain
frpCLI if you live in the terminal, want to script everything, or run headless servers. FRP is excellent. This isn't a replacement — it's a layer on top. - A GUI like MoonProxy if you want set-and-forget tunnels, you're sharing the machine with non-technical users, or you're tired of debugging a YAML file at midnight because a colleague can't reach the staging URL.
- Managed relays (Ngrok, Cloudflare Tunnel) if you want zero infrastructure and don't mind routing through a vendor's network. Different tradeoff — convenience for dependency.
Wrapping up
FRP remains one of the cleanest ways to put a local service on the public internet. The protocol is solid; the friction has always been in the operating of it.
If you've been meaning to try FRP but bounced off the config-file workflow — or you're already running it and want a tray icon instead of a terminal window — MoonProxy is worth a look.
- 🌐 Website: moonproxy.app
- 💻 Source (MIT): github.com/MoonProxyHQ/moonproxy-desktop
- ⬇️ Releases: macOS (Apple Silicon + Intel) and Windows x64
If you build something with it, I'd love to hear what you're tunneling. 👋
Top comments (0)