Bypassing NAT With Reverse SSH Tunneling

Warning: This post is over 365 days old. The information may be out of date.


I had recently moved to China, where I discovered that the carriers used CGNAT to combat the growing problem of shrinking IPv4 assignments. No surprises there, since China is literally booming with people. However, this posed a big problem for my home server and network, since in Korea I had my very own public IP address handed out to me.


But there is always a solution - and this time, the solution came in the form of SSH. Using SSH, I could forward literally any ports I wanted, from my local network, to a remote server that did have a public facing IP address.

In this tutorial, I’ll go over how to get your own remote server set up, and tunnel your very own home server to a remote server so that you could access it from anywhere.

1. DigitalOcean

You need a remote server to get started. I chose a VPS, because it is simple and easy to just spin up an instance. Hop onto DigitalOcean, make an account, link a credit card, and spin up an instance. Size doesn’t really matter - I chose the smallest instance on there, which is $5 a month.

Note down the IP address of your instance. From now on, I will be expressing this IP as XXX.XXX.XXX.XXX.


2. Server setup

Connect to your server using ssh. Do any server-maintenance stuff you need to do. I recommend this quick start article from DigitalOcean to add a sudo user to your server. After you’ve set up your server and secured it enough, proceed to the next step. I won’t be going into detail on server hardening, because that’s a topic for another day.

Then, you want to modify /etc/ssh/sshd_config, assuming you chose Ubuntu when you set up your VPS instance. Inside there, find the line named #GatewayPorts no and change it to GatewayPorts yes(remove the hash in the front and change no to yes.)

Save, exit, and type sudo service sshd restart. In theory you shouldn’t get kicked off from the server, but if you do, just reconnect. Finally, restart the server.

3. Client setup

Let’s say you have a port you want to forward on localhost, port 9000. You want to make it so that when you connect to the remote server, you could access it on port 80.

Client –> Remote server XXX.XXX.XXX.XXX (port 80) –> Home Server –> Web instance (port 9000)


To do that, on the home server type this SSH command:

ssh -nNT -R 80:localhost:9000 username@XXX.XXX.XXX.XXX

Let’s dissect this command. ssh is the program name itself, -nNT instructs SSH to not create a TTY instance (so you will NOT get a Terminal prompt), -R tells SSH we want to make a remote port forward, 80 is the port the remote server will be listening on, localhost is the IP the home server will forward requests to (in this case, it’ll be itself), 9000 would be the port that the home server would listen on, and the rest (username@XXX.XXX.XXX.XXX) is just the typical SSH details to connect to a server.

Boiling all this down to a practical example, I have a instance with an IP address of on my local network, such as a Raspberry Pi. It is listening on port 12984 and is hosting a web server. I want to see my website pop up when I connect to the remote server, so we would have to use port 80 or even port 443. To access this through a reverse tunnel, I would have to use this command on my home server:

ssh -nNT -R 80: username@XXX.XXX.XXX.XXX # HTTP
ssh -nNT -R 443: username@XXX.XXX.XXX.XXX # HTTPS
ssh -nNT -R 80: -R 443: username@XXX.XXX.XXX.XXX # or all in one

And this would be the diagram for how it would work:


4. AutoSSH

Sure, you could type these commands out every time you want a bridge! But personally, I’m lazy, and I want a script that does it for me every boot.

That’s where AutoSSH comes in.

sudo apt install autossh

Change to fit your distribution. In this case, I’m using Ubuntu Server. Then, we need to make a systemd configuration file.

nano /etc/systemd/system/autossh-tunnel.service

Paste the following in, adapting it for your use:

Description=AutoSSH tunnel service

ExecStart=/usr/bin/autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -o "ExitOnForwardFailure yes" -N -R 80: -R 443: -p 22 -i /home/example/.ssh/private-key


Then tell systemd to look for new configuration, and then enable the configuration:

sudo systemctl daemon-reload
sudo systemctl start autossh-tunnel.service
sudo systemctl enable autossh-tunnel.service

If you want to configure how SSH connects to the server, you could utilize ~/.ssh/config:

Host example-tunnel
    User example
    Port 22
    IdentityFile ~/.ssh/private-key
    RemoteForward 80
    RemoteForward 443
    ServerAliveInterval 30
    ServerAliveCountMax 3
    ExitOnForwardFailure yes

You don’t really need to specify RemoteForward here since AutoSSH will take care of that for you anyway, but I just put it there for peace of mind.

For more information, you could check out this great guide over here. I learnt a lot from that post, so thanks to the authors of that guide!