After a colleague alerted me to ngrok, I’ve been using the tool regularly to expose local development services to the internet. During a trip out of the country I needed to access a number of services on my LAN so decided to evaluate the paid plan.

Exposing services

Usage of ngrok requires installation of the ngrok cli. After installing, a service running locally on port 3000 can be exposed to the internet via the cli using:

ngrok http 3000

Doing so will create an ephemeral ngrok subdomain which forwards requests to the local service.

Forwarding  https://fc37fb5d71e3.ngrok.app -> http://localhost:3000

This can be configured via config, which allows for the configuration of multiple tunnels:

version: "2"
authtoken: <your-token>
tunnels:
  my-first-app:
    addr: 3000
    proto: http
  my-second-app:
    addr: 8080
    proto: http

The above config can be used to start multiple tunnels:

# will read config in ~/.config/ngrok/ngrok.yml
ngrok start --all

On reboot, the above command will need to be executed again. This article describes how to configure a systemd service to start ngrok on boot.

Upgrading to a paid plan allows for TCP tunnels (like SSH) and (on the personal plan, a single) custom domain. My use case is to be able to access a number of services via some easy to remember myapp.mydomain.com. Unfortunately by default this will not work, as ngrok will only listen on port 80 or 443, so configuring the following will report that forwarding is occuring for both services, but all traffic will be forwarded to the second service only:

tunnels:
  example1:
    proto: http
    addr: 8080
    domain: myapp.mydomain.com
  example2:
    proto: http
    addr: 8081
    domain: myapp.mydomain.com

A workaround for this is to configure a single ngrok service only which forwards to a local proxy which can then forward to the correct service based on request detail. An example of this using nginx is here. The restriction of a single custom domain, and no configuration for listening port, means only a single service can be exposed via a custom domain. Wildcard domains are supported but require upgrading to the enterprise tier. Another workaround is to just use the free plan and access the endpoints on their ephemeral subdomains via the ngrok dashboard.

The (paid) personal plan also enables TCP tunnels which are useful for SSH access to a machine. A single static TCP endpoint can be requested which is the combination of an ngrok subdomain and port:

tunnels:
  ssh:
    proto: tcp
    addr: 22
    remote_addr: 0.tcp.au.ngrok.io:30524

You can configure your own DNS to point at the subdomain, but the port must be specified when connecting:

ssh -p 30524 myuser@ssh.mydomain.com

Other alternatives

My intention was to use the ssh tunnel via vscode Remote Development, but vscode also supports Local Port Forwarding over ssh and ultimately this was the solution I used. This meant the only value I got from the paid plan was the single ssh tunnel, which was not worth the cost, so I cancelled the ngrok subscription.

I’ll continue to use ngrok for local development on the free plan, but next time I need an ssh tunnel I’ll look for alternatives. One such alternative is the free service serveo.net, however my initial investigations found it to be slow, and in the case of custom domains, unreliable. Additionally its future appears uncertain. I’ll listen out for recommendations and look into alternatives next time I decide to spend time outside my LAN, including: