Beware this content is over a year old and considered stale. It may no longer be accurate and/or reflect the understanding of the author but remains here for reference only. Please keep this in mind as you use this content.

Note: This applies to ngrok v1.x - unfortunately ngrok 2.x hasn’t yet made their source code available.

We’ve been testing a third-party API that uses webhooks to notify your system when particular events take place that you might be interested in.

As you might appreciate, testing webhooks in a local development environment is tricky because these external APIs or sandbox developer environments have no way of pinging (sending a request to) your URI because its running on localhost which is not accessible to the outside world.

And a good thing too.

However, what if you needed to temporarily expose some code to the world for a short period of time? In our case, so you could actually test your reciever for the webhook?

Enter, ngrok. Out the box, it works. After you install the client binary, you tell it which port you want to expose and it generates a random URL for you.

1
ngrok http 80

At this point your local code at http://localhost/webhook.php becomes accessible at http://123456789.ngrok.io/webhook.php.

And that is all good and well for one-off hooks, but what if you needed to link to that endpoint reliably from time-to-time? Each time you run ngrok, it will generate a new random subdomain.

Well, as it turns out, you can specify a subdomain (testing.ngrok.io) but that means you’ll need to reserve that subdomain to avoid someone else using it. As it turns out, that will require a paid account.

OK, what’s my alternative? Host it yourself.

This is what you need to do to run a copy of your own ngrok so you can specify you’re own subdomains under a custom domain name you might own (ngrok.example.com).

0. Re-point you’re domain name:

You’ll need access to pick a domain name you have control over. Decide what the base domain name is going to be.

The most common approach is to use a subdomain. For example, if I own example.com, I would want to setup a subdomain ngrok.example.com so that all tunnels generated would be under that namespace.

1
2
http://123456789.ngrok.example.com
http://testing.ngrok.example.com

It’s important that you check whether your domain name registrar or the interface you’re using to manage your domain name allows you to specify a wildcard entry for a domain name.

I have some domain names with 1&1, it seems their domain name mangement capability are very poor. It is currently impossible to create a wildcard subdomain entry.

You need to be able to create an A-Record entry with a wildcard (asterisk) like this:

*.ngrok.example.com

Setup the A-Record so that any subdomain request points to the IP address of the server that will be hosting ngrok.

1. Setting up your server:

Make sure you have all the necessary build tools and Go langauge. The Mercurial version control is needed for the ngrok dependencies and Git is needed to clone the main repository.
sudo apt-get install build-essential golang mercurial git-core

Clone the repository to helpful location.

1
2
git clone https://github.com/inconshreveable/ngrok.git ~/ngrok.git
cd ~/ngrok.git

Before you compile, you’ll need to create your own self-signed SSL/TLS certificate to secure the link between the client and the server.

This means you cannot use the official or standard ngrok client as the built-in certificate is set to authenticate with the official ngrok server. We will have to compile a custom client with a self-signed certfiicate for this to work.

To generate the self-signed certificate, you can use this script as follows:

1
2
mkdir ~/ngrok.git/tls
~/ngrok.git/tls.sh ngrok.example.com ~/ngrok.git/tls

That should generate a collection of private keys and certicates which will expire in about 10,000 days (~27.4 years)!

Next, ensure the self-signed certificate replaces the standard certifciate included in the source code, so when the client is compiled, it uses this one instead.

1
2
mv ~/ngrok.git/assets/client/tls/ngrokroot.crt ~/ngrok.git/assets/client/tls/ngrokroot.crt.orig
ln -s ~/ngrok.git/tls/base.pem ~/ngrok.git/assets/client/tls/ngrokroot.crt

Here we just move the original out of the way and symlink the one we want included.

Note, the following environment flags assume your server is Ubuntu 64-bit (you could omit the flags as the default is for Linux) and your client is Mac OS X 64-bit.

1
2
3
cd ~/ngrok.git
make release-server GOOS="linux" GOARCH="amd64"
make release-client GOOS="darwin" GOARCH="amd64"

