Build a 10 USD Raspberry Pi Tunnel Gateway

In this tutorial I'll show you how to build an Internet Gateway for your home network using a Raspberry Pi and a HTTPS tunnel for just 10 USD.

You can achieve a similar effect of an Internet gateway by enabling port-forwarding on your home router, however there are downsides to this.

  • Your customer identifier / location is disclosed to users of your services
  • Some ISPs block port 80/443
  • Others give you a DHCP IP which changes often
  • You might not be able to port-forward, perhaps you're behind Carrier Grade NAT, or on a shared WiFi network, where you're not the administrator - think hot-spot, hotel, or university accomodation

An Internet gateway with the inlets OSS project will provide a persistent connection behind the trickiest of networks and let you expose your private and LAN services of choice to the Internet.

Why might you want to get access to a service running on your private network? Perhaps you're running an OwnCloud, Plex, a blog, or some other admin dashboard or panel. Maybe you want to build a Twitter, Slack, or GitHub bot and receive webhooks? These are all valid use-cases and I'm sure there's loads more you can think of.

These are the parts you'll need:

You can ⭐️ star or fork inlets on GitHub

Tutorial

Flash Raspbian Lite to your SD card

etcher

We'll access the RPi headless, via SSH. So before you unmount the SD card, make sure you create a file in the boot partition called ssh.

On MacOS: sudo touch /Volumes/boot/ssh

If you're going to use WiFi for the gateway, then create a wpa_supplicant.conf file in the boot partition too.

country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={ 
    ssid="MySSID"
    psk="mypasswd"
}

Log in for the first time

Now log into the Raspberry Pi via ssh, you should add your ssh key on the device so that you don't need to log in with a password.

Note: the RPi Zero is the slowest version available, so it may take a few mintues for the initial boot, which involves resizing the root partition to fill the SD card.

# Check if you have a key, if not, run `ssh-keygen` and hit enter to everything
ls ~/.ssh/id_rsa.pub

ssh-copy-id pi@raspberrypi.local

Now log in and customise the RPi:

sudo raspi-config
  • Change Password. Make sure you set a password other than the default
  • Advanced Options -> Memory Split. Set the memory split to 16MB
  • Enter Network Otions -> Hostname. Set the hostname to whatever you want, I picked inlets-gw1

I like to remove the message of the day - sudo rm -rf /etc/motd

Don't reboot yet.

Harden the RPi

Now we'll prevent password logins over SSH, and only allow SSH keys to be used.

sudo nano /etc/ssh/sshd_config

Add a line: PasswordAuthentication no

Now reboot.

Install inlets

inlets is a reverse proxy and a network tunnel that will run on the Raspberry Pi and act as a gateway / router for any services we wish to expose.

Inlets diagram

In the diagram, the user wants to expose localhost:3000 to the Internet via DNS entry site.com, but we'll be using the RPi not to host services, just to act as a gateway. We'll be forwarding services running on our LAN.

The Raspberry Pi will run the inlets client software and decide what services to proxy where. It is the gateway.

For the exit-server we'll provision a host on public cloud, such as DigitalOcean. This host will provide a public IP and will run inlets server. Our RPi will connect to it.

If you'd like to know more, then you can read all about inlets on GitHub.

Our hostname is now inlets-gw1, so use that to connect.

ssh pi@inlets-gw1.local

Get inletsctl which can create inlets exit-servers and download the inlets client/server binary.

curl -sSLf https://inletsctl.inlets.dev/ | sudo sh

Get the inlets command:

sudo inletsctl download

# Check it worked

inlets version
 _       _      _            _
