Skip to content

Self-Hosting Guide

The HARP relay is a zero-knowledge dumb pipe that routes encrypted envelopes. While the managed relay at relay.humanauth.ai is the easiest option, teams with strict data residency or compliance requirements can self-host the relay.

The relay package ships with two entry points:

  • Cloudflare Workers — used by the managed service (Durable Objects for state)
  • Node.js — for self-hosting (in-memory store, single-process)

This guide covers the Node.js self-hosting path.

FROM node:22-alpine AS build
WORKDIR /app
RUN npm install -g pnpm@9
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm --filter @humanauth/relay build
FROM node:22-alpine
WORKDIR /app
COPY --from=build /app/packages/relay/dist ./dist
COPY --from=build /app/packages/relay/package.json .
COPY --from=build /app/node_modules ./node_modules
EXPOSE 8787
CMD ["node", "dist/index.js"]
Terminal window
docker build -t harp-relay .
docker run -p 8787:8787 harp-relay

The relay is now running at http://localhost:8787.

Terminal window
curl http://localhost:8787/health
# {"status":"ok"}
VariableDefaultDescription
PORT8787HTTP port
HOST0.0.0.0Bind address

For production, place the relay behind a reverse proxy with TLS:

server {
listen 443 ssl;
server_name relay.yourcompany.com;
ssl_certificate /etc/ssl/certs/relay.crt;
ssl_certificate_key /etc/ssl/private/relay.key;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Long polling support
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}

Point the CLI and MCP server to your relay:

Terminal window
# Pairing
humanauth pair --name "my-agent" --relay https://relay.yourcompany.com
# The relay URL is saved in the pairing file, so all subsequent
# requests automatically route through your self-hosted relay.

For a complete setup with nginx and auto-renewing TLS via certbot:

version: "3.8"
services:
relay:
build: .
restart: unless-stopped
environment:
- PORT=8787
expose:
- "8787"
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "443:443"
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/ssl
depends_on:
- relay

The Node.js relay uses an in-memory store. This means:

  • State is lost on restart — active pairing sessions and pending requests are cleared
  • Single process — no horizontal scaling (all state is in one process)
  • No push notifications — the self-hosted relay does not send push notifications. The app must poll for pending requests.

For production workloads that require persistence, high availability, and push notifications, use the managed relay at relay.humanauth.ai or deploy the Cloudflare Workers version with Durable Objects.

  • Always use TLS in production. The relay handles encrypted blobs, but TLS protects metadata (pair_ids, request_ids, timing).
  • The relay stores only routing data (pair_id -> device_id -> push_token). It never sees plaintext.
  • Rate limit incoming requests to prevent abuse (10 requests/minute per pair_id, 100/minute per IP).
  • Monitor relay logs for unusual traffic patterns.