NGrok: A simple DyDns Reverse Proxy

Published: Mar 9, 2023 by Isaac Johnson

I have come across NGrok in a few tutorials over time. It provides a fantastic service that is basically a dynamic DNS plus reverse proxy in one. It makes exposing ones internal service fantastically easy and the basic plan is free.

Signup

Let’s create a free plan from the main NGrok page

/content/images/2023/03/ngrok-01.png

I chose to federate through google. We arrive at the main dashboard

/content/images/2023/03/ngrok-02.png

Let’s start with a simple test. Whenever I write these, I fire up bundle exec jekyll serve and use https://localhost:4000 to check the site.

/content/images/2023/03/ngrok-03.png

So now I’ll pull down the ngrok binary into WSL

builder@DESKTOP-72D2D9T:~$ wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
--2023-03-06 18:52:55--  https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
Resolving bin.equinox.io (bin.equinox.io)... 52.202.168.65, 54.237.133.81, 18.205.222.128, ...
Connecting to bin.equinox.io (bin.equinox.io)|52.202.168.65|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 9300212 (8.9M) [application/octet-stream]
Saving to: ‘ngrok-v3-stable-linux-amd64.tgz’

ngrok-v3-stable-linux-amd64.t 100%[=================================================>]   8.87M  2.41MB/s    in 4.8s

2023-03-06 18:53:00 (1.86 MB/s) - ‘ngrok-v3-stable-linux-amd64.tgz’ saved [9300212/9300212]

builder@DESKTOP-72D2D9T:~$ tar -xzvf ngrok-v3-stable-linux-amd64.tgz
ngrok

I’ll then configure it

builder@DESKTOP-72D2D9T:~$ chmod 755 ./ngrok
builder@DESKTOP-72D2D9T:~$ ./ngrok config add-authtoken asdfjhiasdfjhkjfsdjksdfakjlsdfasdafjsdafdsfaasd
Authtoken saved to configuration file: /home/builder/.config/ngrok/ngrok.yml

I’ll now fire up NGrok in a seperate window

/content/images/2023/03/ngrok-04.png

which I can now use to visit the site

/content/images/2023/03/ngrok-05.png

This means the site, as running on my laptop is publicly available on that URL as long as ngrok is running. The free plan means it won’t keep that URL if I restart.

I can see the traffic served when looking at the ngrok window

/content/images/2023/03/ngrok-06.png

SSH tunnels

Say I wanted to, for a short period, expose my WSL to an outside source.

I could fire up a 22 forwarder:

$ ./ngrok tcp 22

/content/images/2023/03/ngrok-07.png

Here I’ll snap install Ngrok to a Linux host in my network

isaac@isaac-MacBookAir:~$ sudo snap install ngrok
ngrok (v3/stable) 3.1.0 from Kyle Wenholz (kyle-ngrok) installed
isaac@isaac-MacBookAir:~$ ngrok config add-authtoken asdfjhiasdfjhkjfsdjksdfakjlsdfasdafjsdafdsfaasd
Authtoken saved to configuration file: /home/isaac/snap/ngrok/89/.config/ngrok/ngrok.yml
isaac@isaac-MacBookAir:~$ ngrok tcp 22

/content/images/2023/03/ngrok-08.png

Kubernetes

For this test, I’ll use a pretty basic 3 node cluster that has Traefik for Ingress already

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ kubectl get ingress --all-namespaces
No resources found
builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ kubectl get svc --all-namespaces
NAMESPACE     NAME             TYPE           CLUSTER-IP      EXTERNAL-IP                                PORT(S)                      AGE
default       kubernetes       ClusterIP      10.43.0.1       <none>                                     443/TCP                      11h
kube-system   kube-dns         ClusterIP      10.43.0.10      <none>                                     53/UDP,53/TCP,9153/TCP       11h
kube-system   metrics-server   ClusterIP      10.43.65.92     <none>                                     443/TCP                      11h
kube-system   traefik          LoadBalancer   10.43.239.133   192.168.1.12,192.168.1.159,192.168.1.206   80:30246/TCP,443:31771/TCP   11h

So we have something to look at, I’ll fire up the Azure Vote Sample App

