# a tunnel of my own

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

Source: https://jopcmelo.com/writing/a-tunnel-of-my-own

every now and then I need something on my machine to be reachable from the internet: a webhook to test, a demo to share, an app to open on my phone. ngrok does that, and it just works.

what I wanted was my own subdomains, a few tunnels at once, and nothing to pay for a tool I use a few times a month. ngrok's [free plan](https://ngrok.com/pricing) gives you one domain, picked for your account, and no custom ones. so I looked for something to host myself, and found [frp](https://github.com/fatedier/frp): "a fast reverse proxy to help you expose a local server behind a NAT or firewall to the internet."

## how it works

frp comes in two halves. `frps`, the server, runs somewhere public, like a VPS. `frpc`, the client, runs on my machine and opens a connection out to the server. when a request comes in for one of my subdomains, the server sends it back down that connection to whatever's running locally. **my machine never accepts a connection, it only opens one**, which is why it works behind a router or a firewall.

*(sketch: my machine, running something on localhost:3000, connected to a VPS running frps through a tunnel that frpc opened on port 7000. requests flow from the internet, at app1.frp.jopcmelo.dev, through the VPS and down the tunnel, over and over)*

## the server

I deploy things on my VPS with Coolify, so I wrapped frp's Docker image with the config I needed: [joaopcm/frp-docker-deployment](https://github.com/joaopcm/frp-docker-deployment). it takes three environment variables: `FRP_TOKEN`, `DASHBOARD_USER` and `DASHBOARD_PWD`.

only port 7000, where clients connect, is exposed directly. the subdomains go through Traefik, which Coolify sets up, and that's where the SSL certificates come from.

## the client

I didn't want a config file per tunnel, so I added a `tunnel` command to my shell. it writes a temporary frp config, pulls the token from 1Password so it's never written down anywhere, and starts `frpc`:

```bash
tunnel 3000 app1
```

*(sketch: tunnel 3000 app1 does three things: writes a temporary config, gets the token from 1Password, and starts frpc. after that, https://app1.frp.jopcmelo.dev points at localhost:3000)*

and `localhost:3000` is at `https://app1.frp.jopcmelo.dev`, with SSL. `app2` and `app3` can run at the same time. **it's more setup than ngrok, once**, and then every tunnel after that is one command.

## the plot twist

right after I finished writing this, [Rehan](https://x.com/rehanvdm) mentioned he uses [Tailscale Funnel](https://tailscale.com/kb/1223/funnel). I'd always thought of Tailscale as just a VPN. turns out Funnel routes traffic from the internet to a service on your machine, and creates the HTTPS certificates for you, on a `ts.net` address. **so you probably don't need any of this.** the URLs live under your tailnet's name instead of your own domain, and Funnel is still in beta, but for most of what I used ngrok for, it's enough.

but hey, now I know how a reverse tunnel works.
