A host behind a relay

For a host at home that has no stable address and cannot accept connections (behind a router, or behind carrier-grade NAT). You rent a small VPS with a stable address. The host opens a tunnel out to it, so no port is opened at home, and other devices connect to the VPS, which forwards the connection through the tunnel. The VPS company and the relay are not trusted: the relay holds no secret and forwards bytes it cannot read.

What you need

  1. A VPS with a stable public IPv4 address and root access over SSH. Any provider will do; 1 vCPU and 1 GB of memory is enough. Debian or Ubuntu (the installer uses apt); it was tested on Debian 13, and Ubuntu is expected to work but has not been tested.
  2. Two DNS names, both A records pointing at the VPS's public address: one for the relay (relay.example.org) and one for your host (home.example.org). DNS only: do not put them behind a proxying or CDN service, which would break the pass-through.
  3. Open ports on the VPS, in the provider's firewall and on the machine: inbound TCP 22 (SSH), 80 (the relay's certificate check) and 443.
  4. The computer with your model (Linux x86_64, systemd), as in the other guide.
  5. The app on your phone or Mac. You do not need its address: the host installer prints a one-time claim code, and typing it in the app makes you the host's administrator.

How the parts fit together

How the parts fit togetherYour Mac and iPhone connect to your host, either directly or through a relay on a rented server. The host opens the tunnel to the relay from the inside. Your Macthe app, your key Your iPhonethe app, your key Your hostruns your model, holds your datahas its own key and certificate Relay (optional)a rented server, forwards bytes 1. directly, when the host has a stable address 2. or through the relay the host opens this tunnel itself, outward Every connection ends at the host and is checked against the certificate the host signed with its key.
The app and the host know each other by key. A relay is only a way to be reachable when the host sits behind a router.

Step 1: install the relay on the VPS

Log in to the VPS and run, as root. --name is the relay's DNS name:

curl -fsSL https://euboulia.app/install.sh | sudo bash -s -- relay \
  --name relay.example.org

Add --dry-run first to see everything it would do. It installs nginx, certbot and the OpenSSH server; creates two unprivileged users (one runs the relay's status service, one is the tunnel user with no shell); configures nginx to split port 443 by the name the client asks for (the relay's own name goes to the status service, every other name goes through the tunnel without being decrypted); and asks Let's Encrypt for a certificate for the relay's own name only, using port 80. A certificate authority is involved only there, for the relay's own HTTPS (the status service); your host's connection never depends on one.

By default it checks that the name points at this machine, using a public address lookup service (api.ipify.org, a third party: pass --skip-dns-check to avoid it). When it finishes it prints the options to use for the host, including --relay-hostkey "ssh-ed25519 AAAA...", the relay's SSH host key. Copy that line.

Check it: curl https://relay.example.org/health prints ok.

Two installers, two kinds of port

The relay installer runs as root on the VPS: it installs nginx and has to listen on the privileged ports 80 and 443. The host installer runs as your ordinary user at home and never uses sudo, so the host cannot listen on a port below 1024; that is why its own port is a high one (8443 here) and why people never connect to it directly. The two ports 8443 below are on different machines and do not meet.

Which port is which

PortOnOpen to the internet?What it is
22VPSyesSSH, for you and for the host's tunnel
80VPSyesthe certificate check for the relay's own name
443VPSyeswhere the app connects, for the relay's name and for your host's name
19443VPSno, local onlywhere the tunnel arrives; nginx hands your host's traffic to it
8443home computerno, local onlythe host's own HTTPS port, reached only through the tunnel

Do not put the relay and the host on the same machine. The relay uses 8443 on its own machine (a local port that nginx hands its own name to). If the host were installed there with the default --tls-port 8443, the two would clash. The relay installer stops and says so if 80, 443 or 8443 is held by another program; the host installer stops if its port is taken. If you must, give the host another port, for example --tls-port 18443. The point of a relay is a separate machine with a stable address, so a VPS of its own is the better arrangement.

At home you open nothing and forward nothing. (In the other guide, where the host is reached directly, port 8443 is the one you forward.)

Step 2: install the host on the computer with your model

This is a different machine from the VPS: the computer at home that runs your model. --tls-port 8443 is a port on this computer only: the host listens there on its own loopback address, nothing is opened to the network, and you can pick any free port from 1024 up. People and apps never use it; they reach the host through the VPS on port 443 (the name in --endpoint has no port for that reason).

curl -fsSL https://euboulia.app/install.sh | bash -s -- host \
  --tls-port 8443 \
  --endpoint https://home.example.org \
  --relay relay.example.org \
  --relay-hostkey "ssh-ed25519 AAAA...KEY_FROM_STEP_1"

