Introduction
Relayly is a protocol and reference relay for end-to-end encrypted, device-to-device communication over untrusted infrastructure.
Devices authenticate to the relay and pair through a short out-of-band code; the session itself is established directly between the two devices using the Noise Protocol Framework (Noise_XX_25519_ChaChaPoly_BLAKE2s). The relay coordinates pairing and forwards ciphertext, but holds no key material and is cryptographically excluded from every session it carries.
Why Relayly?
Most relay and tunneling tools require you to trust a third-party server with your data, or require accounts and cloud infrastructure. Relayly is different:
- Zero accounts: devices are identified by cryptographic keys, not emails
- Zero plaintext: the relay forwards encrypted frames it cannot read
- Zero cloud: runs on your Raspberry Pi, VPS, or even a local machine
- Zero lock-in: MIT licensed, portable single binary, five independent official SDKs
When should I use Relayly?
Relayly is ideal for:
- Local-first applications that need device sync
- IoT deployments in environments with unreliable internet
- Privacy-sensitive device communication (health, finance, personal data)
- Development environments where you want to route between devices without ngrok/cloud
- Networks with censorship or intermittent connectivity
Protocol & conformance
docs/PROTOCOL.md in the repository is the normative wire spec. Five independent SDKs, Go, TypeScript, Python, Rust, and C++, each implement it from scratch, and a required cross-language CI matrix pairs every one of them against every other, and against itself, before any change can merge. See the Wire Protocol page for a practical summary.
Architecture
relayly/
├── cmd/relayly/ # Main server entry point
├── internal/ # Private server logic (Relay, Database, Admin)
├── sdk/ # Official Client SDKs (Go, TS, Python, Rust, C++)
├── examples/ # Reference implementations
├── docs/ # Protocol spec, RFCs, roadmap
└── Dockerfile # Production image Next Steps
- Quick Start: Get a relay running in under 60 seconds
- Installation: Docker or binary installation
- Configuration: Full configuration reference
- SDKs: Go, TypeScript, Python, Rust, and C++ client libraries
Quick Start for Developers
Start the server and register a device
relayly pair registers a device and prints its device_id and device_token, the credentials an SDK needs to connect. It’s a separate step from pairing two devices with each other, that happens at runtime via the SDK, see below.
docker compose up --build -d
docker exec relayly /relayly pair "My Device"
# ✓ Device ID: a1b2c3d4-...
# ✓ Device Token: 7f3e9c2a... Connect with the Go SDK
import relayly "github.com/NIKX-Tech/relayly/sdk/go"
key, _ := relayly.LoadOrGenerateKey("~/.relayly/device.key")
// deviceToken comes from `relayly pair` (or POST /api/v1/devices)
client, _ := relayly.Connect(ctx, "ws://localhost:8080/ws", relayly.Options{
DeviceID: "a1b2c3d4-...",
DeviceToken: deviceToken,
PrivateKey: key,
})
defer client.Close()
// Request a pairing code to share with the other device
code, _ := client.RequestPairCode(ctx)
fmt.Println("Share this code:", code.Short)
peer, _ := code.Wait(ctx) // blocks until the other device accepts
client.Send(ctx, peer.ID, []byte("hello!"))
msg := <-client.Messages() Full reference: pkg.go.dev/github.com/NIKX-Tech/relayly/sdk/go
Connect with the TypeScript SDK
import { RelaylyClient, generateKey } from 'relayly';
// deviceToken comes from `relayly pair` (or POST /api/v1/devices)
const client = new RelaylyClient('ws://localhost:8080/ws', {
deviceId: 'a1b2c3d4-...',
deviceToken,
keyPair: generateKey(),
});
await client.connect();
const code = await client.requestPairCode();
console.log('Share this code:', code.shortCode);
client.on('message', (msg) => console.log(msg.payload)); Full reference: npmjs.com/package/relayly, works in Node.js, browsers, and React Native.
Python, Rust, and C++ SDKs follow the same shape, see the SDKs page for each.
Self-Host in 5 Minutes
Prerequisites
- Docker (recommended): any version supporting Compose V2, or
- Go 1.24+: for building the binary directly
Option A: Docker Compose (recommended)
# Clone the repository
git clone https://github.com/NIKX-Tech/relayly.git
cd relayly
# Start in the background
docker compose up --build -d
# Register your first device
docker exec relayly /relayly pair "My Laptop" The relay will be available at ws://localhost:8080/ws and the admin dashboard at http://localhost:8081 (localhost-only by default).
Option B: Go binary
git clone https://github.com/NIKX-Tech/relayly.git
cd relayly
# Build
go build -o relayly ./cmd/relayly
# Start
./relayly start Configuration
The server reads config/relayly.yaml on startup, and every key can be overridden with a RELAYLY_* environment variable.
| Variable | Default | Description |
|---|---|---|
RELAYLY_PORT | 8080 | WebSocket and API port |
RELAYLY_DB_PATH | data/relayly.db | SQLite database file path |
RELAYLY_ADMIN_ENABLED | true | Enable the HTMX admin dashboard |
RELAYLY_ADMIN_HOST | 127.0.0.1 | Interface the admin UI binds to |
RELAYLY_LOG_LEVEL | info | Log verbosity (debug, info, warn, error) |
Security notes
- Pairing codes expire in 5 minutes and are single-use. There are no long-lived shared secrets beyond the device token; devices authenticate to each other via cryptographic public keys pinned at first pairing.
- The admin UI binds to
127.0.0.1by default. It is not exposed to the network unless you explicitly changeRELAYLY_ADMIN_HOST. - The relay never sees plaintext. Every device pair runs its own Noise XX handshake and transport encryption directly with each other; the relay forwards the resulting ciphertext without ever holding a session key.