(_)_ __ | | ___| |_ ___   __| | _____   __
| | '_ \| |/ _ \ __/ __| / _` |/ _ \ \ / /
| | | | | |  __/ |_\__ \| (_| |  __/\ V /
|_|_| |_|_|\___|\__|___(_)__,_|\___| \_/

Version: 2.6.3
Git Commit: c2033ecaaef9381d975a3fcadb86011056865fb9

Create an exit server

If you're a DigitalOcean or AWS customer, then sign up for an API key in your dashboard. Let's say you're using DigitalOcean, save the file on the local filesystem.

Feel free to use this referral link to grab a few credits.

inletsctl create \
 --provider digitalocean \
 --access-token-file $HOME/do-access-token \
 --region lon1

I live close to London, so I'm opting for a server with low-latency in the lon1 region.

You can explore the other options available via inletsctl create --help

After about 20-30 seconds inletsctl will print out the inlets client connection string, auth token and the public IP you can use for the public-facing side.

Using provider: digitalocean
Requesting host: hungry-raman0 in lon1, from digitalocean
2020/01/17 15:50:26 Provisioning host with DigitalOcean
Host: 176033357, status: 
[1/500] Host: 176033357, status: new
[2/500] Host: 176033357, status: new
[3/500] Host: 176033357, status: new

The final output is when the host is up and running with inlets server started and running with systemd.

Inlets OSS exit-node summary:
  IP: 178.62.90.237
  Auth-token: wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr

Command:
  export UPSTREAM=http://127.0.0.1:8000
  inlets client --remote "ws://178.62.90.237:8080" \
	--token "wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr" \
	--upstream $UPSTREAM

To Delete:
  inletsctl delete --provider digitalocean --id "176033357"

Now you need to decide what you want services you want to proxy to the Internet.

Take note of the deletion command, which you can use to delete the exit-server later. The cost is around 5 USD / mo, which is pretty low for a public IP and compute. You can also delete the server in your dashboard if you lose the string.

Start a service in your network, which you want to expose

I'll start you off with my idea, but you can choose anything you want providing it works over HTTP/HTTPS. For pure TCP traffic, you can do exactly the same steps, but use inlets-pro instead.

On my Mac with IP 192.168.0.14 I decided to run Gitea, which is a lightweight GitHub replacement. You can use it to store your side-projects and to collaborate with friends.

I downloaded the binary, and then ran it. It looks like it exposes port 3000 for its UI.

wget https://dl.gitea.io/gitea/1.9.6/gitea-1.9.6-darwin-10.6-amd64
chmod +x gitea-1.9.6-darwin-10.6-amd64
./gitea-1.9.6-darwin-10.6-amd64

2020/01/17 15:52:42 ...dules/setting/log.go:226:newLogService() [I] Gitea v1.9.6 built with GNU Make 4.1, go1.12.13 : bindata, sqlite, sqlite_unlock_notify
2020/01/17 15:52:42 cmd/web.go:151:runWeb() [I] Listen: http://0.0.0.0:3000
2020/01/17 15:52:42 ...ce/gracehttp/http.go:142:Serve() [I] Serving [::]:3000 with pid 1652

Bring up the gateway

So now run command from earlier, but customise the UPSTREAM to patch port 3000 from Gitea, and change 127.0.0.1 to the IP of your machine on the LAN, i.e. 192.168.0.14.

export UPSTREAM=http://192.168.0.14:3000
inlets client --remote "ws://178.62.90.237:8080" \
--token "wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr" \
--upstream $UPSTREAM

2020/01/17 15:58:58 Welcome to inlets.dev! Find out more at https://github.com/inlets/inlets
2020/01/17 15:58:58 Starting client - version 2.6.3
2020/01/17 15:58:58 Upstream:  => http://192.168.0.14:3000
2020/01/17 15:58:58 Token: "wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr"
INFO[0000] Connecting to proxy url="ws://178.62.90.237:8080/tunnel"

Use the exit-server's IP to connect to Gitea.

http://178.62.90.237

We have now deployed an Internet gateway for 10 USD, which has a public IP address and can obtain as many as we like for the cost of running a tiny DigitalOcean droplet. If you really like things cheap, you can run on a GCE or EC2 VM within the free tier offered by the service provider.

tea

Tunnel multiple services with your gateway

inlets supports multiple services for a single exit-server and client. You can specify each with a unique sub-domain or host header, for instance.

Let's imagine we're running gitea on one PC and then on a Raspberry Pi with IP 192.168.0.100 we're running a Node.js microservice running on port 3000.

--upstream gitea.example.com=http://192.168.0.14:3000,nodejs.example.com=http://192.168.0.100:3000

Then, you'd just create two DNS A records pointing at the public IP for:

  • gitea.example.com
  • nodejs.example.com

The inlets server doesn't need any changes, simply restart your inlets client and then access your two sites using the domain names you created.

A domain name can be created for around 2 USD at namecheap.com, and managed on DigitalOcean for free.

Make your gateway permanent with systemd

We can now make the inlets client command start on boot-up, in this way we make our gateway permanent. You can even take your gateway and plug it in on another network, in effect "roaming".

Create /etc/default/inlets and add:

REMOTE=ws://178.62.90.237:8080
AUTHTOKEN=wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr
UPSTREAM=http://192.168.0.14:3000

Create /lib/systemd/system/inlets.service

[Unit]
Description=Inlets Client Service
After=network.target

[Service]
Type=simple
Restart=always
RestartSec=1
StartLimitInterval=0
EnvironmentFile=/etc/default/inlets
ExecStart=/usr/local/bin/inlets client --token="${AUTHTOKEN}" --upstream="${UPSTREAM}" --remote="${REMOTE}"

[Install]
WantedBy=multi-user.target

Install the service:

sudo systemctl daemon-reload
sudo systemctl enable inlets
sudo systemctl start inlets

You can now rest assured that systemd will manage the inlets client process and restart it if there was a network interruption or a crash.

sudo systemctl status inlets

● inlets.service - Inlets Client Service
   Loaded: loaded (/lib/systemd/system/inlets.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2020-01-17 16:08:02 GMT; 20s ago
 Main PID: 1047 (inlets)
   Memory: 1.1M
   CGroup: /system.slice/inlets.service
           └─1047 /usr/local/bin/inlets client --token=wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr --upstream=http://192.168.0.14:3000 --remote=ws://178.62.90.237:8080

Jan 17 16:08:02 inlets-gw1 systemd[1]: Started Inlets Client Service.
Jan 17 16:08:02 inlets-gw1 inlets[1047]: 2020/01/17 16:08:02 Welcome to inlets.dev! Find out more at https://github.com/inlets/inlets
Jan 17 16:08:02 inlets-gw1 inlets[1047]: 2020/01/17 16:08:02 Starting client - version 2.6.3
Jan 17 16:08:02 inlets-gw1 inlets[1047]: 2020/01/17 16:08:02 Upstream:  => http://192.168.0.14:3000
Jan 17 16:08:02 inlets-gw1 inlets[1047]: 2020/01/17 16:08:02 Token: "wgb3nGaiQDQvxFErYkt4yQPIX1TX2dkDsk4sILoHMe4tqjDMMwc8W704qqsBKqfr"
Jan 17 16:08:02 inlets-gw1 inlets[1047]: time="2020-01-17T16:08:02Z" level=info msg="Connecting to proxy" url="ws://178.62.90.237:8080/tunnel"

Check that Gitea is still accessible.

gitea

I'm not a gitea user, but you can edit /custom/conf/app.ini and update the root_url with your new IP, or a custom domain-name so that the URLs match in the dashboard.

I was able to clone the repo, add some content and then do a git push, but before we get carried away, read on.

Hardening with TLS

Only the pro version inlets has TLS built-in, for open source users, you'll have to add it yourself. Fortunately, this is fairly simple and well documented, just not automated.

If you want to add TLS, and you should, then follow this simple tutorial: HTTPS for your local endpoints with inlets and Caddy

Rather than specifying ws:// in your /etc/default/inlets file, you should put wss:// instead, but this is explained in the tutorial.

If you want to get automated TLS, you can try inlets-pro. inlets-pro can also provide a more flexible tunnel for your services, such as proxying SSH access to the gateway, or another computer within your network.

In the diagram below, you can see port 80 and 443 being forwarded over an encrypted link with inlets-pro, we can even run something like Caddy directly on our laptop and store HTTPS certificates from LetsEncrypt locally.

A word of warning

Developers wishing to use inlets within a corporate network are advised to seek approval from their administrators or management before using the tool. By downloading, using, or distributing inlets, you agree to the LICENSE terms & conditions. No warranty or liability is provided.

Wrapping up

In a very short period of time, using only OSS tools we were able to get up a secure Internet gateway on a 10 USD Raspberry Pi Zero W. Now you can expose any HTTP or HTTPS website to the Internet.

Using inlets-pro, we can make things even easier by encrypting the tunnel automatically, and even creating a gateway for any TCP traffic such as SSH, Kubernetes, or MongoDB. You can even bypass your gateway device completely, and run inlets or inlets-pro on your PC, laptop, home-lab, or on private cloud with packages and ports now available for Windows, Linux, MacOS, and FreeBSD.

Connect and learn

inlets is written in Golang and licensed as an Open Source project under MIT. Contributions are welcome in code, documentation, blog posts, and with testing.

See a live demo of inlets and inlets integrated into Kubernetes along with my other OSS work in this edition of DevOps and Docker with Bret Fisher.

Alex Ellis

Read more posts by this author.

Subscribe to Alex Ellis' Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!