# How I replaced ngrok with a free, self-hosted alternative

2026-01-28 · 3 min read · #tech · #privacy · #tutorial

Source: https://jopcmelo.com/writing/how-i-replaced-ngrok-with-a-free-self-hosted-alternative

If you've ever needed to expose a local server to the internet—maybe for testing webhooks, sharing a demo with a client, or debugging a mobile app—you've probably used ngrok. It's great. It just works.

But here's the thing: ngrok's free tier is limited. You get random URLs that change every session, and if you want custom subdomains or multiple tunnels at once, you're looking at a paid plan. For a tool I use occasionally, that felt like overkill.

So I went looking for a self-hosted alternative. Enter frp.

## What is frp?

[frp](https://github.com/fatedier/frp) is an open-source reverse proxy that lets you expose local servers behind a NAT or firewall to the internet. It supports TCP, UDP, HTTP, and HTTPS, with features like custom domains, SSL termination, and authentication.

The setup is straightforward: you run `frps` (the server) on a VPS, and `frpc` (the client) on your local machine. The client connects to the server, and traffic flows through.

## Setting up the server

I wanted something I could deploy easily on my VPS using Coolify, so I wrapped the frp Docker image with some custom configuration. The repo is here: [joaopcm/frp-docker-deployment](https://github.com/joaopcm/frp-docker-deployment).

The server needs three environment variables: `FRP_TOKEN`, `DASHBOARD_USER`, and `DASHBOARD_PWD`.

In Coolify, you configure the domains field to route traffic through Traefik. Only port 7000 needs direct exposure for client connections. Everything else goes through Traefik with automatic SSL.

## The client: a simple shell command

I didn't want to manage config files every time I needed a tunnel. So I added a custom `tunnel` command to my shell that:

1. Generates a temporary frp config file
2. Pulls the auth token from 1Password (so it's never hardcoded)
3. Starts the tunnel

Now I can just run:

```bash
tunnel 3000 app1
```

And my `localhost:3000` is instantly available at `https://app1.frp.jopcmelo.dev` with SSL and a custom subdomain. I can run `app1`, `app2`, and `app3` in parallel without paying a cent.

## Why this setup works for me

- **Free**: No monthly fees, just VPS costs I'm already paying
- **Custom subdomains**: Predictable URLs I can share without regenerating
- **Parallel tunnels**: Multiple services exposed at once
- **Secure**: Token auth via 1Password, SSL via Cloudflare/Traefik
- **Fast**: frp is lightweight and performant

Is it more setup than just running `ngrok http 3000`? Yes. But it's a one-time investment that pays off if you tunnel regularly.

## Wrapping up

If you're tired of ngrok's limitations and already have a VPS, frp is worth a look. The Docker deployment makes it easy to get started, and once you have a shell alias, the day-to-day experience is just as smooth.

Happy tunneling!

---

**Plot twist**: Right after I finished writing this, [Rehan](https://x.com/rehanvdm) casually mentioned he uses Tailscale Funnel—which has automatic SSL/HTTPS built-in. I always thought Tailscale was just a VPN. Turns out you can skip most of this setup and just use Tailscale with much higher limits than ngrok's free tier. So... you probably don't need any of this. But hey, at least I learned something.