$ helm repo add azure-samples https://azure-samples.github.io/helm-charts/
"azure-samples" already exists with the same configuration, skipping
builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "longhorn" chart repository
...Successfully got an update from the "kuma" chart repository
...Successfully got an update from the "freshbrewed" chart repository
...Successfully got an update from the "myharbor" chart repository
...Successfully got an update from the "dapr" chart repository
...Successfully got an update from the "confluentinc" chart repository
...Successfully got an update from the "sonarqube" chart repository
...Successfully got an update from the "azure-samples" chart repository
...Successfully got an update from the "kubecost" chart repository
...Successfully got an update from the "actions-runner-controller" chart repository
...Successfully got an update from the "hashicorp" chart repository
...Successfully got an update from the "sumologic" chart repository
...Successfully got an update from the "novum-rgi-helm" chart repository
...Successfully got an update from the "adwerx" chart repository
...Successfully got an update from the "open-telemetry" chart repository
...Successfully got an update from the "rhcharts" chart repository
...Successfully got an update from the "epsagon" chart repository
...Successfully got an update from the "lifen-charts" chart repository
...Successfully got an update from the "uptime-kuma" chart repository
...Successfully got an update from the "elastic" chart repository
...Successfully got an update from the "datadog" chart repository
...Successfully got an update from the "nginx-stable" chart repository
...Successfully got an update from the "rook-release" chart repository
...Successfully got an update from the "harbor" chart repository
...Successfully got an update from the "argo-cd" chart repository
...Successfully got an update from the "incubator" chart repository
...Successfully got an update from the "newrelic" chart repository
...Successfully got an update from the "crossplane-stable" chart repository
...Successfully got an update from the "gitlab" chart repository
...Successfully got an update from the "rancher-latest" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ⎈Happy Helming!⎈

$ helm install azure-samples/azure-vote --generate-name
NAME: azure-vote-1678237213
LAST DEPLOYED: Tue Mar  7 19:00:14 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Azure Vote application has been started on your Kubernetes cluster.

Title: Azure Vote App
Vote 1 value: Cats
Vote 2 value: Dogs

The externally accessible IP address can take a minute or so to provision. Run the following command to monitor the provisioning status. Once an External IP address has been provisioned, brows to this IP address to access the Azure Vote application.

kubectl get service -l name=azure-vote-front -w

We’ll see a service was created, but I do not expect an external-IP to be provided

$ kubectl get service -l name=azure-vote-front -w
NAME               TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
azure-vote-front   LoadBalancer   10.43.245.23   <pending>     80:31757/TCP   67s

and the pods are running

$ kubectl get pods
NAME                                                READY   STATUS    RESTARTS   AGE
vote-back-azure-vote-1678237213-5b7d969f66-zcx85    1/1     Running   0          87s
vote-front-azure-vote-1678237213-79974bfc78-ss8ps   1/1     Running   0          87s

If I port-forward, I can see the App

/content/images/2023/03/ngrok-12.png

A simple NGrok Deployment forwarder

This method was first laid out in this medium post several years ago. Though I needed to tweak to add the authtoken which is required now.

The Deployment YAML:

$ cat testNgrok.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngrok
spec:
  selector:
    matchLabels:
      app: ngrok
  replicas: 1
  template:
    metadata:
      labels:
        app: ngrok
    spec:
      containers:
      - name: ngrok
        image: wernight/ngrok
        command: ["ngrok"]
        args: ["http", "-authtoken=asdfjhiasdfjhkjfsdjksdfakjlsdfasdafjsdafdsfaasd", "azure-vote-front"]
        ports:
        - containerPort: 4040

$ kubectl apply -f ./testNgrok.yml
deployment.apps/ngrok created

Once launched, I’ll query the port 4040 Tunnel API Endpoint to see the NGrok URL

