18 KiB
Deployment Guide - C Nostr Relay
Complete deployment guide for the C Nostr Relay with event-based configuration system across different environments and platforms.
Table of Contents
- Deployment Overview
- Production Deployment
- Cloud Deployments
- Container Deployment
- Reverse Proxy Setup
- Monitoring Setup
- Security Hardening
- Backup and Recovery
Deployment Overview
The C Nostr Relay's event-based configuration system simplifies deployment:
Key Deployment Benefits
- Zero Configuration: No config files to manage or transfer
- Self-Contained: Single binary + auto-generated database
- Portable: Database contains all relay state and configuration
- Secure: Admin keys generated locally, never transmitted
- Scalable: Efficient SQLite backend with WAL mode
Deployment Requirements
- CPU: 1 vCPU minimum, 2+ recommended
- RAM: 512MB minimum, 2GB+ recommended
- Storage: 100MB for binary + database growth (varies by usage)
- Network: Port 8888 (configurable via events)
- OS: Linux (recommended), macOS, Windows (WSL)
Production Deployment
Server Preparation
System Updates
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y
# CentOS/RHEL
sudo yum update -y
# Install required packages
sudo apt install -y build-essential git sqlite3 libsqlite3-dev \
libwebsockets-dev libssl-dev libsecp256k1-dev libcurl4-openssl-dev \
zlib1g-dev systemd
User and Directory Setup
# Create dedicated system user
sudo useradd --system --home-dir /opt/c-relay-pg --shell /bin/false c-relay-pg
# Create application directory
sudo mkdir -p /opt/c-relay-pg
sudo chown c-relay-pg:c-relay-pg /opt/c-relay-pg
Build and Installation
Automated Installation (Recommended)
# Clone repository
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
git submodule update --init --recursive
# Build
make clean && make
# Install as systemd service
sudo systemd/install-service.sh
Manual Installation
# Build relay
make clean && make
# Install binary
sudo cp build/c_relay_pg_x86 /opt/c-relay-pg/
sudo chown c-relay-pg:c-relay-pg /opt/c-relay-pg/c_relay_pg_x86
sudo chmod +x /opt/c-relay-pg/c_relay_pg_x86
# Install systemd service
sudo cp systemd/c-relay-pg.service /etc/systemd/system/
sudo systemctl daemon-reload
UDP Nostr Ingress (Privileged Port Binding)
If UDP ingress is enabled on a port below 1024 (e.g., the default port 443),
the relay needs the CAP_NET_BIND_SERVICE capability to bind the port as a
non-root user. Add this to the [Service] section of the systemd unit file:
AmbientCapabilities=CAP_NET_BIND_SERVICE
This grants the c-relay-pg service user permission to bind privileged ports
without running as root. The capability is automatically inherited by the
forked relay process and does not require any changes to the binary.
Note: TCP and UDP port namespaces are separate. The UDP listener on port 443 coexists with an HTTPS server (nginx, caddy) on TCP port 443 without conflict. This is the same mechanism used by QUIC/HTTP3.
Service Management
Start and Enable Service
# Start the service
sudo systemctl start c-relay-pg
# Enable auto-start on boot
sudo systemctl enable c-relay-pg
# Check status
sudo systemctl status c-relay-pg
Capture Admin Keys (CRITICAL)
# View startup logs to get admin keys
sudo journalctl -u c-relay-pg --since "5 minutes ago" | grep -A 10 "IMPORTANT: SAVE THIS ADMIN PRIVATE KEY"
# Or check the full log
sudo journalctl -u c-relay-pg --no-pager | grep "Admin Private Key"
⚠️ CRITICAL: Save the admin private key immediately - it's only shown once and is needed for all configuration updates!
Firewall Configuration
UFW (Ubuntu)
# Allow relay WebSocket port
sudo ufw allow 8888/tcp
# Allow UDP Nostr ingress (if enabled)
sudo ufw allow 443/udp
# Allow SSH (ensure you don't lock yourself out)
sudo ufw allow 22/tcp
# Enable firewall
sudo ufw enable
iptables
# Allow relay WebSocket port
sudo iptables -A INPUT -p tcp --dport 8888 -j ACCEPT
# Allow UDP Nostr ingress (if enabled)
sudo iptables -A INPUT -p udp --dport 443 -j ACCEPT
# Save rules (Ubuntu/Debian)
sudo iptables-save > /etc/iptables/rules.v4
Cloud Deployments
AWS EC2
Instance Setup
# Launch Ubuntu 22.04 LTS instance (t3.micro or larger)
# Security Group: Allow port 8888 from 0.0.0.0/0 (or restricted IPs)
# Connect via SSH
ssh -i your-key.pem ubuntu@your-instance-ip
# Use the simple deployment script
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
sudo examples/deployment/simple-vps/deploy.sh
Elastic IP (Recommended)
# Associate Elastic IP to ensure consistent public IP
# Configure DNS A record to point to Elastic IP
EBS Volume for Data
# Attach EBS volume for persistent storage
sudo mkfs.ext4 /dev/xvdf
sudo mkdir /data
sudo mount /dev/xvdf /data
sudo chown c-relay-pg:c-relay-pg /data
# Update systemd service to use /data
sudo sed -i 's/WorkingDirectory=\/opt\/c-relay-pg/WorkingDirectory=\/data/' /etc/systemd/system/c-relay-pg.service
sudo systemctl daemon-reload
Google Cloud Platform
Compute Engine Setup
# Create VM instance (e2-micro or larger)
gcloud compute instances create c-relay-pg-instance \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--machine-type=e2-micro \
--tags=nostr-relay
# Configure firewall
gcloud compute firewall-rules create allow-nostr-relay \
--allow tcp:8888 \
--source-ranges 0.0.0.0/0 \
--target-tags nostr-relay
# SSH and deploy
gcloud compute ssh c-relay-pg-instance
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
sudo examples/deployment/simple-vps/deploy.sh
Persistent Disk
# Create and attach persistent disk
gcloud compute disks create relay-data --size=50GB
gcloud compute instances attach-disk c-relay-pg-instance --disk=relay-data
# Format and mount
sudo mkfs.ext4 /dev/sdb
sudo mkdir /data
sudo mount /dev/sdb /data
sudo chown c-relay-pg:c-relay-pg /data
DigitalOcean
Droplet Creation
# Create Ubuntu 22.04 droplet (Basic plan, $6/month minimum)
# Enable monitoring and backups
# SSH into droplet
ssh root@your-droplet-ip
# Deploy relay
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
examples/deployment/simple-vps/deploy.sh
Block Storage
# Attach block storage volume
# Format and mount as /data
sudo mkfs.ext4 /dev/sda
sudo mkdir /data
sudo mount /dev/sda /data
echo '/dev/sda /data ext4 defaults,nofail,discard 0 2' >> /etc/fstab
Automated Deployment Examples
The examples/deployment/ directory contains ready-to-use scripts:
Simple VPS Deployment
# Clone repository and run automated deployment
git clone https://github.com/your-org/c-relay-pg.git
cd c-relay-pg
sudo examples/deployment/simple-vps/deploy.sh
SSL Proxy Setup
# Set up nginx reverse proxy with SSL
sudo examples/deployment/nginx-proxy/setup-ssl-proxy.sh \
-d relay.example.com -e admin@example.com
Monitoring Setup
# Set up continuous monitoring
sudo examples/deployment/monitoring/monitor-relay.sh \
-c -i 60 -e admin@example.com
Backup Setup
# Set up automated backups
sudo examples/deployment/backup/backup-relay.sh \
-s my-backup-bucket -e admin@example.com
Reverse Proxy Setup
Nginx Configuration
Basic WebSocket Proxy
# /etc/nginx/sites-available/nostr-relay
server {
listen 80;
server_name relay.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8888;
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;
# WebSocket timeouts
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
HTTPS with Let's Encrypt
# Install certbot
sudo apt install -y certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d relay.yourdomain.com
# Auto-renewal (crontab)
echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo crontab -
Enhanced HTTPS Configuration
server {
listen 443 ssl http2;
server_name relay.yourdomain.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/relay.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/relay.yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
# Rate limiting (optional)
limit_req_zone $remote_addr zone=relay:10m rate=10r/s;
limit_req zone=relay burst=20 nodelay;
location / {
proxy_pass http://127.0.0.1:8888;
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;
# WebSocket timeouts
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# Buffer settings
proxy_buffering off;
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name relay.yourdomain.com;
return 301 https://$server_name$request_uri;
}
PHP Admin UI: Public Stats vs Authenticated Admin
The relay ships with a PHP admin UI (in admin/) that talks
directly to PostgreSQL. It is served by nginx + php-fpm and is split into two
URL paths with different access controls:
| URL | Auth | Purpose |
|---|---|---|
https://<domain>/relay/ |
None (public) | Public landing page: relay info + event-rate chart |
https://<domain>/relay/api/chart.php?range=day |
None (public) | Read-only ASCII chart endpoint (aggregate COUNT) |
https://<domain>/relay/admin/ |
HTTP Basic Auth | Full admin UI: config, auth rules, IP bans, DMs, SQL query |
Why Basic Auth is required for /relay/admin/: the PHP endpoints have no
server-side authentication of their own. The nostr_login_lite modal in the
admin UI is a client-side only gate (it only toggles UI visibility in the
browser); it does not send a signed event or session token to the backend.
Without Basic Auth, anyone could hit api/query.php to run SELECT queries
against your database, api/config.php to change relay config, api/auth.php
to edit blacklist/whitelist rules, or api/dm.php to read DMs.
Only admin/api/chart.php is safe to expose
publicly — it runs a fixed aggregate COUNT(*) query with the only user input
being the range selector, which is validated against a whitelist
(hour/day/month/year).
A complete, ready-to-use nginx config including this split is in
examples/deployment/nginx-proxy/nginx.conf.
The relevant blocks:
# Public landing page + chart (no auth)
location /relay/ {
alias /opt/c-relay-pg/admin/public/;
index index.php;
location ~ \.php$ { fastcgi_pass unix:/run/php/php8.2-fpm.sock; ... }
}
location ^~ /relay/api/chart.php { alias /opt/c-relay-pg/admin/api/chart.php; ... }
location ^~ /relay/assets/ { alias /opt/c-relay-pg/admin/assets/; }
# Authenticated admin UI (Basic Auth)
location /relay/admin/ {
alias /opt/c-relay-pg/admin/;
auth_basic "Relay Admin";
auth_basic_user_file /opt/c-relay-pg/admin/.htpasswd;
location ~ \.php$ { fastcgi_pass unix:/run/php/php8.2-fpm.sock; ... }
location ^~ /relay/admin/lib/ { deny all; } # DB credentials
location ^~ /relay/admin/public/ { deny all; } # served via /relay/
}
Create the Basic Auth credentials on the server:
sudo htpasswd -c /opt/c-relay-pg/admin/.htpasswd <username>
sudo systemctl reload nginx
Apache Configuration
WebSocket Proxy with mod_proxy_wstunnel
# Enable required modules
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod ssl
# /etc/apache2/sites-available/nostr-relay.conf
<VirtualHost *:443>
ServerName relay.yourdomain.com
# SSL configuration
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/relay.yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/relay.yourdomain.com/privkey.pem
# WebSocket proxy
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / ws://127.0.0.1:8888/
ProxyPassReverse / ws://127.0.0.1:8888/
# Fallback for HTTP requests
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:8888/$1" [P,L]
# Security headers
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
</VirtualHost>
<VirtualHost *:80>
ServerName relay.yourdomain.com
Redirect permanent / https://relay.yourdomain.com/
</VirtualHost>
Monitoring Setup
System Monitoring
Basic Monitoring Script
#!/bin/bash
# /usr/local/bin/relay-monitor.sh
LOG_FILE="/var/log/relay-monitor.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
# Check if relay is running
if ! pgrep -f "c_relay_pg_x86" > /dev/null; then
echo "[$DATE] ERROR: Relay process not running" >> $LOG_FILE
systemctl restart c-relay-pg
fi
# Check port availability
if ! netstat -tln | grep -q ":8888"; then
echo "[$DATE] ERROR: Port 8888 not listening" >> $LOG_FILE
fi
# Check database file
RELAY_DB=$(find /opt/c-relay-pg -name "*.nrdb" | head -1)
if [[ -n "$RELAY_DB" ]]; then
DB_SIZE=$(du -h "$RELAY_DB" | cut -f1)
echo "[$DATE] INFO: Database size: $DB_SIZE" >> $LOG_FILE
fi
# Check memory usage
MEM_USAGE=$(ps aux | grep c_relay_pg_x86 | grep -v grep | awk '{print $6}')
if [[ -n "$MEM_USAGE" ]]; then
echo "[$DATE] INFO: Memory usage: ${MEM_USAGE}KB" >> $LOG_FILE
fi
Cron Job Setup
# Add to crontab
echo "*/5 * * * * /usr/local/bin/relay-monitor.sh" | sudo crontab -
# Make script executable
sudo chmod +x /usr/local/bin/relay-monitor.sh
Log Aggregation
Centralized Logging with rsyslog
# /etc/rsyslog.d/50-c-relay-pg.conf
if $programname == 'c-relay-pg' then /var/log/c-relay-pg.log
& stop
External Monitoring
Prometheus Integration
# /etc/prometheus/prometheus.yml
scrape_configs:
- job_name: 'c-relay-pg'
static_configs:
- targets: ['localhost:8888']
metrics_path: '/metrics' # If implemented
scrape_interval: 30s
Security Hardening
System Hardening
Service User Restrictions
# Restrict service user
sudo usermod -s /bin/false c-relay-pg
sudo usermod -d /opt/c-relay-pg c-relay-pg
# Set proper permissions
sudo chmod 700 /opt/c-relay-pg
sudo chown -R c-relay-pg:c-relay-pg /opt/c-relay-pg
File System Restrictions
# Mount data directory with appropriate options
echo "/dev/sdb /opt/c-relay-pg ext4 defaults,noexec,nosuid,nodev 0 2" >> /etc/fstab
Network Security
Fail2Ban Configuration
# /etc/fail2ban/jail.d/c-relay-pg.conf
[c-relay-pg-dos]
enabled = true
port = 8888
filter = c-relay-pg-dos
logpath = /var/log/c-relay-pg.log
maxretry = 10
findtime = 60
bantime = 300
DDoS Protection
# iptables rate limiting
sudo iptables -A INPUT -p tcp --dport 8888 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8888 -j DROP
Database Security
Encryption at Rest
# Use encrypted filesystem
sudo cryptsetup luksFormat /dev/sdb
sudo cryptsetup luksOpen /dev/sdb relay-data
sudo mkfs.ext4 /dev/mapper/relay-data
Backup and Recovery
Automated Backup
Database Backup Script
#!/bin/bash
# /usr/local/bin/backup-relay.sh
BACKUP_DIR="/backup/c-relay-pg"
DATE=$(date +%Y%m%d_%H%M%S)
RELAY_DB=$(find /opt/c-relay-pg -name "*.nrdb" | head -1)
mkdir -p "$BACKUP_DIR"
if [[ -n "$RELAY_DB" ]]; then
# SQLite backup
sqlite3 "$RELAY_DB" ".backup $BACKUP_DIR/relay_backup_$DATE.nrdb"
# Compress backup
gzip "$BACKUP_DIR/relay_backup_$DATE.nrdb"
# Cleanup old backups (keep 30 days)
find "$BACKUP_DIR" -name "relay_backup_*.nrdb.gz" -mtime +30 -delete
echo "Backup completed: relay_backup_$DATE.nrdb.gz"
else
echo "No relay database found!"
exit 1
fi
Cron Schedule
# Daily backup at 2 AM
echo "0 2 * * * /usr/local/bin/backup-relay.sh" | sudo crontab -
Cloud Backup
AWS S3 Sync
# Install AWS CLI
sudo apt install -y awscli
# Configure AWS credentials
aws configure
# Sync backups to S3
aws s3 sync /backup/c-relay-pg/ s3://your-backup-bucket/c-relay-pg/ --delete
Disaster Recovery
Recovery Procedures
# 1. Restore from backup
gunzip backup/relay_backup_20231201_020000.nrdb.gz
cp backup/relay_backup_20231201_020000.nrdb /opt/c-relay-pg/
# 2. Fix permissions
sudo chown c-relay-pg:c-relay-pg /opt/c-relay-pg/*.nrdb
# 3. Restart service
sudo systemctl restart c-relay-pg
# 4. Verify recovery
sudo journalctl -u c-relay-pg --since "1 minute ago"
This deployment guide provides comprehensive coverage for deploying the C Nostr Relay across various environments while taking full advantage of the event-based configuration system's simplicity and security features.