Roll your own ngrok in 15 minutes
How to expose your local server over nginx aka reverse proxy over ssh.
This is one of my favorite posts from a few years ago. It shows how a common service, like ngrok, that costs money, can be recreated in 15 minutes for a single developer.
I enjoy finding “hacks” like this. It shows sometimes, something that seems big and complex, can be made much simpler if you understand the foundations that thing is built on.
Hope you enjoy, I’m preparing a bunch of new content for 2024 that will be coming soon!
I recently saw a post about using ngrok for testing webhooks using a local development server. They charge $5 a month for reserved domains, and other features. This can be done with the power of ssh
, nginx and Let's Encrypt. The only catch is, you need your own server.
Benefits of this approach
You get control over your data, it's not going through a 3rd party
Many developers already have access to a web server, so it won't cost anything
You can setup unlimited domains with this approach, making it better and cheaper than ngrok
You can setup other things on the server and learn more about linux
Setup
I'll be using DigitalOcean to create a cheap $5 server. If you already have a linux server running somewhere, preferably Ubuntu, you can follow along.
Let's go into the DigitalOcean dashboard and create a new $5/mo droplet. If you already have a server with open ssh installed, you can skip this and get all the benefits of ngrok for free. I'll be using an Ubuntu 19.04
droplet with ssh key authentication. Setting up ssh keys is out of scope for this guide. Use password authentication if you must, although ssh keys are the most secure, and can be automatically installed to a server created with DigitalOcean.
Install some tools
Now that we created a server, let's ssh into it:
ssh [email protected]
You should definitely disable root logins, password authentication, and other security measures, but we will not worry about that in this guide.
sudo apt-get update
sudo apt-get install nginx -y
sudo apt install certbot -y
sudo apt-get install -y python-certbot-nginx
Setup Nginx
Let's create an nginx configuration for our site. I'll use vim:
vim /etc/nginx/sites-enabled/server.conf
The contents will be:
upstream tunnel {
server 127.0.0.1:8888;
}
server {
server_name tutorial.zach.codes;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://tunnel;
}
}
This configuration tells nginx we have a tunnel running on port 8888. Our local computer will send traffic through this port using ssh after we finish setup. Next, we need to change tutorial.zach.codes
to be a domain name that you own, and is pointed to our server. Using DigitalOcean, you can go to the Networking
section and add DNS records for any domains hosted with them. This will vary based on your hosting provider:
After this record is created, and you changed server_name tutorial.zach.codes;
to your domain name, save the file.
Setup HTTPS
Next up, we can run one easy command to get https working on our server:
sudo certbot --nginx -d tutorial.zach.codes
Once again replacing the domain with your own. It'll ask for your email so that it can notify you of any issues or renewals. After it completes, you will now have the correct configuration in the nginx file we first created to serve our tunnel over https.
The really neat part is that certbot automatically adds a cron job to renew this certificate with the command we just ran. So there is no work to do with renewing anything.
We're done!
Now we can try out our freshly created, self-hosted solution to forwarding our traffic over the internet. I have a development server running on port 3000. The command below will forward it to our server:
ssh -R 8888:localhost:3000 [email protected]
Once again, replacing the domain with your own.
This command tells your server that any requests to 8888
on it should go to port 3000
on our local machine. The command is a little hard to memorize, so I've added this function to my ~/.zshrc
file:
forward () {
ssh -R 8888:localhost:$1 [email protected]
}
Anytime I want to expose something publicly, I can type forward 3000
in my terminal. Incredibly simple, and since I already pay for a small server that hosts this website, I can do everything ngrok can do for free.
I hope you enjoyed this article. This stuff hasn't always been so easy to do on your own. Recent Let's Encrypt updates have made this process really simple.
Once again, I recommend setting up a firewall like csf, disabling root logins, and blocking all ports except 80 and 443 if you plan to keep this server up and running.