$ kubectl exec `kubectl get pods -l=app=ngrok -o=jsonpath='{.items[0].metadata.name}'` -- curl http://localhost:4040/api/tunnels | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   799  100   799    0     0   588k      0 --:--:-- --:--:-- --:--:--  780k
{
  "tunnels": [
    {
      "name": "command_line",
      "uri": "/api/tunnels/command_line",
      "public_url": "https://09cf-73-242-50-46.ngrok.io",
      "proto": "https",
      "config": {
        "addr": "http://azure-vote-front:80",
        "inspect": true
      },
      "metrics": {
        "conns": {
          "count": 0,
          "gauge": 0,
          "rate1": 0,
          "rate5": 0,
          "rate15": 0,
          "p50": 0,
          "p90": 0,
          "p95": 0,
          "p99": 0
        },
        "http": {
          "count": 0,
          "rate1": 0,
          "rate5": 0,
          "rate15": 0,
          "p50": 0,
          "p90": 0,
          "p95": 0,
          "p99": 0
        }
      }
    },
    {
      "name": "command_line (http)",
      "uri": "/api/tunnels/command_line%20%28http%29",
      "public_url": "http://09cf-73-242-50-46.ngrok.io",
      "proto": "http",
      "config": {
        "addr": "http://azure-vote-front:80",
        "inspect": true
      },
      "metrics": {
        "conns": {
          "count": 0,
          "gauge": 0,
          "rate1": 0,
          "rate5": 0,
          "rate15": 0,
          "p50": 0,
          "p90": 0,
          "p95": 0,
          "p99": 0
        },
        "http": {
          "count": 0,
          "rate1": 0,
          "rate5": 0,
          "rate15": 0,
          "p50": 0,
          "p90": 0,
          "p95": 0,
          "p99": 0
        }
      }
    }
  ],
  "uri": "/api/tunnels"
}

You can see, we have exposed both HTTP and HTTPS

/content/images/2023/03/ngrok-13.png

And it’s just as easy to remove

$ kubectl delete -f testNgrok.yml
deployment.apps "ngrok" deleted

As an aside, if you don’t have a valid Auth key or it’s missing, expect an error

/content/images/2023/03/ngrok-14.png

Unofficial Official Helm Chart

Note: I’m not successful but that’s because this path only works for paid accounts

We already have our Auth Token as we used it to configure our other instances

But we need to get an API key

/content/images/2023/03/ngrok-09.png

I’ll click create an optionally change its name

/content/images/2023/03/ngrok-10.png

When we click create, we’ll be shown the key once

/content/images/2023/03/ngrok-11.png

We can see the Unofficial NGrok Ingress Controller Helm Chart for a more scalable way to use NGrok.

First we add the Helm chart

$ helm repo add ngrok https://ngrok.github.io/kubernetes-ingress-controller
"ngrok" has been added to your repositories
$ helm repo update

Then we can install

$ helm install ngrok-ingress-controller ngrok/kubernetes-ingress-controller --namespace ngrok-ingress-controller --create-namespace --set credentials.apiKey=ruruywreuiweuitruiwiureutrerreterter --set credentials.authtoken=asdfjhiasdfjhkjfsdjksdfakjlsdfasdafjsdafdsfaasd --set ingressClass.default=true
NAME: ngrok-ingress-controller
LAST DEPLOYED: Tue Mar  7 19:37:04 2023
NAMESPACE: ngrok-ingress-controller
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
================================================================================
The ngrok Ingress controller has been deployed as a Deployment type to your
cluster.

If you haven't yet, create some Ingress resources in your cluster and they will
be automatically configured on the internet using ngrok.

One example, taken from your cluster, is the Service:
   "metrics-server"

You can make this accessible via Ngrok with the following manifest:
--------------------------------------------------------------------------------
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: metrics-server
  namespace: kube-system
spec:
  ingressClassName: ngrok
  rules:
  - host: metrics-server-3mdBbZaf.ngrok.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: metrics-server
            port:
              number: 443
--------------------------------------------------------------------------------
Applying this manifest will make the service "metrics-server"
available on the public internet at "https://metrics-server-3mdBbZaf.ngrok.io/".

Once done, view your edges in the Dashboard https://dashboard.ngrok.com/cloud-edge/edges
Find the tunnels running in your cluster here https://dashboard.ngrok.com/tunnels/agents

This controller is under active development and is in Alpha. It is not suggested to use this in production.
If you have any questions or feedback, please join us in https://ngrok.com/slack and let us know!

Sadly, I cannot just update the Azure Vote App helm values to use the new Ingress type as that isn’t exposed

$ helm get values --all azure-vote-1678237213
COMPUTED VALUES:
serviceNameFront: azure-vote-front
serviceType: LoadBalancer
title: Azure Vote App
value1: Cats
value2: Dogs

I’ll delete the old Azure Vote helm then redo to use the new default Ingress controller

$ helm delete azure-vote-1678237213
release "azure-vote-1678237213" uninstalled
$ helm install azure-samples/azure-vote --generate-name
NAME: azure-vote-1678239487
LAST DEPLOYED: Tue Mar  7 19:38:08 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Azure Vote application has been started on your Kubernetes cluster.

