ngrok is a tool that proxies traffic from a subdomain (like to a port on your localhost. I’ve been using it off and on for years. There’s a couple use cases I’ve used it for:

  • Forward web traffic to a local next.js / webpack server, to show a website in progress without having to deploy it. This way I can send an ngrok link to a friend/coworker and they can see the latest.
  • Forward api traffic from a device (ex. an iPhone if I’m working on an iOS app) to my local api server.

It’s been very useful, but recently I’ve switched to ktunnel. ktunnel deploys a service on kubernetes, which will proxy traffic to a local port of your choice. It’s more work to set up than ngrok, so it won’t be worth the hassle for a lot of people, but there’s a few upsides to self-hosting.

The biggest benefit for me is that I can use my own domain, so instead of a random ngrok subdomain I’ll set it up on something like Additionally, there’s no connections limit, you’re only limited by the server you’re hosting your cluster on. Last but not least, it’s free if you have a cluster available, which I happened to have.


I’ll assume that you have a kubernetes cluster running. If you don’t, and want to get started with one, you can start a cluster and connect to it with kubectl pretty quickly. I recommend Digital Ocean, they have an easier setup than AWS/GKE. Once you’re at the point where you can run kubectl get all, and have an ingress controller set up (I use kong), then you’re good to go.


First, you’ll need to install ktunnel, check out the installation instructions here for the latest. I built from source but you can also get the latest binary. After that, you can start ktunnel like this:

ktunnel expose ktunnel-proxy <LOCAL_PORT>

This will create aservice on your cluster called ktunnel-proxy, which will take traffic on port 80 and forward it to your localhost on port <LOCAL_PORT>.

The next step is to expose the new service to the world. To do that, create an ingress that looks like this:

apiVersion: extensions/v1beta1
kind: Ingress
  name: ktunnel-proxy
    - host: proxy.<YOUR_TLD>
          - path: /
              serviceName: ktunnel-proxy
              servicePort: 80

Of course, you’ll also have to set up DNS to get traffic from YOUR_TLD to go to your cluster. If you’re using Digital Ocean, you can point the A records to a load balancer that points to your cluster.

So after this, when you go to proxy.<YOUR_TLD>:

  1. DNS will resolve to your cluster IP
  2. The ingress controller will direct traffic to the ktunnel-proxy service
  3. The ktunnel-proxy service will direct traffic to your <LOCAL_PORT> on your localhost


Keep in mind that the termination process for ktunnel will try to delete the service and deployment it created on startup. If you’ve lost internet connection or something, it’s possible this doesn’t get run. If ktunnel isn’t able to start up, you may want to run kubectl get services and see if there’s a stray ktunnel service hanging around.