nostr-deploy-server
v2.0.2
Published
Node.js server for hosting static websites under npub subdomains using Nostr protocol and Blossom servers
Maintainers
Readme
Nostr Deploy - Server Implementation
A Node.js server implementation for serving static websites under npub subdomains using the Nostr protocol and Blossom servers. This server implements the Pubkey Static Websites NIP specification, allowing decentralized hosting of static websites without storing any data locally.
🌟 Features
Decentralized Hosting: Serves websites using Nostr events (kind 34128) for path mappings and Blossom servers for file storage
Npub Subdomains: Supports
npub1xxx.example.comwildcard subdomain routingZero Storage: Acts as a pure proxy/gateway without storing any files locally
Advanced Multi-Layer Caching: Sophisticated caching system with multiple backends (In-Memory, Redis, SQLite), intelligent cache invalidation, TTL management, and comprehensive monitoring tools for optimal performance
Real-Time Cache Invalidation: Proactive pre-caching system that updates cache entries immediately when content is published to Nostr, ensuring zero-latency responses for users
Automatic Fallbacks: Falls back to
/404.htmlfor missing files and default servers when user configurations are unavailableRate Limiting: Configurable rate limiting to prevent abuse
Graceful Shutdown: Proper cleanup of connections and resources
🚀 Key Innovation: Real-Time Pre-Caching
This server's real-time cache invalidation system sets it apart from traditional static hosting solutions:
- 📡 Nostr Event Monitoring: Continuously monitors Nostr relays for content updates
- ⚡ Zero-Latency Responses: Content is cached before users visit, not after
- 🔄 Instant Updates: Cache refreshes immediately when content is published to Nostr
- 🎯 Smart Pre-Loading: Automatically caches file mappings, relay lists, and server preferences
Traditional Flow: User visits → Query Nostr → Wait 500-2000ms → Serve content
Our Flow: Content published → Instant cache update → User visits → Serve in 5-50ms
📋 NIP Implementation
This server implements the Pubkey Static Websites NIP which allows:
Static File Events (Kind 34128): Maps file paths to SHA256 hashes
{ "kind": 34128, "tags": [ ["d", "/index.html"], ["x", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"] ] }Relay Lists (Kind 10002): User's preferred Nostr relays (NIP-65)
Blossom Server Lists (Kind 10063): User's preferred Blossom servers (BUD-03)
🚀 Quick Start
Install dependencies:
npm installConfigure environment variables:
cp env.example .env # Edit .env with your configurationRun in development mode:
npm run devBuild and run in production:
npm run build npm start
🔧 Advanced Caching System
This server features a sophisticated multi-layer caching system inspired by modern NoSQL and distributed caching patterns. The caching system supports multiple backends and provides significant performance improvements.
Cache Backends
The server supports three cache backends:
In-Memory Cache (Default)
CACHE_PATH=in-memoryRedis Cache (Recommended for production)
CACHE_PATH=redis://localhost:6379 # or with authentication: CACHE_PATH=redis://username:password@localhost:6379/0SQLite Cache (Good for single-instance deployments)
CACHE_PATH=sqlite:///path/to/cache.db
Cache Layers
The caching system includes multiple specialized cache layers:
- Domain Resolution Cache: Maps domain names to pubkeys
- Blossom Servers Cache: Caches available blossom servers per pubkey
- Relay Lists Cache: Caches relay lists per pubkey
- Path Mapping Cache: Maps file paths to blob metadata
- Blob URLs Cache: Caches available URLs for each blob
- File Content Cache: Caches actual file content
- Negative Cache: Caches "not found" results to avoid repeated lookups
Real-Time Cache Invalidation
The server features a sophisticated real-time cache invalidation system that proactively updates cache entries as content is published to Nostr, ensuring users always get the latest content without waiting for cache expiration.
How It Works
- Nostr Event Subscription: The server subscribes to configured Nostr relays and monitors for relevant events
- Event Processing: When events are published, the cache is immediately updated with new information
- Pre-Caching: Content is cached before users visit the site, ensuring zero-latency responses
Monitored Event Types
- Kind 34128: Static file events (file path → SHA256 mappings)
- Kind 10002: Relay list events (user's preferred Nostr relays)
- Kind 10063: Blossom server list events (user's preferred file servers)
Benefits
- Zero-Latency Responses: Files are already cached when users visit
- Always Fresh Content: Cache updates immediately when content is published
- Reduced Load: Fewer queries to Nostr relays during user requests
- Better User Experience: Instant page loads for published content
Configuration
Enable real-time cache invalidation in your .env file:
# Enable real-time cache invalidation (default: true)
REALTIME_CACHE_INVALIDATION=true
# Relays to monitor for cache invalidation events
INVALIDATION_RELAYS=wss://relay.primal.net,wss://relay.damus.io,wss://relay.nostr.band
# Timeouts for invalidation connections
INVALIDATION_TIMEOUT_MS=30000
INVALIDATION_RECONNECT_DELAY_MS=5000Example Workflow
- Publisher: User publishes
/index.html→ SHA256 mapping (Kind 34128) to Nostr - Server: Immediately receives event and updates cache with new mapping
- User: Visits
npub1xyz.example.com/index.html→ Gets instant response from cache - Result: Zero-latency response because content was pre-cached
Cache Configuration
Add these environment variables to your .env file:
# Advanced Caching Configuration
CACHE_PATH=in-memory # or redis://... or sqlite://...
CACHE_TIME=3600 # Default TTL in seconds (1 hour)Testing the Cache System
Run the comprehensive cache test suite:
# Test all cache functionality
npm run test:cache
# Run all tests including cache tests
npm test
# Run tests in watch mode
npm run test:watchComplete Documentation
For comprehensive documentation on the caching system, see: 📖 Caching Documentation
This includes:
- Detailed architecture overview
- Complete API reference
- Performance benchmarks
- Monitoring and troubleshooting guides
- Production deployment strategies
Setting Up Redis Cache Backend
Redis is the recommended cache backend for production deployments as it provides persistence, horizontal scaling, and high performance.
Installing Redis
macOS (using Homebrew):
# Install Redis
brew install redis
# Start Redis service
brew services start redis
# Or start manually
redis-serverUbuntu/Debian:
# Update package list
sudo apt update
# Install Redis
sudo apt install redis-server
# Start Redis service
sudo systemctl start redis-server
sudo systemctl enable redis-server
# Check Redis status
sudo systemctl status redis-serverDocker (Recommended for development):
# Run Redis in Docker
docker run -d --name redis-cache -p 6379:6379 redis:alpine
# With persistence
docker run -d --name redis-cache \
-p 6379:6379 \
-v redis-data:/data \
redis:alpine redis-server --appendonly yesCentOS/RHEL/Rocky Linux:
# Install EPEL repository
sudo dnf install epel-release
# Install Redis
sudo dnf install redis
# Start and enable Redis
sudo systemctl start redis
sudo systemctl enable redisRedis Configuration
Create a Redis configuration for production:
# Create Redis config directory
sudo mkdir -p /etc/redis
# Create configuration file
sudo tee /etc/redis/redis.conf << EOF
# Network configuration
bind 127.0.0.1
port 6379
protected-mode yes
# Memory configuration
maxmemory 256mb
maxmemory-policy allkeys-lru
# Persistence configuration
save 900 1
save 300 10
save 60 10000
# Security
requirepass your-secure-password-here
# Logging
loglevel notice
logfile /var/log/redis/redis-server.log
# Performance
tcp-keepalive 300
timeout 0
EOF
# Restart Redis with new config
sudo systemctl restart redisEnvironment Configuration for Redis
Add to your .env file:
# Basic Redis connection
CACHE_PATH=redis://localhost:6379
# Redis with password
CACHE_PATH=redis://:your-password@localhost:6379
# Redis with username and password (Redis 6+)
CACHE_PATH=redis://username:password@localhost:6379
# Redis with database selection
CACHE_PATH=redis://localhost:6379/0
# Redis with custom connection options
CACHE_PATH=redis://localhost:6379?retry_delay=100&max_attempts=3
# Redis cluster or remote server
CACHE_PATH=redis://redis.example.com:6379Redis Monitoring
Monitor Redis performance:
# Connect to Redis CLI
redis-cli
# Monitor commands in real-time
redis-cli monitor
# Get Redis info
redis-cli info
# Check memory usage
redis-cli memory usage
# List all keys (use with caution in production)
redis-cli keys "*"
# Get cache statistics
redis-cli info statsSetting Up SQLite Cache Backend
SQLite is perfect for single-instance deployments or when you want persistent caching without setting up a Redis server.
Installing SQLite
SQLite comes pre-installed on most systems, but you can install it if needed:
macOS:
# Usually pre-installed, or install via Homebrew
brew install sqliteUbuntu/Debian:
sudo apt update
sudo apt install sqlite3CentOS/RHEL:
sudo dnf install sqliteSQLite Configuration
SQLite requires minimal configuration. Just specify the database file path:
# Environment configuration for SQLite
CACHE_PATH=sqlite:///var/lib/nostr-deploy/cache.db
# Or relative path
CACHE_PATH=sqlite://./data/cache.db
# In-memory SQLite (loses data on restart)
CACHE_PATH=sqlite://:memory:Creating SQLite Cache Directory
# Create cache directory
sudo mkdir -p /var/lib/nostr-deploy
sudo chown $(whoami):$(whoami) /var/lib/nostr-deploy
# Or for local development
mkdir -p ./dataSQLite Performance Tuning
For better SQLite performance, create a configuration script:
// SQLite performance optimization (optional)
// Add to your application startup code
import Database from 'better-sqlite3';
if (process.env.CACHE_PATH?.startsWith('sqlite://')) {
const dbPath = process.env.CACHE_PATH.replace('sqlite://', '');
const db = new Database(dbPath);
// Performance optimizations
db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = 1000000;
PRAGMA temp_store = memory;
PRAGMA mmap_size = 268435456;
`);
db.close();
}SQLite Monitoring
Monitor SQLite database:
# Connect to SQLite database
sqlite3 /var/lib/nostr-deploy/cache.db
# List tables
.tables
# Get database info
.dbinfo
# Check database size
.dbconfig
# Analyze database performance
ANALYZE;
# Vacuum database (optimize storage)
VACUUM;Cache Backend Comparison
| Feature | In-Memory | Redis | SQLite | | -------------------- | ---------------- | ------------ | ----------------- | | Performance | Fastest | Very Fast | Fast | | Persistence | None | Yes | Yes | | Scalability | Single Process | Horizontal | Single Process | | Memory Usage | High | Configurable | Low | | Setup Complexity | None | Medium | Low | | Production Ready | Development Only | Yes | Small-Medium Apps | | Network Overhead | None | Low | None |
Cache Performance Benchmarks
Typical performance characteristics:
- In-Memory: ~1-2ms response time, 100,000+ ops/sec
- Redis: ~2-5ms response time, 50,000+ ops/sec
- SQLite: ~5-10ms response time, 10,000+ ops/sec
Troubleshooting Cache Issues
Redis Connection Issues:
# Test Redis connection
redis-cli ping
# Check Redis logs
sudo tail -f /var/log/redis/redis-server.log
# Verify Redis is listening
sudo netstat -tlnp | grep 6379SQLite Permission Issues:
# Fix file permissions
sudo chown -R $(whoami):$(whoami) /var/lib/nostr-deploy/
chmod 755 /var/lib/nostr-deploy/
chmod 644 /var/lib/nostr-deploy/cache.dbMemory Issues:
# Check Redis memory usage
redis-cli info memory
# Monitor system resources
htop
df -hPerformance Benefits
The advanced caching system provides:
- 3-5x faster response times for cached requests
- Reduced load on Nostr relays through intelligent caching
- Lower bandwidth usage with blob URL availability caching
- Improved reliability with negative caching for failed lookups
- Horizontal scaling support with Redis backend
⚙️ Configuration
Configure the server using environment variables or create a .env file:
# Server Configuration
PORT=3000
NODE_ENV=production
# Domain Configuration
BASE_DOMAIN=example.com
# Default Nostr Relays (comma-separated)
DEFAULT_RELAYS=wss://relay.nostr.band,wss://nostrue.com,wss://purplerelay.com,wss://relay.primal.net,wss://nos.lol,wss://relay.damus.io,wss://relay.nsite.lol
# Default Blossom Servers (comma-separated)
DEFAULT_BLOSSOM_SERVERS=https://cdn.hzrd149.com,https://blossom.primal.net,https://blossom.band,https://loratu.bitcointxoko.com,https://blossom.f7z.io,https://cdn.sovbit.host
# Caching Configuration
CACHE_TTL_SECONDS=300
MAX_CACHE_SIZE=100
# Rate Limiting
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX_REQUESTS=100
# Security & Performance
CORS_ORIGIN=*
TRUST_PROXY=false
REQUEST_TIMEOUT_MS=30000
MAX_FILE_SIZE_MB=50
LOG_LEVEL=infoSSL Certificate Setup
For production deployment, you need a wildcard SSL certificate:
# Example with Let's Encrypt/Certbot
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
-d "*.example.com" \
-d "example.com"
# Update .env with certificate paths
WILDCARD_CERT_PATH=/etc/letsencrypt/live/example.com/fullchain.pem
WILDCARD_KEY_PATH=/etc/letsencrypt/live/example.com/privkey.pem🌐 How It Works
1. Request Flow with Advanced Caching
sequenceDiagram
participant Client
participant Server
participant Cache as Cache Layer
participant Nostr as Nostr Relays
participant Blossom as Blossom Servers
Client->>Server: GET npub123.example.com/index.html
Server->>Server: Resolve npub to pubkey
Server->>Cache: Check domain resolution cache
alt Cache Hit
Cache-->>Server: Return cached pubkey mapping
else Cache Miss
Server->>Nostr: Fetch relay list (kind 10002)
Server->>Cache: Store relay list
end
Server->>Cache: Check path mapping cache
alt Cache Hit
Cache-->>Server: Return cached file mapping
else Cache Miss
Server->>Nostr: Fetch file mapping (kind 34128)
Server->>Cache: Store path mapping
end
Server->>Cache: Check Blossom servers cache
alt Cache Hit
Cache-->>Server: Return cached server list
else Cache Miss
Server->>Nostr: Fetch Blossom servers (kind 10063)
Server->>Cache: Store Blossom servers
end
Server->>Cache: Check file content cache
alt Cache Hit
Cache-->>Server: Return cached file content
Server->>Client: Serve cached file
else Cache Miss
Server->>Blossom: Fetch file by SHA256
Server->>Cache: Store file content
Server->>Client: Serve file with headers
end2. Path Resolution
/→/index.html/blog/→/blog/index.html/about→/about/index.html(if no extension)- Missing files →
/404.htmlfallback
3. Advanced Multi-Layer Caching Strategy
The server implements a sophisticated caching system with multiple specialized layers:
Cache Layers & TTL Configuration
- Domain Resolution Cache: Maps npub subdomains to pubkeys (TTL: 1 hour)
- Relay Lists Cache: User's preferred Nostr relays (kind 10002) (TTL: 5 minutes)
- Path Mapping Cache: File path to SHA256 mappings (kind 34128) (TTL: 5 minutes)
- Blossom Servers Cache: User's preferred Blossom servers (kind 10063) (TTL: 5 minutes)
- Blob URLs Cache: Available URLs for each blob across servers (TTL: 10 minutes)
- File Content Cache: Actual file content with compression (TTL: 30 minutes)
- Negative Cache: "Not found" results to avoid repeated lookups (TTL: 1 minute)
Cache Backends Support
In-Memory Cache (Development)
- Fastest performance (~1-2ms response time)
- No persistence, data lost on restart
- Suitable for single-instance development
Redis Cache (Production Recommended)
- High performance (~2-5ms response time)
- Persistent storage with configurable eviction policies
- Horizontal scaling support with clustering
- Advanced features: pub/sub, transactions, Lua scripting
SQLite Cache (Small/Medium Production)
- Good performance (~5-10ms response time)
- File-based persistence with ACID transactions
- Zero-configuration setup
- Perfect for single-instance deployments
Intelligent Cache Invalidation
- Real-Time Updates: Subscribes to Nostr relays and updates cache immediately when content is published
- Pre-Caching: Content is cached before users visit, ensuring zero-latency responses
- Event-Driven: Monitors Kind 34128 (file mappings), 10002 (relay lists), and 10063 (blossom servers)
- Time-based Expiration: Configurable TTL per cache layer
- Dependency Tracking: Cascading invalidation for related cache entries
- Manual Cache Control: Administrative endpoints for cache management
- Smart Prefetching: Predictive caching based on access patterns
Cache Performance Features
- LRU Eviction: Automatic removal of least recently used entries
- Memory Management: Configurable memory limits with overflow protection
- Compression: Gzip compression for file content cache
- Batch Operations: Efficient bulk cache operations
- Connection Pooling: Optimized Redis/SQLite connection management
- Circuit Breakers: Automatic fallback when cache backend is unavailable
Monitoring & Debugging
- Cache Hit/Miss Metrics: Real-time performance statistics
- Memory Usage Tracking: Detailed memory consumption monitoring
- Debug Logging: Comprehensive cache operation logging
- Health Checks: Cache backend health monitoring endpoints
- Performance Profiling: Request-level cache performance analysis
🔌 API Endpoints
Static File Serving
GET *Main endpoint that serves static files based on npub subdomain routing.
🏗️ Architecture
Project Structure
/
├── src/
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # Utility classes (config, logger, cache)
│ ├── helpers/ # Core helpers (Nostr, Blossom)
│ ├── __tests__/ # Test files
│ └── server.ts # Main server application
├── dist/ # Compiled JavaScript (generated)
├── package.json
├── tsconfig.json
├── jest.config.js
└── README.mdCore Components
- ConfigManager: Environment configuration and validation
- Logger: Structured logging with Winston
- MemoryCache: TTL-based in-memory caching
- NostrHelper: Nostr relay communication and event parsing
- BlossomHelper: Blossom server file fetching
- Express Server: HTTP request handling and routing
🧪 Testing
The server includes comprehensive test coverage:
# Run all tests
npm test
# Run specific test files
npm test -- config.test.ts
npm test -- nostr.test.ts
# Run with coverage
npm test -- --coverage
# Watch mode for development
npm run test:watchTest Categories
- Unit Tests: Individual component testing
- Integration Tests: Component interaction testing
- Mock Tests: External service simulation
- Configuration Tests: Environment validation
🚀 Production Deployment
Docker Deployment
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
COPY .env ./
EXPOSE 3000
CMD ["node", "dist/server.js"]Reverse Proxy Configuration
Nginx
server {
listen 443 ssl http2;
server_name *.example.com;
ssl_certificate /path/to/wildcard.crt;
ssl_certificate_key /path/to/wildcard.key;
location / {
proxy_pass http://localhost:3000;
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;
}
}Apache
<VirtualHost *:443>
ServerName *.example.com
ServerAlias *.example.com
SSLEngine on
SSLCertificateFile /path/to/wildcard.crt
SSLCertificateKeyFile /path/to/wildcard.key
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
ProxyPreserveHost On
</VirtualHost>Process Management
PM2
# Install PM2
npm install -g pm2
# Start with PM2
pm2 start dist/server.js --name nostr-deploy-server
# Setup auto-restart
pm2 startup
pm2 saveSystemd
[Unit]
Description=Nostr Deploy Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/nostr-deploy-server
ExecStart=/usr/bin/node dist/server.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target🔧 Troubleshooting
Common Issues
Invalid npub subdomains returning 404
- Check BASE_DOMAIN configuration
- Verify wildcard DNS setup
- Ensure npub format is correct
Files not loading from Blossom servers
- Check DEFAULT_BLOSSOM_SERVERS configuration
- Verify network connectivity to Blossom servers
- Check server logs for specific error messages
Slow response times
- Increase CACHE_TTL_SECONDS
- Increase MAX_CACHE_SIZE
- Check network latency to relays/Blossom servers
Rate limiting issues
- Adjust RATE_LIMIT_MAX_REQUESTS
- Adjust RATE_LIMIT_WINDOW_MS
- Implement IP whitelisting if needed
Debug Mode
# Enable debug logging
LOG_LEVEL=debug npm start
# Check if server is running
curl http://localhost:3000🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure all tests pass:
npm test - Submit a pull request
Development Guidelines
- Follow TypeScript best practices
- Maintain test coverage above 80%
- Use meaningful commit messages
- Update documentation for new features
📄 License
MIT License - see LICENSE file for details.
🔗 Related Projects
- Nostr Deploy CLI - CLI tool for deploying static sites
- Nostr Protocol
- Blossom Protocol
🙋♂️ Support
- Create an issue for bug reports
- Check existing issues before creating new ones
- Join the Nostr community for protocol discussions
