Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Setup for an easy to use, simple reverse http tunnels with nginx and ssh. It's that simple there's no authentication at all. The end result, a single ssh command invocation gives you a public url for your web app hosted on your laptop.

What

A lot of times you are developing a web application on your own laptop or home computer and would like to demo it to the public. Most of those times you are behind a router/firewall and you don't have a public IP address. Instead of configuring routers (often not possible), this solution gives you a public URL that's reverse tunnelled via ssh to your laptop.

Because of the relaxation of the sshd setup, it's best used on a dedicated virtual machine just for this (an Amazon micro instance for example).

Requirements

Server side:

  • a server with a public ip (1.2.3.4 in this document)
  • a domain name (domain.tld in this document)
  • a wildcard dns entry in the domain pointing to the public ip (*.ie.mk. 1800 IN A 1.2.3.4)
  • nginx
  • sshd

Client side:

  • ssh client (even plink would work on Windows)

Nginx config

A wildcard dns should point to this nginx instance. Every www<port>.domain.tld will be proxied to 127.0.0.1:<port>

Where <port> needs to be 4 or 5 digits.

server {
  server_name   "~^www(?<port>\d{4,5})\.domain\.tld$";

  location / {
    proxy_pass        http://127.0.0.1:$port;
    proxy_set_header  X-Real-IP  $remote_addr;
    proxy_set_header  Host $host;
  }
}

SSH configuration

A sshd configuration to allow a user with no password and a forced command, so that the user can't get shell access.

Match User tunnel
  # ChrootDirectory
  ForceCommand /bin/echo do-not-send-commands
  AllowTcpForwarding yes
  PasswordAuthentication yes
  PermitEmptyPasswords yes

PAM needs to be disabled if sshd is to allow login without a password. That's not always possible, is not even smart. Another approach would be a separate instance of sshd, on a different port, just for the tunnel user.

Make a copy of the config file, change/add these settings:

UsePAM no
AllowUsers tunnel
Port 722

And then run sshd -f /etc/ssh/sshd_config_tunnel.

The tunnel user has an empty password field in /etc/shaddow.

tunnel::15726:0:99999:7:::

Client

Just connect with:

ssh -N -T 1.2.3.4 -l tunnel -R 0:localhost:5050 -p 722

ssh will respond with a Allocated port 56889 for remote forward to localhost:5050 message. Then you can use www56889.domain.tld

TODO

Test ChrootDirectory in sshd

#! /bin/sh
local_port=$1
ssh_server=1.2.3.4
ssh_user=tunnel
ssh_port=722
url_tmpl=http://www\\1.domain.tld/
exec 3>&1
eval ssh -N -T $ssh_server -l $ssh_user -R 0:localhost:$local_port -p $ssh_port 2>&1 1>&3 \
| sed 's|^Allocated port \([[:digit:]]\+\) for remote forward to|Your url is '$url_tmpl' will be forwarded to|'
@marcellmars

This comment has been minimized.

Copy link

commented Jan 21, 2013

brillitant. my dear yoda.

@andineck

This comment has been minimized.

Copy link

commented Apr 17, 2014

thx a lot! you rock!

@gkop

This comment has been minimized.

Copy link

commented Mar 13, 2015

I found this to work great on Linux, but had difficulty with the sed command on OSX. I decided to switch to perl since it is more standard across *nix systems:

#! /bin/sh

local_port=$1
ssh_server=1.2.3.4
ssh_user=tunnel
ssh_port=722
url_tmpl=http://www\$1.domain.tld/

exec 3>&1
eval ssh -N -T $ssh_server -l $ssh_user -R 0:localhost:$local_port -p $ssh_port 2>&1 1>&3 \
    | perl -pe "s#Allocated port (\d+) for remote forward to ([^\s]+)#Success! \$2 is now being forwarded to $url_tmpl#"
@ilanni2460

This comment has been minimized.

Copy link

commented Aug 18, 2015

very good

@gkop

This comment has been minimized.

Copy link

commented Dec 3, 2015

Folks, be aware the above nginx config may be unnecessarily loose with regard to the ports it forwards, depending on your environment. For example sshd will dynamically allocate a free ephemeral port in Linux on the range 32768 to 61000 and in some other environments on the IANA recommended range 49152 to 65535. Therefore in such environments it is unnecessary and imprudent to match ports 0-99999 (eg. 00022 gives you port 22) with \d{4,5}. A quick and better match for Linux is on [3-6]\d{4}. Unfortunately regex doesn't provide a convenient way to lock the match down precisely to the desired range.

@dejlek

This comment has been minimized.

Copy link

commented Dec 7, 2015

Hell, this is exactly what I was looking for! Thanks man!

@simkimsia

This comment has been minimized.

Copy link

commented Dec 6, 2016

I don't quite get this.

So imagine we have 3 machines:

  1. your laptop at home running the web application (let's call this L)
  2. a server with a public ip address and domain which is basically acting as a middleman of some sorts (let's call this M for middleman)
  3. a client machine which is trying to access your web application hosted on a laptop at home using its browser (let's call this C)

So SSH Config and Nginx Config are on the M machine?

no config or any steps needed on the L machine?

@kenichi-shibata

This comment has been minimized.

Copy link

commented Mar 29, 2017

Or you can use ngrok

@gboddin

This comment has been minimized.

Copy link

commented Jun 16, 2017

Brilliant, thanks !

@askz

This comment has been minimized.

Copy link

commented Jul 31, 2017

Or you can use http://serveo.net/
DISCLAIMER:
I'm not the official developper but I loved the idea, simpler and more out-of-the-box than ngrok! No setup required, just your ssh client.
The plus is the request & response analyzer/replayer ! Give it a go ;)

@shahnwazsheikh00

This comment has been minimized.

Copy link

commented Oct 20, 2018

its very good techniques

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.