A friend of mine was so excited about his Raspberry Pi and turning it into a small home server, littles did he know that you almost always get to put in a CGNAT pool in Turkey which restricts you having open ports and stuff for remote connections. We decided to use my Oracle Cloud VPS with a public IP as a relay for his remote connections. I'm explaning how I made his Pi reachable from anywhere through a small server I own with no port forwarding on the home router.
The problem
When youre in a CGNAT pool, many households share one public IP address, so you can make outbound connections but can't accept inbound ones. You need something with a public address that is reachable and ok with inbound connections. There are many services offer solutions to this problem but many are paid, limited, or depend on a third party.
The plan
You can't reach the Pi but Pi can reach to your server. So Pi opens an SSH connection to my VPS, asks it to listen on a port then forwards everything coming from there back to it's own ssh port. The person connecting logs into the VPS first, then hops through that listener to the Pi.
laptop → VPS (public IP) ← Pi (behind CGNAT)
127.0.0.1:2222 connects out and keeps
forwards to Pi:22 the tunnel open
Two details keep this safe; the tunnel listens only on the VPS’s localhost, so it isn’t exposed to the internet, and each account on the VPS is locked to the one thing it needs to do, so a leaked key can’t give anyone a shell on the server.
Steps
- On the Pi, create a dedicated key for the tunnel, with no passphrase so the service can start unattended.
ssh-keygen -t ed25519 -f ~/.ssh/tunnel_key -N "" cat ~/.ssh/tunnel_key.pub - On the VPS, create a user for the tunnel and give it the Pi’s public key.
adduserdoesn’t create~/.ssh, so make it yourself.
Replacesudo adduser --disabled-password tunnel sudo mkdir -p /home/tunnel/.ssh sudo tee /home/tunnel/.ssh/authorized_keys > /dev/null <<'EOF' restrict,port-forwarding,permitlisten="127.0.0.1:2222" ssh-ed25519 AAAA... pi-tunnel EOF sudo chown -R tunnel:tunnel /home/tunnel/.ssh sudo chmod 700 /home/tunnel/.ssh sudo chmod 600 /home/tunnel/.ssh/authorized_keysAAAA... pi-tunnelwith the real contents oftunnel_key.pub, all on one line.restrictdisables shells and everything else,port-forwardingturns forwarding back on, andpermitlistenallows only that single listening port. - On the Pi, install
autossh, which restarts the SSH connection whenever it drops.sudo apt install autossh - Still on the Pi, run the tunnel as a systemd service so it starts at boot and restarts on failure. Put this in
/etc/systemd/system/reverse-tunnel.service, after changing the user and key path to match the Pi’s username.
The[Unit] Description=Reverse SSH tunnel to VPS After=network-online.target Wants=network-online.target [Service] User=example-user Environment="AUTOSSH_GATETIME=0" ExecStart=/usr/bin/autossh -M 0 -N \ -o "ServerAliveInterval=30" \ -o "ServerAliveCountMax=3" \ -o "ExitOnForwardFailure=yes" \ -o "StrictHostKeyChecking=accept-new" \ -i /home/example-user/.ssh/tunnel_key \ -R 127.0.0.1:2222:localhost:22 \ tunnel@203.0.113.10 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target-Rline is the whole thing: listen on port 2222 on the VPS and send it to port 22 on the Pi.ExitOnForwardFailuremakes the service not run without a working tunnel and makes it fail. - Start the service and check that it stays up.
sudo systemctl daemon-reload sudo systemctl enable --now reverse-tunnel sudo systemctl status reverse-tunnel - On the VPS, confirm the listener exists.
You should see a line forss -tln | grep 2222127.0.0.1:2222. If you don’t, the Pi’s side probably isn’t connected yet. - The person who will connect needs their own key. On their laptop, generate one and share the public half.
ssh-keygen -t ed25519 cat ~/.ssh/id_ed25519.pub - On the VPS, create a jump account for that person. It can only forward to the tunnel port and can’t open a shell.
Again, replace thesudo adduser --disabled-password jumpuser sudo mkdir -p /home/jumpuser/.ssh sudo tee /home/jumpuser/.ssh/authorized_keys > /dev/null <<'EOF' restrict,port-forwarding,permitopen="127.0.0.1:2222" ssh-ed25519 AAAA... laptop EOF sudo chown -R jumpuser:jumpuser /home/jumpuser/.ssh sudo chmod 700 /home/jumpuser/.ssh sudo chmod 600 /home/jumpuser/.ssh/authorized_keysAAAA... laptopsection with your own information. - On the Pi, add the laptop’s public key so the person can log into the Pi itself with a key instead of a password.
echo 'ssh-ed25519 AAAA... laptop' >> ~/.ssh/authorized_keys - On the laptop, add a shortcut to
~/.ssh/config.ProxyJumpmakes SSH go through the VPS automatically.
NowHost mypi HostName 127.0.0.1 Port 2222 User example-user ProxyJump jumpuser@203.0.113.10ssh mypireaches the Pi from anywhere.
IP addresses, keys and stuff here are placeholders. Don't forget to replace 203.0.113.10 with your own VPS address, and you can pick any free port above 1024 in place of 2222 if you want to. 2222 is cool because SSH 22 heheh![]()
I know we could have used Tailscale or something similar but it was quite fun configuring this and now my VPS has some real use case too...