Nodofi Docs

Running a Nodofi Relay

A relay lets a node with no domain and no port forwarding — running from home, behind a router, even behind CGNAT — be reached anyway. It hosts nothing and stores essentially nothing: it just connects a node and whoever's asking for it, and passes messages between them.

Overview

A node connects outbound to a relay (which works through almost any router or firewall, the same way opening a website in a browser does), and anyone else can then reach that node by its permanent, cryptographic ID through the relay — no matter where the node's computer physically is or what its address is right now.

You need a relay if you're running a node locally without port forwarding. You generally don't need one if your node already has a real domain (see Setting up a node on a domain) — a relay exists specifically for the case a direct address doesn't.

Anyone can run a relay — it costs real bandwidth but no storage, and you can optionally charge nodes a periodic USDC fee to use yours (see Paid access), or leave it free.

Requirements

  • Node.js 18 or later.
  • Linux (systemd) for the packaged installer; Windows via Task Scheduler; any OS from source.
  • A real, internet-reachable address if you want anyone other than yourself to use it — a relay running on your own home network alongside the node you're trying to make reachable defeats the purpose.

Quickstart

Windows

  1. Download nodofi-relay-win-x64.zip and extract it anywhere you'll keep it.
  2. Double-click run.bat to try it, or install-startup.bat for it to start automatically on sign-in.
  3. Check http://localhost:8820 — you should see a small JSON status response.

Linux

tar -xzf nodofi-relay-linux-x64.tar.gz
cd nodofi-relay-linux-x64
sudo ./install.sh

Installs it to /opt/nodofi-relay, its config to ~/.nodofi-relay/.env, and registers it as a systemd service (nodofi-relay).

sudo systemctl status nodofi-relay
curl -s http://localhost:8820/

By default a freshly installed relay is completely open: free, unregistered, no identity verification. That's fine for testing, but for a relay other people will actually rely on, see Setting up on a domain and Paid access & directory below.

Setting up on a domain

The same shape as a node's own domain setup: a small server, a DNS record, the relay running as a systemd service, and a reverse proxy handling TLS and — critically for a relay, whose entire job is a live socket.io connection — the WebSocket upgrade.

  1. Get a server and point DNS at it (e.g. an A record for relay pointing at its IP, giving you relay.yourdomain.com) — identical to a node's own steps 1–2.
  2. Install Node.js and the relay, same as the Linux quickstart above.
  3. Put a reverse proxy in front of it — see below for full Apache and Nginx configs.
  4. Configure it — edit ~/.nodofi-relay/.env:
    RELAY_PUBLIC_URL=https://relay.yourdomain.com
    RELAY_NAME=My Relay
    RELAY_OPERATOR=Your name or company
    REGISTRY_URL=https://registry.nodofi.com
    # Optional: charge nodes for access
    # ACCESS_FEE_AMOUNT=2
    # ACCESS_FEE_WALLET=04...your MooChedda wallet...
    # ACCESS_FEE_PERIOD_DAYS=30
    then sudo systemctl restart nodofi-relay. RELAY_PUBLIC_URL is what gets listed in the relay directory and what the Registry live-probes to confirm the relay is real — it must be the address others can actually reach, not a local one.
  5. Verify with the checks in Troubleshooting below.

Reverse proxy (Apache / Nginx)

A relay is nothing but a live socket.io connection, so getting the WebSocket upgrade right matters even more here than for a node. Replace relay.yourdomain.com with your own subdomain.

Apache

sudo a2enmod proxy proxy_http proxy_wstunnel rewrite
sudo systemctl reload apache2
<VirtualHost *:443>
    ServerName relay.yourdomain.com
    # ... your SSL/TLS config (or let certbot manage this block for you) ...

    ProxyPreserveHost On
    ProxyRequests Off
    ProxyTimeout 86400

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule ^/(.*) ws://127.0.0.1:8820/$1 [P,L]

    ProxyPass / http://127.0.0.1:8820/
    ProxyPassReverse / http://127.0.0.1:8820/
</VirtualHost>

The RewriteCond/RewriteRule pair is the important part: it intercepts only requests carrying an Upgrade: websocket header and sends those through mod_proxy_wstunnel (ws://); everything else — including the initial engine.io HTTP handshake — falls through to the plain ProxyPass line below it. Without that split, Apache proxies the HTTP parts fine (a plain health-check request looks completely healthy) but never completes the WebSocket upgrade itself — which is exactly what a stuck "websocket error" on a connecting node usually means. See Troubleshooting for how to tell the two apart.

Nginx

server {
    listen 80;
    server_name relay.yourdomain.com;
    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl http2;
    server_name relay.yourdomain.com;

    ssl_certificate     /etc/letsencrypt/live/relay.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/relay.yourdomain.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8820;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
        proxy_read_timeout 86400;
    }
}

Nginx can handle both the plain HTTP and the WebSocket upgrade in one location block (unlike Apache, which needs the split above) — the Upgrade/Connection headers are what make that work. The proxy_read_timeout matters too: nginx's 60-second default will silently drop an idle socket.io connection, which looks like random disconnects rather than an obvious error.

Troubleshooting: "websocket error" / a node won’t connect

If a node's admin page shows "Not connected: websocket error" when pointed at your relay, work through these in order, from any machine other than the relay's own box:

1. Is the relay itself up?

curl -i https://relay.yourdomain.com/

Expect 200 and a small JSON body ({"ok":true,"service":"Nodofi Relay",...}). If this fails, the relay process or DNS is the problem, not the proxy — check sudo systemctl status nodofi-relay and DNS first.

2. Does the engine.io HTTP handshake work?

curl -i "https://relay.yourdomain.com/socket.io/?EIO=4&transport=polling"

Expect 200 and a body starting 0{"sid":".... If step 1 works but this doesn't, the proxy isn't forwarding to the right port/path.

3. Does the WebSocket upgrade actually complete?

curl --http1.1 -i -N \
  -H "Connection: Upgrade" -H "Upgrade: websocket" \
  -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  "https://relay.yourdomain.com/socket.io/?EIO=4&transport=websocket"

Expect 101 Switching Protocols. If steps 1 and 2 succeed but this one comes back 503, 404, or just hangs — that's the exact signature of a reverse proxy that handles plain HTTP fine but was never configured to forward the WebSocket upgrade. Apply the Apache or Nginx config above (specifically the mod_proxy_wstunnel/RewriteRule split for Apache, or the Upgrade/Connection headers for Nginx) and re-run this check — it should come back 101 once it's wired up correctly, and a connecting node picks up the fixed connection on its own next automatic retry (every couple of seconds), no restart needed on the node's side.

Note: A 503 here that shows an Apache or Nginx error page (not JSON from the relay itself) confirms the proxy is what's rejecting the upgrade — the relay's own socket.io server never even saw the request.

Uninstalling

Linux: sudo ./uninstall.sh from the folder you extracted (your .env is kept). Windows: double-click uninstall-startup.bat, then delete the extracted folder.