For other environment options, see here:
https://golang.org/doc/install/source#environment

You should have the executable binaries which may need to made executable:

1
2
chmod +x ~/ngrok.git/bin/ngrokd
chmod +x ~/ngrok.git/bin/ngrok

Credit:

  • Run Ngrok on Your Own Server Using Self-Signed SSL Certificate (svenbit)
  • How to run your own ngrokd server (inconshreveable)

After you’ve setup you’re own copy of ngrok, you can swap it out for the one installed with Homebrew:

1
2
3
4
5
6
7
8
9
brew install ngrok
cd /usr/local/Cellar/ngrok/1.7/bin
mv ngrok ngrok.orig
scp root@server:~ngrok.git/bin/darwin_amd64/ngrok ./
./ngrok version
mkdir /usr/local/etc/ngrok
cp ~/ngrok.git/tls/server.crt /usr/local/etc/ngrok/server.crt
cp ~/ngrok.git/tls/server.key /usr/local/etc/ngrok/server.key

ngrok script /usr/local/etc/ngrok/ngrokd.sh

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
NGROKD="$(which ngrokd)"
DIR="/usr/local/etc/ngrok"
KEY="$DIR/server.key"
CRT="$DIR/server.crt"
DOMAIN="ngrok.example.com"
HTTPADDR=":8080"
HTTPSADDR=":8081"
$NGROKD -tlsKey="$KEY" -tlsCrt="$CRT" -domain="$DOMAIN" -httpAddr="$HTTPADDR" -httpsAddr="$HTTPSADDR"

ngrok TLS script ~/ngrok.git/tls.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#!/bin/bash
DOMAIN="$1"
TLSDIR="$2"
if [ -z "${DOMAIN}" ]; then
echo "Domain required: ngrok.example.com"
exit 1
fi
if [ -z "${TLSDIR}" ]; then
echo "Specify a directory to place the TLS files."
exit 1
fi
echo "ngrok base domain: $DOMAIN"
echo "tls directory: $TLSDIR"
#exit 0
BASE_KEY="$TLSDIR/base.key"
BASE_PEM="$TLSDIR/base.pem"
SERVER_KEY="$TLSDIR/server.key"
SERVER_CSR="$TLSDIR/server.csr"
SERVER_PEM="$TLSDIR/server.pem"
SERVER_CRT="$TLSDIR/server.crt"
COMMON_NAME="/CN=$DOMAIN"
DAYS=10000
NUMBITS=2048
openssl genrsa -out $BASE_KEY $NUMBITS
openssl req -new -x509 -nodes -key $BASE_KEY -days $DAYS -subj "$COMMON_NAME" -out $BASE_PEM
openssl genrsa -out $SERVER_KEY $NUMBITS
openssl req -new -key $SERVER_KEY -subj "$COMMON_NAME" -out $SERVER_CSR
openssl x509 -req -in $SERVER_CSR -CA $BASE_PEM -CAkey $BASE_KEY -CAcreateserial -days $DAYS -out $SERVER_CRT
exit 0

ngrok configuration: /etc/init/ngrokd.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
description "ngrokd service"
author "Unit6"
start on runlevel [2345]
stop on shutdown
script
echo $$ > /usr/local/etc/ngrok/ngrokd.pid
exec /usr/local/bin/ngrokd -tlsKey=/usr/local/etc/ngrok/server.key -tlsCrt=/usr/local/etc/ngrok/server.crt -domain="ngrok.webdev.org.uk" -httpAddr=":8080" -httpsAddr=":8081"
end script
pre-start script
echo "[`date`] ngrokd start" >> /usr/local/etc/ngrok/ngrokd.log
end script
pre-stop script
rm /usr/local/etc/ngrok/ngrokd.pid
echo "[`date`] ngrokd stop" >> /usr/local/etc/ngrok/ngrokd.log
end script