Title: Azure Vote App
Vote 1 value: Cats
Vote 2 value: Dogs

The externally accessible IP address can take a minute or so to provision. Run the following command to monitor the provisioning status. Once an External IP address has been provisioned, brows to this IP address to access the Azure Vote application.

kubectl get service -l name=azure-vote-front -w

However, this does work

builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ cat testAVFIngress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: azure-vote
spec:
  ingressClassName: ngrok
  rules:
  - host: azure-vote-3mdbbzaf.ngrok.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: azure-vote-front
            port:
              number: 80
builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ kubectl apply -f testAVFIngress.yaml
ingress.networking.k8s.io/azure-vote created
builder@DESKTOP-QADGF36:~/Workspaces/ansible-playbooks$ kubectl get ingress
NAME         CLASS   HOSTS                          ADDRESS   PORTS   AGE
azure-vote   ngrok   azure-vote-3mdbbzaf.ngrok.io             80      10s

But it does not work - we can see the normal ngrok page when it is not a valid URL https://azure-vote-3mdbbzaf.ngrok.io/

/content/images/2023/03/ngrok-15.png

Looking at the logs, I see plenty of errors

2023-03-08T01:37:26Z    INFO    cache-store-driver      syncing driver state!!
2023-03-08T01:37:26Z    DEBUG   events  Deleted Domain azure-vote-gdhwbgtg-ngrok-io     {"type": "Normal", "object": {"kind":"Domain","namespace":"default","name":"azure-vote-gdhwbgtg-ngrok-io
","uid":"d37da700-dd7c-4593-93ac-4a38d971ee50","apiVersion":"ingress.k8s.ngrok.com/v1alpha1","resourceVersion":"18867"}, "reason": "Deleted"}
2023-03-08T01:37:26Z    DEBUG   events  Deleted Edge azure-vote-gdhwbgtg-ngrok-io       {"type": "Normal", "object": {"kind":"HTTPSEdge","namespace":"default","name":"azure-vote-gdhwbgtg-ngrok
-io","uid":"afd39ff7-e1ca-47de-8832-3f6332424a3e","apiVersion":"ingress.k8s.ngrok.com/v1alpha1","resourceVersion":"18868"}, "reason": "Deleted"}
2023-03-08T01:37:26Z    ERROR   controllers.https-edge  unable to fetch Edge    {"V1Alpha1HTTPSEdge": "default/azure-vote-gdhwbgtg-ngrok-io", "error": "HTTPSEdge.ingress.k8s.ngrok.com \"azure-
vote-gdhwbgtg-ngrok-io\" not found"}
E0308 01:37:32.404626       1 reflector.go:140] pkg/mod/k8s.io/client-go@v0.26.0/tools/cache/reflector.go:169: Failed to watch *v1alpha1.Domain: the server could not find the requested resourc
e (get domains.ingress.k8s.ngrok.com)
E0308 01:37:32.434640       1 reflector.go:140] pkg/mod/k8s.io/client-go@v0.26.0/tools/cache/reflector.go:169: Failed to watch *v1alpha1.HTTPSEdge: the server could not find the requested reso
urce (get httpsedges.ingress.k8s.ngrok.com)
W0308 01:37:33.491187       1 reflector.go:424] pkg/mod/k8s.io/client-go@v0.26.0/tools/cache/reflector.go:169: failed to list *v1alpha1.Domain: the server could not find the requested resource
 (get domains.ingress.k8s.ngrok.com)
E0308 01:37:33.491323       1 reflector.go:140] pkg/mod/k8s.io/client-go@v0.26.0/tools/cache/reflector.go:169: Failed to watch *v1alpha1.Domain: failed to list *v1alpha1.Domain: the server cou
ld not find the requested resource (get domains.ingress.k8s.ngrok.com)

The repeated error is

