Roll your own Ngrok with Nginx, Letsencrypt, and SSH reverse tunnelling
Ngrok is a fantastic tool for creating a secure tunnel from the public web to a machine behind NAT or a firewall. Sadly, it costs money and it’s proprietary. If you’re a developer, odds are that you’re already renting a server in the public cloud, so why not roll your own ngrok?
It turns out that you can do it using free, off-the-shelf tools, with no sophisticated scripting required! In this article, I’ll show you how.
Step 1. Configuring Nginx
Use a server block like this, so that incoming HTTP connections to
tunnel.yourdomain are reverse proxied into the application listening on
port 3333.
server {
server_name tunnel.yourdomain;
access_log /var/log/nginx/$host;
location / {
proxy_pass http://localhost:3333/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
}
error_page 502 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
With this configuration in place, suppose I visited
tunnel.yourdomain. Nginx will receive the connection, and see that it
should reverse proxy it. It will effectively pass the connection on to whatever
application is listening on port 3333. Currently, there is nothing listening
on this port, so we will get a 502 Bad Gateway or 404 Not Found error from
Nginx.
Let’s fix that.
Step 2. Using an SSH reverse tunnel
SSH reverse tunnelling port N to port K means making sshd listen on port N
and effectively transfer incoming connections over the SSH connection to the
SSH client. The SSH client will then transfer the connection to the application
listening on port K on the client machine.
Here’s the command to run on your client machine:
ssh -R N:localhost:K yourdomain
An interactive session on your server should begin; while it is open, the
reverse tunnel from port N to port K is active, and sshd will allow
connections originating only from localhost, i.e. your server.
Choosing N = 3333 will make it so Nginx reverse proxies incoming connections
on tunnel.yourdomain into sshd, over the SSH connection, and into the
application running on your local machine on port K.
To test this out, on your local machine, in one shell run
python -m http.server 8888 and in another shell run
ssh -R 3333:localhost:8888 yourdomain. Visit tunnel.yourdomain. You should see a
directory listing for whatever directory you were in when you ran the Python
command!
However, there’s a glaring problem with this setup.
Step 3. Securing the connection in the browser
The connection the browser is making to Nginx is at the moment not secure: it was a plain HTTP connection. You can fix this by obtaining a free TLS certificate with Letsencrypt, and using it to secure the connection the browser is making.
There are already excellent tutorials available on setting up Letsencrypt, so I won’t repeat that here. I recommend consulting the ArchWiki article here. Letsencrypt is a self-hosters dream-come-true since it is truly a set-it-and-forget-it type of thing. With the appropriate setup, (namely a simple systemd timer,) the certificate you get will renew itself when it its expiry is approaching.
Once you have a certificate, it suffices to adjust the Nginx server block above so it looks like this.
server {
server_name tunnel.yourdomain;
access_log /var/log/nginx/$host;
# These three lines are new.
listen 443 ssl;
ssl_certificate /path/to/tls/cert/fullchain.pem;
ssl_certificate_key /path/to/tls/cert/privkey.pem;
location / {
proxy_pass http://localhost:3333/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
}
error_page 502 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Only three lines need to be added!
Conclusion
With very little setup, we saw how to configure Nginx to act as a reverse proxy, and how to use an SSH reverse tunnel. By combining these off-the-shelf tools, we essentially replicated the core functionality of the fantastic tool Ngrok. Using this double-reverse-proxy technique, web applications running on a machine behind NAT or a firewall can be accessed easily and securely from a public domain or IP address.
If you have any comments or concerns, open an issue on Github.