Free Alternative to ngrok with Permanent Subdomain using Nginx, SSL, and reverse SSH Using Your Own Server

I enjoy using ngrok quite a bit to expose my laptop temporarily to the world for testing or demoing.  However, every time I have to restart ngrok, the subdomain changes unless I pay $60/year for a better account.  I’m not against paying, in fact, I support paying for software that I use, but being a tinkerer, there’s gotta be a better and cheaper way using my currently available resources.

Here are my requirements for the replacement solution:

  • Free: to use an existing server.
  • Easy to start:  similar to ngrok, locally, I should only need to run a SSH command to connect to the remote server and start forwarding the local port.  It has to be DEAD SIMPLE.
  • Permanent:  the public endpoint needs to be permanent.  I only need to configure the DNS once and forget about it.
  • SSL: The endpoint needs to serve through SSL, using Let’s Encrypt.
  • Secure: I can disconnect anytime and the tunnel is dead.

I found a post from StackOverflow on setting up the SSH side of thing, and I was able to configure the whole process through and I’m quite happy with the result.

So to save you the trouble, let me walk you through the whole setup.

0. What you need to make this work:
  1. A server with root access (to configure nginx and certbot)
  2. Access to modify your DNS
  3. 15 minutes of your time

Let’s start!

1. Configure DNS to Add Permanent Subdomain

You need to add a new A record to point to your server, like dev.mydomain.com.  This will be the fixed subdomain that the outside world will see.  In my case, I use dev.mydomain.com

2. Setup SSL with certbot

Let’s Encrypt provides a quick and easy way to implement SSL for your server.  The whole process is super simple.  You can read more on how to install Let’s Encrypt certbot on Digital Ocean’s Guide.

It boils down to:

    1. SSH to your box.
    2. Install certbot: (I’m using Ubuntu)
      sudo add-apt-repository ppa:certbot/certbot
      sudo apt install certbot
      sudo certbot --nginx -d dev.mydomain.com

(You can choose  option 2 when prompted by certbot)

certbot will show something like this:

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/dev.mydomain.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/dev.mydomain.com/privkey.pem
Your cert will expire on 2019-05-29. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"

I bolded the 2 important files that we will need to configure our Nginx.

3. Setup Nginx

We only need to add a new subdomain configuration for Nginx to start accepting traffic.  I prefer to isolate all configurations for a particular domain, or subdomain into its own Nginx configuration file.

  1. Add a new subdomain configuration to  /etc/nginx/sites-available/dev.mydomain.com (replace mydomain.com with your domain)
  2. Remember to replace the correct domain and the correct path to the fullchain.pem and privkey.pem files from step 2 above.
  3. Symlink the new configuration file
    cd /etc/nginx/sites-enable/
    sudo ln -s ../sites-available/dev.mydomain.com .
  4. Clean up the main nginx configuration file of the stuff that certbot inserted. sudo vi /etc/nginx/sites-available/default. You should remove all the references to the dev.mydomain.com part.  Just look for the # managed by cerbot comment that are relevant to the dev.mydomain.com and clean those lines up.
  5. Reload nginx:
    sudo nginx -s reload
  6. You should now be able to hit https://dev.mydomain.com and you’ll see a 503 error page.  This means that nginx is now serving the SSL version of the site but it’s unable to proxy to the local port 30000 that we define in the configuration.  Congratulations, you’re almost there.
4. Tunnel and Profit

On your local machine, we now open the connection to the remote server.

ssh -f -N -T -R 30000:localhost:3000 remoteserver

  • -f to background the ssh command after authentication so you can close the command line.  For testing, remove the -f flag.
  • -N to save resources as we’re not sending any commands.
  • -T to have a non-interactive session, e.g. you are not issuing any commands.  We only ssh in to create a tunnel bridge.
  • -R to tell SSH to forward all traffic on the remote server that hits port 30000 to forward to our local machine on port 3000.

You can read more about this on this excellent guide on StackExchange.

5. Testing

You should now be able to hit https://dev.mydomain.com and start seeing the content.

Conclusion

This process is pretty painless and it satisfies all of the above requirements.  As an extension, you can setup dev1, dev2, dev3 if you have multiple applications with different ports.

 

 

2 Comments

BN October 8, 2020 Reply

Hi, I am also using ubuntu. My local box has a fixed IP. I own a domain xxx.eu. Using my domain registrar, I created a subdomain yyy.xxx.eu. Again using the registrar, I also created DNS A entries for yyy.xxx.eu and http://www.yyy.xxx.eu pointing to my fixed IP.
I forwarded external port 18080 on the router to my box’s port 80, and external port 18443 to my box’s port 443.
I disabled my box’s firewall.

I then ran sudo certbot –nginx -d yyy.xxx.eu –http-01-port 18080 –https-port 18443.

The result was a failure:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for yyy.xxx.eu
Using default addresses 18080 and [::]:18080 ipv6only=on for authentication.
Waiting for verification…
Challenge failed for domain yyy.xxx.eu
http-01 challenge for yyy.xxx.eu
Cleaning up challenges
Some challenges have failed.

IMPORTANT NOTES:
– The following errors were reported by the server:

Domain: yyy.xxx.eu
Type: unauthorized
Detail: Invalid response from
http://yyy.xxx.eu/.well-known/acme-challenge/i9rUjP7e-NzVbMTpuRuepeNujX99QV2_w2jnAAEqIVg
[194.117.254.56]: “\n\n404 Not
Found\n\nNot Found\n<p"

To fix these errors, please make sure that your domain name was
entered correctly and the DNS A/AAAA record(s) for that domain
contain(s) the right IP address.

Do you have an idea what is going wrong?
Thanks,
BN

Leave a Reply to BN Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.