2023-03-08T12:02:35Z    INFO    cache-store-driver      syncing driver state!!
2023-03-08T12:02:35Z    ERROR   cache-store-driver      error creating domain   {"domain": {"metadata":{"name":"azure-vote-3mdbbzaf-ngrok-io","namespace":"default","creationTimestamp":null},"spec":{"domain":"azure-vote-3mdbbzaf.ngrok.io"},"status":{}}, "error": "the server could not find the requested resource (post domains.ingress.k8s.ngrok.com)"}
2023-03-08T12:02:45Z    ERROR   controllers.ingress     Failed to sync ingress to store {"error": "the server could not find the requested resource (post domains.ingress.k8s.ngrok.com)"}
2023-03-08T12:02:45Z    ERROR   Reconciler error        {"controller": "ingress", "controllerGroup": "networking.k8s.io", "controllerKind": "Ingress", "Ingress": {"name":"azure-vote","namespace":"default"}, "namespace": "default", "name": "azure-vote", "reconcileID": "d7628c86-6e04-42af-8305-8521e7076550", "error": "the server could not find the requested resource (post domains.ingress.k8s.ngrok.com)"}

Perhaps this requires a paid account.

Redoing it all I found that the error now is rather clear

2023-03-08T12:29:57Z    DEBUG   events  Failed to create Domain azure-vote-3mdbbzaf-ngrok-io: HTTP 400: Only Personal, Pro or Enterprise accounts can reserve domains.
Your account can't reserve domains.

Upgrade to a paid plan at: https://dashboard.ngrok.com/billing/subscription [ERR_NGROK_401]

Operation ID: op_2MjQdOarUYbzOntLfQpoc1Zb3zM    {"type": "Warning", "object": {"kind":"Domain","namespace":"default","name":"azure-vote-3mdbbzaf-ngrok-io","uid":"cf399f04-fd8a-49e2-ae0d-6356fe4d2127","apiVersion":"ingress.k8s.ngrok.com/v1alpha1","resourceVersion":"1108"}, "reason": "CreateFailed"}
2023-03-08T12:29:58Z    ERROR   Reconciler error        {"controller": "httpsedge", "controllerGroup": "ingress.k8s.ngrok.com", "controllerKind": "HTTPSEdge", "HTTPSEdge": {"name":"azure-vote-3mdbbzaf-ngrok-io","namespace":"default"}, "namespace": "default", "name": "azure-vote-3mdbbzaf-ngrok-io", "reconcileID": "c61ac568-af7c-4d66-834c-3eab686e9e3b", "error": "HTTP 400: The domain 'azure-vote-3mdbbzaf.ngrok.io:443' is not reserved. [ERR_NGROK_7117]\n\nOperation ID: op_2MjQdOXEGjpzMcSq3SFr8s83VDU"}

which lines up to the lack of domain reservation

/content/images/2023/03/ngrok-17.png

Costs

By month, NGrok ranges from $10 to $75. You can save 20% billing annually.

/content/images/2023/03/ngrok-16.png

All the accounts are limited to 1Gb a month. Most basic websites can handle that. But when you get into hosting artifact stores, containers, or perhaps video you can blow past that with ease.

In the “Pro” and “Enterprise” tiers you can optionally enable On-Demand pricing which would enable additional bandwidth at $0.10/Gb

/content/images/2023/03/ngrok-18.png

I know for my home internet I routinely touch upward of a Tb - as I have a residential provider with Data Caps I have to pay extra to remove (grumble grumble) and this site generates upwards of 38Gb of traffic from a static site

/content/images/2023/03/ngrok-19.png

At 37.21Gb in a month, that would be about $4. To handle all the traffic from my house would be $100/mo. That isn’t counting the other overages such as Peak usage or additional agents.

Other features

Most other features are behind the paywall

TLS Certs:

/content/images/2023/03/ngrok-20.png

IP Policies:

/content/images/2023/03/ngrok-21.png

SSO:

/content/images/2023/03/ngrok-22.png

Domains:

/content/images/2023/03/ngrok-23.png

Summmary

Ngrok solves a problem. It creates a basic Dynamic DNS reverse tunnel easily. It works with most OSes and certainly can work in Kubernetes. The free offering is great for demos or quick checks. I actually like that they have some lower cost paid tiers.

Think of the following scenarios:

  1. you have a legacy system on prem that cannot move to the cloud - due to age, rules, software - but you need to expose this middleware to an application
  2. you have a restricted device like a nanycam or a NAS with photo software - you want to share with family or check when you are remote - low bandwidth
  3. you have a basic webhook, function or tool on-prem
  4. you have a legacy revision control system that by rules you cannot expose but it’s auth is insufficient (think SourceSafe or an old CVS host).

For some reason $10/mo is on my line for random stuff I’ll pay for - I probably would have pulled the trigger at $5 month for doing demos but not $10.

NGrok ReverseProxy Tunnel

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes