Why a relay?
Cuevello normally connects directly to the Mac on the local network. A relay is only needed when the client cannot reach the Mac directly, for example over cellular, from another Wi-Fi network, or behind NAT and firewall boundaries.
The Mac Server opens an outbound encrypted control connection to the relay. The client connects to the relay as well. The relay forwards traffic to the registered Mac; pairing, TLS, and certificate pinning remain the security foundation.
Requirements
- A VPS or server with a public IPv4 address.
- Docker and Docker Compose.
- A relay subdomain, for example
relay.example.com.
- A wildcard DNS record for device subdomains.
- A TLS certificate for the relay domain.
- Open TCP ports for client and Mac Server control connections.
1. Configure DNS
The client connects to a device-specific subdomain in the form <device-id>.<relay-host>. Create both the relay record and the wildcard record.
relay.example.com A 203.0.113.10
*.relay.example.com A 203.0.113.10
Verify DNS:
dig +short relay.example.com A
dig +short abc123.relay.example.com A
2. Create the TLS certificate
The certificate is used for the Mac server control connection and must cover relay.example.com. The client port uses TLS passthrough to the Mac server, so a wildcard TLS certificate is not required.
certbot certonly --manual --preferred-challenges dns \
-d relay.example.com \
--agree-tos \
--email you@example.com \
--no-eff-email
Certbot shows TXT records for _acme-challenge.relay.example.com. Add all requested TXT values before continuing.
/etc/letsencrypt/live/relay.example.com/fullchain.pem
/etc/letsencrypt/live/relay.example.com/privkey.pem
3. Start the Docker container
The relay container is published from the dedicated repository github.com/hosy/cuevello-relay.
Download Compose template
mkdir -p /opt/cuevello-relay/certs
cd /opt/cuevello-relay
cp /etc/letsencrypt/live/relay.example.com/fullchain.pem ./certs/fullchain.pem
cp /etc/letsencrypt/live/relay.example.com/privkey.pem ./certs/privkey.pem
chmod 600 ./certs/privkey.pem
services:
cuevello-relay:
image: ghcr.io/hosy/cuevello-relay:latest
container_name: cuevello-relay
restart: unless-stopped
environment:
RELAY_SECRET_PEPPER: "replace-with-at-least-32-random-bytes"
TLS_CERT_FILE: "/certs/fullchain.pem"
TLS_KEY_FILE: "/certs/privkey.pem"
MAX_STREAMS_PER_DEVICE: "32"
CLIENT_RATE_LIMIT_PER_MINUTE: "120"
CONTROL_RATE_LIMIT_PER_MINUTE: "30"
REQUIRE_SIGNED_REGISTRATION: "true"
ports:
- "9443:443"
- "9444:8443"
volumes:
- ./certs:/certs:ro
- relay-data:/data
volumes:
relay-data:
Generate a long secret pepper:
openssl rand -base64 48
docker compose up -d
docker compose ps
docker compose logs --tail=100
4. Open the firewall
9443 for clients.
9444 for Mac Server control connections.
nc -vz relay.example.com 9443
nc -vz relay.example.com 9444
5. Configure the Mac Server
- Start Cuevello Server on the Mac.
- Open Relay Settings....
- Enable Relay.
- Set Host to
relay.example.com.
- Set Client Port to
9443.
- Set Control Port to
9444.
- Apply the settings.
6. Pair the client
Open the Mac pairing window. When relay is enabled and connected, the QR code contains the relay information. The client can pair through the relay even when it is outside the local network.
Security
- Knowing a relay URL is not enough to control a Mac. Cuevello still requires end-to-end TLS with a paired client certificate.
- The public client port uses TLS passthrough. The relay only reads the SNI hostname needed for routing and cannot decrypt Cuevello requests.
- Mac server control registrations are signed with a persistent device key. The relay secret alone is not sufficient to take over a registered device.
- The container enforces TLS 1.3, bounded registration requests, per-IP rate limits, and per-device stream limits.
- Keep
RELAY_SECRET_PEPPER, /data/devices.json, and the TLS private key private.
Debugging
DEVICE_ID="replace-with-device-id"
HOST="$DEVICE_ID.relay.example.com"
dig +short "$HOST" A
nc -vz "$HOST" 9443
openssl s_client -connect "$HOST:9443" -servername "$HOST"
cd /opt/cuevello-relay
docker compose logs -f --tail=100
Warum ein Relay?
Cuevello verbindet sich normalerweise direkt mit dem Mac im lokalen Netzwerk. Ein Relay wird nur benötigt, wenn der Client den Mac nicht direkt erreichen kann, zum Beispiel über Mobilfunk, aus einem anderen WLAN oder hinter NAT- und Firewall-Grenzen.
Der Mac Server baut selbst eine ausgehende, verschlüsselte Control-Verbindung zum Relay auf. Der Client verbindet sich ebenfalls mit dem Relay. Das Relay leitet den Verkehr zum registrierten Mac weiter; Pairing, TLS und Zertifikat-Pinning bleiben die Sicherheitsbasis.
Voraussetzungen
- Ein VPS oder Server mit öffentlicher IPv4-Adresse.
- Docker und Docker Compose.
- Eine Relay-Subdomain, zum Beispiel
relay.example.com.
- Ein Wildcard-DNS-Eintrag für Device-Subdomains.
- Ein TLS-Zertifikat für die Relay-Domain.
- Offene TCP-Ports für Client- und Mac-Server-Control-Verbindungen.
1. DNS einrichten
Der Client verbindet sich mit einer Device-Subdomain im Format <device-id>.<relay-host>. Lege deshalb den Relay-Eintrag und den Wildcard-Eintrag an.
relay.example.com A 203.0.113.10
*.relay.example.com A 203.0.113.10
DNS prüfen:
dig +short relay.example.com A
dig +short abc123.relay.example.com A
2. TLS-Zertifikat erstellen
Das Zertifikat wird für die Mac-Server-Control-Verbindung genutzt und muss relay.example.com abdecken. Der Client-Port nutzt TLS-Passthrough zum Mac Server, deshalb ist kein Wildcard-TLS-Zertifikat für das Relay nötig.
certbot certonly --manual --preferred-challenges dns \
-d relay.example.com \
--agree-tos \
--email you@example.com \
--no-eff-email
Certbot zeigt TXT-Records für _acme-challenge.relay.example.com. Trage alle angeforderten TXT-Werte ein, bevor du fortfährst.
/etc/letsencrypt/live/relay.example.com/fullchain.pem
/etc/letsencrypt/live/relay.example.com/privkey.pem
3. Docker Container starten
Der Relay-Container wird aus dem eigenen Repository github.com/hosy/cuevello-relay veröffentlicht.
Compose-Vorlage herunterladen
mkdir -p /opt/cuevello-relay/certs
cd /opt/cuevello-relay
cp /etc/letsencrypt/live/relay.example.com/fullchain.pem ./certs/fullchain.pem
cp /etc/letsencrypt/live/relay.example.com/privkey.pem ./certs/privkey.pem
chmod 600 ./certs/privkey.pem
services:
cuevello-relay:
image: ghcr.io/hosy/cuevello-relay:latest
container_name: cuevello-relay
restart: unless-stopped
environment:
RELAY_SECRET_PEPPER: "replace-with-at-least-32-random-bytes"
TLS_CERT_FILE: "/certs/fullchain.pem"
TLS_KEY_FILE: "/certs/privkey.pem"
MAX_STREAMS_PER_DEVICE: "32"
CLIENT_RATE_LIMIT_PER_MINUTE: "120"
CONTROL_RATE_LIMIT_PER_MINUTE: "30"
REQUIRE_SIGNED_REGISTRATION: "true"
ports:
- "9443:443"
- "9444:8443"
volumes:
- ./certs:/certs:ro
- relay-data:/data
volumes:
relay-data:
Erzeuge eine lange zufällige Secret-Pepper-Zeichenkette:
openssl rand -base64 48
docker compose up -d
docker compose ps
docker compose logs --tail=100
4. Firewall freigeben
9443 für Clients.
9444 für Mac-Server-Control-Verbindungen.
nc -vz relay.example.com 9443
nc -vz relay.example.com 9444
5. Mac Server konfigurieren
- Cuevello Server auf dem Mac starten.
- Relay Settings... öffnen.
- Relay aktivieren.
- Host auf
relay.example.com setzen.
- Client Port auf
9443 setzen.
- Control Port auf
9444 setzen.
- Einstellungen anwenden.
6. Client pairen
Öffne das Pairing-Fenster am Mac. Wenn Relay aktiv und verbunden ist, enthält der QR-Code die Relay-Daten. Der Client kann dann auch außerhalb des lokalen Netzwerks über das Relay pairen.
Sicherheit
- Die Relay-URL allein reicht nicht aus, um einen Mac zu steuern. Cuevello verlangt weiterhin Ende-zu-Ende-TLS mit einem gepairten Client-Zertifikat.
- Der öffentliche Client-Port nutzt TLS-Passthrough. Das Relay liest nur den SNI-Hostnamen für das Routing und kann Cuevello-Requests nicht entschlüsseln.
- Mac-Server-Control-Registrierungen werden mit einem persistenten Device-Key signiert. Das Relay-Secret allein reicht nicht aus, um ein registriertes Device zu übernehmen.
- Der Container erzwingt TLS 1.3, begrenzte Registrierungs-Requests, IP-basierte Rate-Limits und Limits pro Device-Stream.
- Halte
RELAY_SECRET_PEPPER, /data/devices.json und den privaten TLS-Schlüssel geheim.
Debugging
DEVICE_ID="replace-with-device-id"
HOST="$DEVICE_ID.relay.example.com"
dig +short "$HOST" A
nc -vz "$HOST" 9443
openssl s_client -connect "$HOST:9443" -servername "$HOST"
cd /opt/cuevello-relay
docker compose logs -f --tail=100