Overview
In order to have a resilient decentralized nostr network there needs to be a good distribution of relays. Avoiding the caveat of too many large (centralized) relays, many of unknown architecture and availability. It is not too difficult to run your own private relay at home or on an inexpensive cloud provider.
The following is based on the unofficial strfry docker repo:
https://hub.docker.com/r/relayable/strfry
Install Requirements
(In this example we're assuming your host is running Ubuntu 22.04 but should work with most Debian based OSs)
On your cloud server or home server be sure to install Docker.
Now install docker-compose:
curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
Make executable:
sudo chmod +x /usr/local/bin/docker-compose
Point DNS to IP of Relay
In your DNS registrar or hosting control panel add an A record for relay.yourdomain.com
to your instance public IP. You can alternatively use dynamic DNS if hosting from home. If using from home I recommend using a free CloudFlare account to proxy DNS to your home IP to obfuscate it. There are many videos and howtos on this.
Create docker-compose.yml
Make a docker-compose.yml file with contents below:
services:
strfry-nostr-relay:
image: relayable/strfry:latest
restart: unless-stopped
volumes:
- /local/path/to/strfry-data/etc:/etc/
- /local/path/to/strfry-data/strfry-db:/app/strfry-db
- /local/path/to/strfry-data/plugins:/app/plugins
ports:
- "7777:7777"
Add whitelist.js Plugin to Lock Down Relay
Add the following to you plugins directory:
#!/usr/bin/env node
const whiteList = {
'003ba9b2c5bd8afeed41a4ce362a8b7fc3ab59c25b6a1359cae9093f296dac01': true,
};
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', (line) => {
let req = JSON.parse(line);
if (req.type === 'lookback') {
return; // do nothing
}
if (req.type !== 'new') {
console.error("unexpected request type"); // will appear in strfry logs
return;
}
let res = { id: req.event.id }; // must echo the event's id
if (whiteList[req.event.pubkey]) {
res.action = 'accept';
} else {
res.action = 'reject';
res.msg = 'blocked: not on white-list';
}
console.log(JSON.stringify(res));
});
Change the hex public key (003ba9b2c5bd8afeed41a4ce362a8b7fc3ab59c25b6a1359cae9093f296dac01) to yours and add others you want to allow to use the relay.
Make it executable:
sudo chmod +x whitelist.js
Create your strfry.conf in your /etc directory from above docker-compose.yml
##
## Default strfry config for relayable/strfry Docker
##
# Directory that contains the strfry LMDB database (restart required)
db = "./strfry-db/"
dbParams {
# Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required)
maxreaders = 256
# Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required)
mapsize = 10995116277760
}
relay {
# Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required)
bind = "0.0.0.0"
# Port to open for the nostr websocket protocol (restart required)
port = 7777
# Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required)
nofiles = 1000000
# HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case)
realIpHeader = ""
info {
# NIP-11: Name of this server. Short/descriptive (< 30 characters)
name = "strfry docker test"
# NIP-11: Detailed information about relay, free-form
description = "This is a strfry instance."
# NIP-11: Administrative nostr pubkey, for contact purposes
pubkey = "unset"
# NIP-11: Alternative administrative contact (email, website, etc)
contact = "unset"
}
# Maximum accepted incoming websocket frame size (should be larger than max event and yesstr msg) (restart required)
maxWebsocketPayloadSize = 131072
# Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required)
autoPingSeconds = 55
# If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy)
enableTcpKeepalive = false
# How much uninterrupted CPU time a REQ query should get during its DB scan
queryTimesliceBudgetMicroseconds = 10000
# Maximum records that can be returned per filter
maxFilterLimit = 500
# Maximum number of subscriptions (concurrent REQs) a connection can have open at any time
maxSubsPerConnection = 20
writePolicy {
# If non-empty, path to an executable script that implements the writePolicy plugin logic
plugin = "./plugins/whitelist.js"
# Number of seconds to search backwards for lookback events when starting the writePolicy plugin (0 for no lookback)
lookbackSeconds = 0
}
compression {
# Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required)
enabled = true
# Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required)
slidingWindow = true
}
logging {
# Dump all incoming messages
dumpInAll = false
# Dump all incoming EVENT messages
dumpInEvents = false
# Dump all incoming REQ/CLOSE messages
dumpInReqs = false
# Log performance metrics for initial REQ database scans
dbScanPerf = false
}
numThreads {
# Ingester threads: route incoming requests, validate events/sigs (restart required)
ingester = 3
# reqWorker threads: Handle initial DB scan for events (restart required)
reqWorker = 3
# reqMonitor threads: Handle filtering of new events (restart required)
reqMonitor = 3
# yesstr threads: Experimental yesstr protocol (restart required)
yesstr = 1
}
}
events {
# Maximum size of normalised JSON, in bytes
maxEventSize = 65536
# Events newer than this will be rejected
rejectEventsNewerThanSeconds = 900
# Events older than this will be rejected
rejectEventsOlderThanSeconds = 94608000
# Ephemeral events older than this will be rejected
rejectEphemeralEventsOlderThanSeconds = 60
# Ephemeral events will be deleted from the DB when older than this
ephemeralEventsLifetimeSeconds = 300
# Maximum number of tags allowed
maxNumTags = 2000
# Maximum size for tag values, in bytes
maxTagValSize = 1024
}
Under the info
section can add name, description, pubkey, and contact to fit your relay.
If using below nginx config you can change bind
back to 127.0.0.1 to make it more secure. Change any other settings you feel confident you need to alter.
Start your container with docker-compose
to test working from directory with the docker-compose.yml file:
sudo docker-compose up
This will start container in terminal. Once you are happy configuration is working can start as a daemon:
sudo docker-compose up -d
Add Nginx Reverse Proxy and SSL
Install nginx on your relay:
sudo apt-get update && sudo apt-get install nginx certbot python3-certbot-nginx
Remove default config:
sudo rm -rf /etc/nginx/sites-available/default
Create new default config:
sudo nano /etc/nginx/sites-available/default
Add new reverse proxy config:
server{
server_name relay.yourdomain.com;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:7777;
proxy_http_version 1.1;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
send_timeout 300s;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Change relay.yourdomain.com
to your DNS name.
Restart nginx:
sudo systemctl restart nginx
Add LetsEncrypt SSL Certificate
Use certbot
to create new SSL and install it with nginx-plugin (replace with your DNS name):
sudo certbot --nginx -d relay.yourdomain.com
Restart nginx again:
sudo systemctl restart nginx
If no errors then good to go!
Testing and Usage
You can now install something like nostril to test your relay. Just use a testing nostr account you add to whitelist.js to test with. Or add relay to your client.
nostril --envelope --sec <your sec hex key> --content "docker container is working and whitelisting!" | websocat ws://localhost:7777
Using Other strfry Commands
See container name or ID:
sudo docker ps
Enter container to get bash access:
sudo docker exec -it <container> /bin/bash
This will show you have entered the running container. You can now run any strfry commands needed. See strfry readme for more.
bash-5.1# ./strfry --help
Congrats you now have a working strfry nostr relay!
By npub1y3uh89v5a4vq92t8q0j6su94zhvcdxpywjn3l6hpsr5welarqtrqj7yzhd
@jascha.
V4V lightning:crispactor61@walletofsatoshi.com
Follow me on Twitter