The host makes its own certificate and its own SSH key for the tunnel. The relay's SSH host key is checked strictly: if it is ever different, the tunnel refuses to connect. The installer prints the tunnel key (a line starting with ssh-ed25519), the host's own address (0x…, which is not yours), the pairing link, and a ready-made configuration file. Copy the tunnel key and the host's address, and keep the claim code for the app (valid 30 minutes, works once). The host reports to the relay's status service by itself, so the app can show whether it is online. The tunnel service keeps retrying every five seconds until step 3 is done.

Step 3: let the host's tunnel in

On the VPS, run the relay command again and add the host's address (it may report to the status service) and the tunnel key:

curl -fsSL https://euboulia.app/install.sh | sudo bash -s -- relay \
  --name relay.example.org \
  --allowed 0xHOST_ADDRESS_FROM_STEP_2 \
  --tunnel-key "ssh-ed25519 AAAA...KEY_FROM_STEP_2 home"

Running the installer again keeps what is there and adds the key. --allowed is the complete list of hosts that may report (separate several with commas); without it the old list is kept. Give each additional host the same way. The tunnel key is limited to opening one port on the relay and nothing else: no shell, no other forwarding.

Step 4: check it

From any computer:

curl -sk https://home.example.org/.well-known/phronesis.json

You should get the host's signed record: its address, "endpoints":["https://home.example.org"] and a tlsCertSHA256 field. -k is expected: the certificate is the host's own, which no certificate authority vouches for; the app checks its fingerprint against the signed record. If this works, the connection passes through the relay.

The status service should now know your host:

curl -s https://relay.example.org/presence/0xHOST_ADDRESS_FROM_STEP_2

It answers with "online":true within about a minute. Then, in the app (Settings, Connection), scan the pairing code or paste the link from step 2, or import the configuration file (it also carries the status service address), and press Connect. The app checks that the host it reaches proves exactly the address in the link. The host then says it does not know this device yet: choose Claim this host and type the claim code. That makes this device the administrator; other devices ask to join and you approve them in the app (Settings, Members).

If something is wrong

SymptomLook at
Step 1: the name does not resolve to this machineWait for DNS to propagate, or fix the A record. If you are sure, --skip-dns-check.
Step 1: the certificate request failsPort 80 must be open to the internet and the name must point at the VPS. See /var/log/letsencrypt/.
Step 4 times out or is refusedThe tunnel is not up. On the host: euboulia-host status and journalctl --user -u euboulia-tunnel -n 50. On the VPS: ss -ltn | grep 19443 should show a listener.
"Host key verification failed" in the tunnel logThe relay's SSH host key is not the one given with --relay-hostkey. If you did not reinstall the relay, treat it as an attack and stop; otherwise repeat step 2 with the new key.
The tunnel log says permission deniedStep 3 was not done, or the key was not copied whole.
The app says the claim code does not work any moreIt was used, it expired (30 minutes), five wrong tries voided it, or the host already has an administrator. On the host: euboulia-host claim prints a new code.
The app says the connection is not bound to the hostThe certificate it saw is not the one in the signed record. Something is answering in place of your host. Do not continue.

What the relay can and cannot do

Who is trusted and what each can seeYour devices and host are trusted. The relay, the server company, DNS, certificate authorities, delivery networks and cloud AI services are not trusted. The relay sees addresses, times and amounts, not content. Trusted Your devices and your host The people you invitemore than strangers, not unconditionally They hold the keys, read the content,and decide who is a member. Your host answers only with a certificateit signed with its own key, so theconnection cannot be taken over on the way. Not trusted Relay operatorsServer and hosting companiesDNS providersCertificate authoritiesDelivery networks (CDNs)Cloud AI services A relay or a server company can see whichaddresses connect, when, and how much. It cannot read or change what is said, orpass itself off as your host. It can stopthe service. Nothing here hides that.
Large organizations are outside the circle. The design assumes they may be curious or hostile.
It cannotIt can
Read or change a request or a reply. Pass itself off as your host (your devices accept only the certificate the host signed for). Learn your keys, your chats or your model's output.See which addresses connect, when, and how much data moves. Stop the service. Show whether your host is online. The VPS company can do the same and can read the relay's disk, which holds only an allow-list and the tunnel's public key.

Not hardened yet. The relay does not install fail2ban or automatic security updates, and does not force key-only SSH logins. Internet scanners will find the SSH port within hours: keep the VPS updated and use key-only logins yourself until that is done.

Remove it

curl -fsSL https://euboulia.app/install.sh | bash -s -- uninstall --purge     # on the host

On the VPS, delete the machine, or remove nginx, certbot and the euboulia users and files.