SSH server behind NAT
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.