DEV Community

ChenXX
ChenXX

Posted on

How to Put a Local Service on the Public Internet with FRP (Without Losing Your Mind Over Config Files)

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:

  1. frps (the server) — runs on a machine with a public IP (a $5 VPS is plenty).
  2. 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]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 frpc output, 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 frpc releases 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:

  1. Open the app.
  2. Click "Add tunnel."
  3. Fill in: name vite-dev, type tcp, local 127.0.0.1:5173, remote port 5173, server address + your token.
  4. 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 frp CLI 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.

If you build something with it, I'd love to hear what you're tunneling. 👋

Top comments (0)