Self-Hosting With SSH Forwarding
PublishedIf you’re like me then you’re cheap and you don’t want to pay Amazon or Microsoft or Google or whomever hundreds of dollars a month so that you can get some decently sized hardware connected to the internet. For example, it used to cost me about $15 a month to get 4GB RAM, 2 rate limited CPUs, and 100GB of disk in the cloud. If I wanted something reasonably sized for a server like 16GB RAM or 32GB RAM and maybe a few hundred gigabytes of storage, now I’m talking more than a hundred bucks a month for a hobby. That is absurd.
So what can you do? Here’s what I did and here’s how you can do it, too.
I went out and bought a reasonably sized computer. I actually ended up with an Intel NUC which works well enough. I installed an operating system on it and set it up as a server for my website and some other projects. Connecting it to the internet so that it can serve up some content is the hard part. Sure, you can open some ports on your home router. However, I guarantee that (1) your ISP will decide at some point that they don’t like you having port 80 and/or 443 open or, more likely, (2) your home IP address will change at some point because your home internet connection uses DHCP and all of your stuff will break until DNS catches up. Additionally, just opening ports means that anyone who knows your website can reasonably determine your physical location. This solution obscures that piece of information, too.
To work around these issues let me introduce SSH forwarding. We are going to have a tiny server running in the cloud which will give us our static public IP address. Then we are going to have our home computer create a tunnel from the cloud computer back to the home computer. The tunnel will be initiated on the home side. No firewall needs to be opened on the home side.
Setting this up requires:
- A computer in your home that you want use to serve content to the internet. This computer does not need a static IP and in fact it can use DHCP from your home router.
- A tiny server in the cloud to function as a proxy server. This cloud server requires very minimal requirements. I pay less than $5 per month for mine.
- SSH keys such that your home computer can connect to your cloud computer without entering a password.
- A daemon like autossh running on your home computer to maintain an active SSH connection to your cloud computer.
- A daemon like haproxy running on your cloud computer to tunnel the connections to your home computer.
I’m going to assume that you’ve set up the computers and the SSH keys and so we’ll just focus on the last two steps.
First, you will want to install autossh
on your home computer. This tool will maintain the SSH connection and reconnect if it ever drops. You can install it with this command:
apt-get install --no-install-recommends autossh
You’ll configure autossh
by creating and editing the /etc/default/autossh
file and modifying it like this:
# how long ssh must be up before we consider it a successful connection
AUTOSSH_GATETIME=0
# sets the connection monitoring port -- "0" turns off monitoring
AUTOSSH_PORT=0
# these are the options that we'll pass to ssh for forwarding
# we are establishing two connections:
# 1. cloud side: /etc/ssh/remote.sock, home side: localhost:22
# 2. cloud side: /etc/haproxy/remote.sock, home side: /etc/haproxy/remote.sock
SSH_OPTIONS="-N -T -o 'StreamLocalBindUnlink=yes' -o 'ServerAliveInterval=60' -o 'ServerAliveCountMax=3' -R /etc/ssh/remote.sock:127.0.0.1:22 -R /etc/haproxy/remote.sock:/etc/haproxy/remote.sock cloud-server.example.com"
You’ll note that we establish two connections and both of them use Unix sockets instead of TCP sockets. This is because the Unix sockets, in my experience, are more resilient and have better performance. However, with Unix sockets you’ll need to make sure that the socket file is removed before trying to create it so we set the StreamLocalBindUnlink
option. However, that only covers the local side. We need to add the same option to the /etc/ssh/sshd_config
file on the cloud side:
# clean up unix sockets before re-using them
StreamLocalBindUnlink yes
You’ll need to configure systemd
to start autossh
by creating and editing /etc/systemd/system/autossh.service
:
[Unit]
Description=autossh
Wants=network-online.target
After=network-online.target
[Service]
User=root
EnvironmentFile=/etc/default/autossh
ExecStart=/usr/bin/autossh $SSH_OPTIONS
Restart=always
RestartSec=60
[Install]
WantedBy=multi-user.target
You can now activate autossh
with these commands:
systemctl daemon-reload
systemctl enable autossh
systemctl start autossh
Once you’ve enabled autossh
on your home server, if it successfully starts then you should see a these files on the cloud server:
/etc/haproxy/remote.sock
- this socket will connect to the home server for HTTP requests/etc/ssh/remote.sock
- this socket will connect to the home server for SSH connections
You can now SSH into your home server through your cloud server by adding this to your ~/.ssh/config
file on your workstation:
Host cloud-server.example.com
ProxyCommand ssh cloud-server.example.com sudo socat - UNIX-CLIENT:/etc/ssh/remote.sock
The reason for this weird proxy command is because (1) the Unix socket may not be owned by your user so you’ll need to use sudo
to access it and (2) the SSH client doesn’t know how to connect to Unix sockets so you need to use socat
to translate from TCP in order to make the connection.
Now that you have the ports forwarding, you need to configure HAProxy on the cloud side to actually make the connection. To do that, start by installing HAProxy on your cloud server:
apt-get install --no-install-recommends haproxy
With HAProxy installed, it’s a very simple configuration to add to /etc/haproxy/haproxy.cfg
:
listen http-in
bind :::80 v4v6
mode http
log global
redirect scheme https code 301
listen https-in
bind :::443 v4v6
mode tcp
server localhost /etc/haproxy/remote.sock send-proxy-v2-ssl-cn
Of note is the send-proxy-v2-ssl-cn
option. With this option I’m using the PROXY protocol to ensure that the source IP address of the client gets passed forward to your home computer so that it doesn’t appear that all of the requests are coming from localhost
.
On the home side, I configure HAProxy with this block:
frontend https-frontend
# listen for normal connections
bind :::443 v4v6 ssl crt-list /etc/haproxy/crt-list.txt
# listens for connections from our autossh connection
bind unix@/etc/haproxy/remote.sock user root mode 600 ssl crt-list /etc/haproxy/crt-list.txt accept-proxy
This way you can connect to your home computer normally over port 443 but when requests come in over the proxy connection then they will use the PROXY protocol.
Once you’ve got all this configured you can boot up the computer at home and have yourself a server just as though it were on the real internet. Note, however, a few drawbacks:
- When you run a server in the cloud you typically do not pay for incoming bandwidth but you do pay for outgoing bandwidth. This means that if someone decides to DDOS you then you’re typically OK because it’s just incoming traffic. However, this configuration will forward all of that traffic back down the pipe into your home and so you’ll pay for the bandwidth on the cloud provider side and your home connection will be hammered until you kill the forwarding configuration.
- While connections over the tunnel will all appear to be coming from your server and no one will really have any way to see where the server actually lives, if your server makes outgoing connections to do things like connect to another service or whatever then you will be revealing your home IP address which you may not want to do so be careful about how your web application connects back out to the world.
Good luck!