Making a local server publicly accessible

August 16, 2015

Let's say you're making an application that needs to respond to some sort of HTTP callbacks, like GitHub hooks, Twilio SMS callbacks, etc.; these services need to connect to a public server to send the relevant data to. Or let's say you simply want to have someone try your web application that you are hosting locally, but they aren't on the same network as you are. For these sorts of things, you can't normally just use a local server; your computer is probably behind a firewall, so Github, Twilio, your friend etc. wouldn't be able to establish a connection to it. So you need some way to allow these things to make that connection.

ngrok

Ngrok is a super easy, free tool for temporarily making a local server public, which is handy for all sorts of things.

It creates an ssh tunnel from your computer to ngrok's servers, then gives you an ngrok url that you can use, which will forward all traffic securely to your computer. After installing ngrok, running ./ngrok http 80 is all you need to do to set up the ssh tunnel, giving you a domain like http://8b2c75e9.ngrok.io which you can use. All requests made to that URL will then be routed through the ssh tunnel to your computer, meaning if you have a web server running on port 80 of your computer, it is now publically accessible (as long as ./ngrok is running in your shell) via the ngrok url.

How to make your own ngrok

It turns out it's pretty darn easy to make your own hosted version of ngrok if you have root access to a public server (like a VPS)! In fact, all it is is a single ssh command (as well as changing one line in your sshd configuration file). This will let you use your own domain name or IP address as your proxy server rather than ngrok's. The command is:

ssh -nNTR <remote port>:localhost:<local port> username@server_address

Not too hairy is it? I'll break it down:

  • The -nNT flags make ssh not enter interactive mode, not run any remote commands, and disable pseudo-TTY allocation (No shell necessary, just port forwarding).
  • <remote port> is the port on your remote server that you want to proxy to your local server.
  • localhost is specifying the location of the destination server for the requests entering this ssh tunnel.
  • <local port> is used to specify what port of your computer your application is running on.
  • username@server_address is your remote server: what you would normally put after ssh when establishing a normal ssh connection to your server.

Running this command establishes the connection to your remote server, but for security reasons, the only host allowed to access your local machine from your remote server is your remote server. This means you could curl your local server from the remote one, but you couldn't access your local server via the remote server from anywhere but the remote server.

To fix this, you just need to direct sshd, the process that manages all ssh connections, to allow all hosts to access your forwarded port. Simply add this line to your sshd configuration file, which is probably located at /etc/ssh/sshd_config (you'll need to edit as root, so use sudo):

GatewayPorts yes

And then restart sshd:

sudo service ssh restart

And that's all! Now, when you run the ssh command above, it will create an ssh tunnel to your server, making your local server accessible to anyone via your remote server.

An example

Let's run through a quick example to see this in action; this assumes you have root access to an external server, have edited the sshd configuration file, and have restarted sshd.

In one shell (locally), run:

mkdir tunnel-test
cd tunnel-test
touch hello.text
python -m SimpleHTTPServer 1234

to create a new directory with a single file in it and run a simple server to serve the contents of that directory.

Now open another shell and run:

ssh -nNTR 2345:localhost:1234 root@YOUR-IP-ADDRESS

(You may have to type your password, if you don't have SSH Keys set up!)

If you now open a web browser and navigate to YOUR-IP-ADDRESS:2345, it should show you a directory listing for the tunnel-test directory you made. This means you've succesfully proxied the connection from your remote server to your local computer!! Now you can easily share your server in progress with others, before you ever have to think about deploying it!