systemd deployment (Linux relay host)
This guide installs orbitdb-relay under systemd on a Linux VPS or bare-metal host that
may also run Kubo, without nginx (or any other reverse proxy) for libp2p TLS. Secure
WebSockets come from AutoTLS (@ipshipyard/libp2p-auto-tls): a certificate for
<peerId>.libp2p.direct via registration.libp2p.direct
and Let's Encrypt DNS-01 (see the upstream walkthrough:
libp2p/js-libp2p-example-auto-tls).
From your laptop: SSH in and run the steps below (or use
deploy/install-on-server.shfrom the repository). Automated agents in CI sandboxes often cannot resolve or reach your private relay hostname.
Port plan vs Kubo
Default Kubo ports to leave alone:
| Service | Typical ports |
|---|---|
| Swarm | 4001/tcp, 4001/udp |
| API | 5001/tcp |
| Gateway | 8080/tcp |
Relay uses a disjoint block (change if these collide with other software):
| Role | Port |
|---|---|
Metrics / /health / /multiaddrs | 28190/tcp |
| libp2p TCP | 28191/tcp |
| libp2p WebSocket | 28192/tcp |
| WebRTC-direct UDP | 28193/udp |
How this repo lines up with js-libp2p-example-auto-tls
The AutoTLS example expects, in short:
- A publicly dialable socket (or
appendAnnouncewhen you know your public IP/ports). - TCP and/or WebSocket listeners with Noise + Yamux.
- Identify (and usually identify push) plus keychain for persistent ACME/account keys.
autoTLS()— on servers,autoConfirmAddress: truematches the example's "auto-confirm" path so addresses are trusted without waiting on extra AutoNAT rounds.
orbitdb-relay already wires TCP, WebSockets, WebRTC, noise, yamux,
identify / identifyPush, keychain, autoNAT, amino DHT (/ipfs/kad/1.0.0), and
autoTLS({ autoConfirmAddress: true }) in src/config/libp2p.ts. A persistent datastore
stores the relay key and libp2p state under DATASTORE_PATH.
Operational requirement on your side: firewall and VITE_APPEND_ANNOUNCE must reflect the
same public IP and 28191–28193 ports so the node's advertised addresses match what the
internet can dial.
Prerequisites
- Node.js ≥ 22 (
enginesin package.json). - Outbound HTTPS to Let's Encrypt and
registration.libp2p.direct. - Inbound rules for
28191/tcp,28192/tcp,28193/udp(and optionally28190/tcponly if you scrape metrics remotely).
On Ubuntu without Node, install 22.x before running install-on-server.sh (example using
NodeSource):
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
Quick install (script)
As root on the server, after copying this repository (or at least deploy/):
bash deploy/install-on-server.sh
The script creates the service user, installs the npm package under /opt/orbitdb-relay, writes
/etc/default/orbitdb-relay (attempting to detect public IPv4), installs the systemd unit, and
enables the service. Review the generated /etc/default/orbitdb-relay before relying on
production certs.
Manual install
1. System user and directories
sudo mkdir -p /opt/orbitdb-relay /var/lib/orbitdb-relay
if ! id orbitdb-relay &>/dev/null; then
sudo useradd --system --home /var/lib/orbitdb-relay --create-home --shell /usr/sbin/nologin orbitdb-relay
fi
sudo chown -R orbitdb-relay:orbitdb-relay /var/lib/orbitdb-relay
2. Install the package under /opt
sudo tee /opt/orbitdb-relay/package.json >/dev/null <<'EOF'
{
"private": true,
"type": "module",
"dependencies": {
"orbitdb-relay": "^0.10.1"
}
}
EOF
sudo chown -R orbitdb-relay:orbitdb-relay /opt/orbitdb-relay
sudo -u orbitdb-relay bash -lc 'cd /opt/orbitdb-relay && npm install --omit=dev'
If scoped packages fail to resolve, see Access controllers.
3. Environment file
sudo cp deploy/orbitdb-relay.env.example /etc/default/orbitdb-relay
sudo chmod 640 /etc/default/orbitdb-relay
sudo chown root:orbitdb-relay /etc/default/orbitdb-relay
Edit /etc/default/orbitdb-relay:
-
DATASTORE_PATH=/var/lib/orbitdb-relay -
VITE_APPEND_ANNOUNCE— set to your public IPv4 and ports 28191–28193. Example at203.0.113.7:VITE_APPEND_ANNOUNCE=/ip4/203.0.113.7/tcp/28191,/ip4/203.0.113.7/tcp/28192/ws,/ip4/203.0.113.7/udp/28193/webrtc-direct -
Do not set
disableAutoTLSfor production WSS via AutoTLS. -
Optional:
STAGING=truewhile testing Let's Encrypt staging (then remove for production). -
Optional: enable the test/debug libp2p protocols only when you actively need them:
RELAY_CONNECTIVITY_ECHO_ENABLED=1RELAY_CONNECTIVITY_BULK_ENABLED=1Optional bulk tuning:
RELAY_CONNECTIVITY_BULK_MAX_FRAME_BYTES=262144RELAY_CONNECTIVITY_BULK_READ_TIMEOUT_MS=10000RELAY_CONNECTIVITY_BULK_IDLE_TIMEOUT_MS=30000Leave them unset for normal production operation; they are disabled by default.
4. systemd unit
sudo cp deploy/orbitdb-relay.service /etc/systemd/system/orbitdb-relay.service
sudo systemctl daemon-reload
sudo systemctl enable --now orbitdb-relay
sudo systemctl status orbitdb-relay
sudo journalctl -u orbitdb-relay -f
5. Firewall (example: ufw)
sudo ufw allow 28191/tcp comment 'orbitdb-relay tcp'
sudo ufw allow 28192/tcp comment 'orbitdb-relay ws'
sudo ufw allow 28193/udp comment 'orbitdb-relay webrtc-direct'
# optional: sudo ufw allow 28190/tcp comment 'relay metrics'
sudo ufw reload
Verify AutoTLS
-
Logs should show
Relay PeerId:and listener multiaddrs. -
On the host:
curl -sS http://127.0.0.1:28190/multiaddrs | jq .curl -sS http://127.0.0.1:28190/healthAfter issuance, expect multiaddrs containing
/tls/ws(often with SNI /*.libp2p.directstyle names as in the upstream example output). The cert is for libp2p.direct, not your vanity hostname.
Why no nginx-proxy
AutoTLS terminates TLS inside libp2p for WSS. Nginx would not replace the DNS-01 flow that libp2p.direct uses; adding another proxy is unnecessary for this stack unless you run separate non-libp2p HTTPS apps on the same machine.
Coexisting with Kubo
- Keep Kubo on its ports (e.g. 4001 / 5001 / 8080).
- Run the relay only on 28190–28193 (or adjust if you move WS to 80 — see below).
- Data: relay uses
DATASTORE_PATH(Helia + LevelDB), not Kubo's repository.
WebSocket listener on port 80
Yes: set RELAY_WS_PORT=80 in /etc/default/orbitdb-relay. AutoTLS will then
request/announce certificates for the WS listener on 80 as well (same libp2p.direct flow).
Important details:
-
Privileged ports: On Linux, ports below 1024 need
CAP_NET_BIND_SERVICE. The base unit setsNoNewPrivileges=true; for a non-rootUser=, systemd then ignoresAmbientCapabilities=, so binding fails witherrno=13(EACCES) on TCP or UDP (often first on WebRTC UDP). Install a drop-in that setsNoNewPrivileges=falseand the capability — usedeploy/orbitdb-relay-low-ports.conf.example(ororbitdb-relay-ws-port80.conf.examplefor WS-on-80 only):sudo mkdir -p /etc/systemd/system/orbitdb-relay.service.dsudo cp deploy/orbitdb-relay-low-ports.conf.example \/etc/systemd/system/orbitdb-relay.service.d/low-ports.confsudo systemctl daemon-reload -
VITE_APPEND_ANNOUNCE: Use the same public IP and port 80 for the WS multiaddr, e.g.…/tcp/28191,…/tcp/80/ws,…/udp/28193/webrtc-direct(replace28192with80in bothRELAY_WS_PORTand this line). -
Firewall: Allow
80/tcpinstead of (or in addition to)28192/tcpif you no longer expose WS on 28192. -
Conflicts: Nothing else may listen on 80 (nginx, Apache, another app, or a Kubo gateway if you ever bound it to 80).
-
Metrics / TCP / WebRTC can stay on 28190 / 28191 / 28193; only the WS port moves to 80.
Troubleshooting: errno=13 / UDP socket binding failed
If logs show UDP socket binding failed … errno=13 or the service exits right after
start, you are likely on a privileged port without effective CAP_NET_BIND_SERVICE. The
usual cause is NoNewPrivileges=true in the main unit without NoNewPrivileges=false
in the drop-in (see Privileged ports above). Alternative: use ports