SSH server behind NAT

English
,

Hi!

I have an always-on Raspberry Pi at home, and once in a while I need to connect to something on my home network, or even exit to the internet as if I were at home (quite handy to access services that block datacenter/country IP ranges). This post documents all the steps needed to make it work.

Architecture

My home connection is behind a few of layers of (CG)NAT, so I can’t connect to it directly from outside my home network. Instead, I’ll be tunnelling through a VPS that I own. This approach consists of two parts: a persistent SSH tunnel between the Raspberry Pi and a VPS, and a connection from my laptop to the Raspberry Pi, through the SSH tunnel.

This approach isn’t quite performant, specially with Raspberry Pi 3B scoring low on SSH performance. I often see the latency increasing four fold through the tunnel (with everything being ~100ms apart of each other), and bandwidth is also expected to be limited (my tests yielded around 20Mbps, with the weakest link capped at 50Mbps). It works alright for my use case, but I wouldn’t expect to stream video through the tunnel.

Configuration

Raspberry Pi: generate SSH key

Generate a SSH keypair for the root user of the Raspberry Pi. Copy the public key to configure in the VPS.

VPS: dedidated user for the tunnel

Assuming you already can SSH into your VPS from anywhere, we now need to allow the Raspberry Pi to create a tunnel to the VPS. I’m setting up a dedicated Linux user for this:

1
root@vps# adduser -m -s /usr/sbin/nologin ssh-a1b2c3d4

Then configure the .ssh/authorized_keys of this user:

1
command="/usr/sbin/nologin",port-forwarding,permitlisten="127.0.0.1:50001",no-pty ssh-public-key-goes-here

The extra parameters at the beginning make sure the user can only setup the tunnel and forward ports, but has no access to a shell.

Raspberry Pi: setup the persistent tunnel

I’m using autossh, as it monitor the connection and restarts if it becomes stale. Quite handy if your home connection isn’t stable. To make it persistent across reboots, I’m setting it up as a systemd unit:

1
2
3
4
5
6
7
8
9
10
11
12
13
# cat /etc/systemd/system/sshtunnel.service
[Unit]
Description=SSH Tunnel
After=network.target

[Service]
Restart=always
RestartSec=20
User=root
ExecStart=/usr/bin/autossh -M 0 -nNT -o ServerAliveInterval=120 -R 127.0.0.1:50001:localhost:22 ssh-a1b2c3d4@vps-ip-address

[Install]
WantedBy=multi-user.target

This will bind the port 50001 in the VPS to the port 22 of the Raspberry Pi.

Don’t forget to run systemctl enable sshtunnel and systemctl start sshtunnel.

Usage

To make use of the tunnel, run:

1
$ ssh -J vps-user@vps.ip.goes.here -p 50001 pi-user@127.0.0.1

This will connect to the pi-user in the Raspberry Pi, via the SSH tunnel we set up earlier. To use the same tunnel as a SOCKS5 proxy, so I can exit to the internet as if I was home, use:

1
$ ssh -J vps-user@vps.ip.goes.here -p 50001 pi-user@127.0.0.1 -D 1337 -q -C -N

This opens a SOCKS5 proxy on port 1337 on my computer, which then I can configure in the browser/system.

.ssh/config

To simplify usage it’s possible to run ssh pi-home and connect to it from anywhere. Add those lines to the .ssh/config of the computer that will access the Raspberry Pi:

1
2
3
4
5
6
7
8
9
Host vps
  HostName vps.ip.goes.here
  User vps-user

Host pi-home
  HostName 127.0.0.1
  Port 50001
  User pi-user
  ProxyJump vps

Thank you.