Skip to content

Install Tealhand Relay

Tealhand Relay passes encrypted traffic between your phone and your computers when they can’t reach each other directly, for example when you are away from home. You need one only for that. On your own network, no relay is needed. This page is the install. For what a relay sees and keeps, read Relays; for every config key and the upload answers, read Run your own relay.

  • A Linux server (a VPS) with a public IP address. The examples use 203.0.113.10 and relay.example.com.
  • Either a DNS name that points at the server (for a certificate from Let’s Encrypt), or nothing but the IP address (for a self-signed certificate that your devices pin).
  • Your phone, with Tealhand installed, to copy its owner key from.
  • Git, Rust through rustup, and a C compiler on the server, or build elsewhere and copy the binary over.
Terminal window
git clone https://github.com/tealvpn/tealhand
cd tealhand
rustup toolchain install
cargo build --release -p tealhand-relay
sudo install -m 755 target/release/tealhand-relay /usr/local/bin/tealhand-relay

Run the relay as its own normal user, never as root:

Terminal window
sudo useradd --system --home-dir /var/lib/tealhand-relay --create-home --shell /usr/sbin/nologin tealhand-relay
sudo mkdir -p /etc/tealhand-relay
sudo chown tealhand-relay:tealhand-relay /etc/tealhand-relay

In Tealhand, open Settings, then Network, and find Owner key. Copy it. It is your phone’s device public key in text form: thhw: and 66 hex digits. The relay admits only devices listed in a signed device list from an owner you name, so this key is how the relay knows your phone.

Save this as /etc/tealhand-relay/config.toml, with your own addresses and your owner key:

relay_bind = "203.0.113.10:443"
roster_bind = "203.0.113.10:8443"
allowed_owners = ["thhw:REPLACE_WITH_THE_OWNER_KEY_66_HEX_DIGITS"]
data_dir = "/var/lib/tealhand-relay"
[tls]
cert = "/etc/tealhand-relay/fullchain.pem"
key = "/etc/tealhand-relay/privkey.pem"
  • relay_bind is the relay itself (HTTPS). roster_bind is a second HTTPS listener where your phone uploads its device list. Both are required, and each needs its own address and port.
  • Both addresses must belong to the machine. 0.0.0.0 and [::] are refused, so the relay never listens on every interface by accident. On a server with its own public address, write that address. On a cloud server where the provider forwards the public address to the machine, write the machine’s private IP (ip addr shows it) and keep the public address in your DNS name or your phone’s settings.
  • allowed_owners takes one key per phone you own. An empty list allows nobody.
  • The key file must be readable by its owner only (chmod 600). The relay refuses a key that group or others can read.

Optional keys (http_bind, quic_bind, connection and rate limits) are in Relay config.

Pick one of the two ways.

Point a DNS name (here relay.example.com) at the server, then get a certificate with certbot’s standalone mode. Standalone mode needs TCP port 80 open and free while certbot runs; the relay does not use port 80.

Terminal window
sudo certbot certonly --standalone -d relay.example.com

Certbot keeps its files in /etc/letsencrypt/live/relay.example.com/, readable by root only. The relay runs as its own user, so a deploy hook copies the certificate and key to where the relay can read them, every time certbot renews. Save this as /etc/letsencrypt/renewal-hooks/deploy/tealhand-relay.sh:

#!/bin/sh
set -e
install -o tealhand-relay -g tealhand-relay -m 644 "$RENEWED_LINEAGE/fullchain.pem" /etc/tealhand-relay/fullchain.pem
install -o tealhand-relay -g tealhand-relay -m 600 "$RENEWED_LINEAGE/privkey.pem" /etc/tealhand-relay/privkey.pem

Make it executable and run it once now:

Terminal window
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/tealhand-relay.sh
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/relay.example.com /etc/letsencrypt/renewal-hooks/deploy/tealhand-relay.sh

The relay re-reads the certificate and key once a day, so a renewal needs no restart. The phone and the computers check this certificate against the public roots, so you enter no fingerprint anywhere.

Option B: a self-signed certificate and a pinned fingerprint

Section titled “Option B: a self-signed certificate and a pinned fingerprint”

For a server with no DNS name, make a certificate that names the IP address, and let your devices pin it: they then accept only this one certificate, recognised by its SHA-256 fingerprint, and refuse every other. The name is not checked; the certificate’s dates still are.

Terminal window
sudo -u tealhand-relay openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 825 \
-subj "/CN=tealhand-relay" -addext "subjectAltName=IP:203.0.113.10" \
-keyout /etc/tealhand-relay/privkey.pem -out /etc/tealhand-relay/fullchain.pem
sudo chmod 600 /etc/tealhand-relay/privkey.pem

Then print the fingerprint (the relay also logs it at every start):

Terminal window
tealhand-relay fingerprint --config /etc/tealhand-relay/config.toml
sha256:<64 hex digits>

Pin it on both sides:

  • Phone: Settings, then Network, then My own relay. Fill in the relay address https://203.0.113.10, the device list upload address https://203.0.113.10:8443/tealhand/v1/roster, and paste the fingerprint into Certificate fingerprint.
  • Each computer: tealhand-host connect --relay https://203.0.113.10 --relay-pin sha256:<64 hex digits>. connect saves both. run takes the same two options to use another relay for one start.

A new certificate has a new fingerprint: pin the new one on the phone and on every computer. Don’t pin a certificate that renews by itself (Let’s Encrypt); leave the field empty and the public roots check it. There is no switch that turns certificate checking off.

Port Why Needed
TCP 443 (relay_bind) The relay itself Always
TCP 8443 (roster_bind) Your phone’s device list upload Always
TCP 80 certbot’s standalone challenge Only while certbot runs, option A
UDP 7842 (quic_bind) Address discovery Only if you turn on quic_bind

With ufw:

Terminal window
sudo ufw allow 443/tcp
sudo ufw allow 8443/tcp

If your provider has its own firewall or security group, open the same ports there. A normal user can’t listen on a port below 1024 on its own. The unit below gives the relay that one right.

Save this as /etc/systemd/system/tealhand-relay.service. It is an example, not a file the repository ships:

[Unit]
Description=Tealhand Relay
After=network-online.target
Wants=network-online.target
[Service]
User=tealhand-relay
ExecStart=/usr/local/bin/tealhand-relay --config /etc/tealhand-relay/config.toml
AmbientCapabilities=CAP_NET_BIND_SERVICE
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Terminal window
sudo systemctl daemon-reload
sudo systemctl enable --now tealhand-relay
sudo journalctl -u tealhand-relay

At start it logs where the source code of this version lives, the addresses it listens on and, with a certificate it can read, the certificate fingerprint. A config problem exits with code 2 and names the setting. Every error is two lines: what happened, then what to do.

  • Phone: Settings, then Network, then My own relay: the relay address and the upload address, both starting with https://. With option A they are https://relay.example.com and https://relay.example.com:8443/tealhand/v1/roster. The phone has no default for the upload address. Changing it reconnects your devices and loses nothing.
  • Each computer: tealhand-host connect --relay https://relay.example.com, then tealhand-host run. The choice is saved at connect.

Settings, then Network, shows under Right now whether the phone reaches each computer directly or via the relay. Away from home it should say via relay.

Next: Connect your phone.