Nostr Web
Nostr Web enables hosting and browsing static websites as Nostr events, creating censorship-resistant, decentralized web publishing.
What is Nostr Web?
Nostr Web is a protocol and ecosystem for publishing static websites as Nostr events. Instead of traditional web servers, content is stored across Nostr relays, making sites:
- Censorship-resistant — Replicated across multiple relays
- Verifiable — Cryptographically signed by the author
- Decentralized — No single point of failure
- Accessible — Works with existing Nostr infrastructure
Protocol Details
Event Kinds
| Kind | Name | Type | Purpose |
|---|---|---|---|
| 1125 | Asset | Regular | All web assets (HTML, CSS, JS, etc.) |
| 1126 | Page Manifest | Regular | Links assets per page |
| 31126 | Site Index | Addressable | Maps routes (content-addressed) |
| 11126 | Entrypoint | Replaceable | Points to current site index |
DNS Bootstrap
DNS TXT record at _nweb.<host> contains JSON:
{ "v": 1, "pk": "5e56a8f2c91b3d4e7f0a9c1b2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e", "relays": [ "wss://shu01.shugur.net", "wss://shu02.shugur.net", "wss://shu03.shugur.net" ]}Required Fields:
v— Version (currently1)pk— Public key (hex or npub format)relays— Array of WebSocket relay URLs
Note: The DNS record does NOT contain the site index event ID. Clients query relays for the most recent entrypoint (kind 11126) from the specified pubkey, which points to the current site index (kind 31126). This enables automatic updates without DNS changes.
Data Flow
DNS TXT (_nweb.example.com) ↓Entrypoint (kind 11126) ← Query by pubkey ↓Site Index (kind 31126) ← Reference via 'a' tag ↓Page Manifest (kind 1126) ← Get for specific route ↓Assets (kind 1125) ← HTML, CSS, JS, etc.Key Change: DNS is set once. All content updates happen via new entrypoint events pointing to new site indexes, eliminating DNS propagation delays.
Security Model
1. Author Pinning
DNS TXT record pins the site’s public key. All events MUST be authored by this public key. Events from other authors are rejected.
2. Content Addressing & Integrity
All assets (kind 1125) MUST include SHA256 content hash in the x tag:
["x", "a3f9c8b2e1d0..."]This enables:
- Content deduplication - Relays can identify identical assets
- Integrity verification - Clients verify content matches the hash
- Efficient caching - Assets can be cached by hash
3. Content Security Policy (CSP)
Content is rendered in a sandboxed environment with strict CSP:
Extension Pages:
script-src 'self' 'wasm-unsafe-eval';object-src 'none';Sandboxed Pages:
script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';4. Rate Limiting
- DNS Queries: Max 10 per host per minute
- Global Limit: Max 100 DNS queries per minute across all hosts
Caching Strategy
DNS Records
- TTL: Offline only (no expiration)
- Strategy: Always fetch fresh from DNS-over-HTTPS
- Fallback: Use cache only when offline
Site Index (kind 31126)
- TTL: 30 seconds
- Strategy: Fetch fresh, content-addressed by
dtag (truncated hash) - Fallback: Use cache if relays offline
Entrypoint (kind 11126)
- TTL: 0 seconds (always fresh)
- Strategy: Always query relays for latest replaceable event
- Rationale: Must detect site updates immediately
Page Manifests (kind 1126)
- TTL: 30 seconds
- Strategy: Fetch fresh, identified by event ID
- Fallback: Use cache if relays offline
Assets (kind 1125)
- TTL: 7 days
- Strategy: Content-addressed by SHA256 hash (from
xtag) - Cache: In-memory with LRU eviction (max 500 entries)
For Publishers: Host Your Site on Nostr
Prerequisites
- Node.js (v16 or later)
- npm or yarn
- A Nostr private key (hex format)
- Access to Nostr relays
- Git (for cloning the repository)
Installation
1. Clone the Repository
First, clone or download the Nostr Web repository:
# Clone the repositorygit clone https://github.com/Shugur-Network/nw-publisher.git
# Navigate to the nw-publisher directorycd nw-publisherAlternatively, install directly from npm (recommended):
npm install -g nw-publisherOr download from GitHub Releases.
2. Install Publisher (if cloned from source)
# Install dependenciesnpm install
# Create global commandnpm linkThis creates a global nw-publisher command you can use anywhere.
Quick Start
1. Set Up Environment Variables
Create a .env file in the publisher directory:
# Required: Your Nostr private key (64-character hex)NOSTR_SK_HEX="your_private_key_hex_here"
# Required: Relay URLs (comma-separated)RELAYS="wss://shu01.shugur.net,wss://shu02.shugur.net,wss://shu03.shugur.net"
# Recommended: Your domain (for DNS TXT record generation)NWEB_HOST="yourdomain.com"2. Publish Your Site
nw-publisher deploy /path/to/your/siteExample:
nw-publisher deploy ../examples/hello-world3. Set Up DNS
The publisher outputs _nweb.txt with instructions. Copy the JSON value into a TXT record:
Host: _nweb.yourdomain.comType: TXTValue: {"v":1,"pk":"npub1...","relays":["wss://relay.damus.io",...]}Note: DNS only needs to be set once! All future site updates happen through new entrypoint events (kind 11126), eliminating DNS propagation delays.
DNS Provider Examples:
Cloudflare:
Type: TXTName: _nwebContent: {"v":1,"pk":"npub1...","relays":["wss://..."]}TTL: AutoRoute 53:
Type: TXTName: _nweb.yourdomain.comValue: "{"v":1,"pk":"npub1...","relays":["wss://..."]}"TTL: 300Publishing Workflow
Static Site Folder ├─> Scan files (HTML, CSS, JS, fonts, etc.) ├─> Compute SHA256 hashes ├─> Sign as Nostr events │ └─> Kind 1125: All assets (HTML, CSS, JS, fonts, images) ├─> Publish to relays (parallel) ├─> Create page manifests (kind 1126) ├─> Update site index (kind 31126) └─> Update entrypoint (kind 11126)Smart Caching
The publisher uses intelligent caching to avoid republishing unchanged content:
Immutable assets (kind 1125):
- Cache source: Nostr relays (queries on every deploy)
- Cache key:
${kind}:${content-hash}(content-addressed) - Behavior: If file unchanged and found on relays, reuses cached event ID
- Benefit: Only publishes new/changed assets
- Reliability: Always reflects true relay state, no stale local files
Addressable events (kind 31126 - Site Index):
- Uses content-addressed
dtag (first 7-12 chars of content hash) - Different content = different event
- Each version is preserved on relays
Replaceable events (kind 11126 - Entrypoint):
- Always republished to point to current site index
- Only latest event per author is kept
- Ensures clients find the newest version
Site Structure
Input (static site):
my-site/ ├─ index.html # Home page ├─ style.css # Stylesheet ├─ app.js # JavaScript └─ about.html # Subpage (optional)Output:
my-site/ ├─ _nweb.txt # DNS setup instructions └─ _nweb.txt.json # DNS TXT JSON (ready to paste)Advanced Usage
Force Full Republish
# Rebuild cache from relaysnw-publisher deploy /path/to/site --rebuild-cacheCustom Relays
RELAYS="wss://custom-relay.example.com" nw-publisher deploy /path/to/siteMulti-Site Publishing
# Same pubkey, different sitesnw-publisher deploy ../examples/hello-worldnw-publisher deploy ../examples/blog
# Each site uses different version trackingEnvironment Variables Reference
| Variable | Required | Description | Example |
|---|---|---|---|
NOSTR_SK_HEX | ✅ Yes | Nostr private key (64-char hex) | a1b2c3d4... |
RELAYS | ✅ Yes | Comma-separated relay URLs | wss://shu01.shugur.net,... |
NWEB_HOST | ⚠️ Recommended | Your domain | yourdomain.com |
Troubleshooting
”Cannot find module ‘nostr-tools’”
Solution: Run npm install first
”NOSTR_SK_HEX not found”
Solution: Create .env file:
echo "NOSTR_SK_HEX=your_hex_key_here" > .env“Failed to publish to relays”
Causes:
- Relays offline (try different relays)
- Events too large (relay limits typically ~64KB)
- Rate limiting (wait a few minutes)
Site Not Loading After Publishing
Check:
- DNS TXT record at
_nweb.<yourdomain.com>is correct - JSON format is valid
- Pubkey in DNS matches published events
- Wait for DNS propagation (5-30 minutes)
Additional Commands
The publisher includes powerful commands for managing your site:
Check Site Status
# Your own sitenw-publisher status
# Another site (no private key needed)nw-publisher status npub1abc123...Version Management
# List all versionsnw-publisher versions list
# Show specific versionnw-publisher versions show 1.0.0
# Compare versionsnw-publisher versions compare 0.9.0 1.0.0Sync Across Relays
# Ensure all versions exist on all relaysnw-publisher syncClean Up Events
# Preview what will be deletednw-publisher cleanup --orphans --dry-run
# Delete orphaned eventsnw-publisher cleanup --orphans
# Delete a specific versionnw-publisher cleanup --version 0.1.0
# Full reset (delete all events)nw-publisher cleanup --allMedia Assets
Large media files (images, videos, fonts) are included as assets (kind 1125) with appropriate MIME types:
<!-- Image --><img src="image.png" alt="Image" />
<!-- Video --><video src="video.mp4" controls></video>
<!-- Font --><style>@font-face { font-family: 'MyFont'; src: url('font.woff2') format('woff2');}</style>The publisher automatically detects MIME types and includes them in the m tag of each asset event.
For Users: Browse Nostr Web Sites
Install the Browser Extension
The Nostr Web browser extension enables transparent browsing of decentralized websites.
Installation Steps
- Download from Chrome Web Store
- Or download from Firefox Add-ons
- Load in Browser:
- Chrome: Open
chrome://extensions/, enable “Developer mode”, click “Load unpacked” - Firefox: Open
about:debugging#/runtime/this-firefox, click “Load Temporary Add-on”
- Chrome: Open
Features
- 🌐 Transparent Browsing — Just type a URL, automatic Nostr Web detection
- ⚡ Fast Loading — Parallel relay fetching with smart caching
- 🔒 Secure — Author verification, SHA256 integrity, sandboxed rendering
- 💾 Smart Caching — DNS offline-only, site index always fresh
- 📡 Multi-Relay — Fetches from multiple relays for redundancy
Using the Extension
Automatic Detection (Recommended)
- Type any domain in your browser address bar (e.g.,
example.com) - Extension automatically checks for
_nweb.<domain>DNS TXT record - If found, loads the Nostr Web site automatically
How it works:
User navigates to example.com ↓Extension intercepts navigation ↓Checks DNS for _nweb.example.com ↓Found? → Load from Nostr relaysNot found? → Allow normal browsingManual Entry
- Click the extension icon in your browser toolbar
- Enter a domain (e.g.,
nweb.shugur.com) - Click “Open” or press Enter
- Page loads in the Nostr Web viewer
Settings
Click the ⚙️ icon in the viewer to access:
- Default Website — Set a site to load on extension first launch
- Clear Cache — Remove all cached DNS records and events (preserves settings)
Troubleshooting
Extension not detecting sites
Problem: Typing a domain doesn’t load Nostr Web site
Solutions:
- Check DNS TXT record exists at
_nweb.<domain> - Wait for DNS propagation (5-30 minutes after DNS update)
- Try manual entry via extension popup
- Check browser console for errors (F12)
Slow loading
Problem: Pages take more than 10 seconds to load
Solutions:
- Check your internet connection
- Verify relays are online and responding
- Clear extension cache (Settings → Clear Cache)
- Try a different network
Scripts not working
Problem: JavaScript on page doesn’t execute
Solutions:
- Check browser console for CSP errors (F12 → Console)
- Verify the site was published correctly
- Try refreshing the page
- Clear cache and reload
Best Practices
For Publishers
- Use Multiple Relays — Include at least 2-3 relays for redundancy
- Optimize Assets — Keep HTML, CSS, JS small (under 64KB per file)
- Enable DNSSEC — Protect against DNS spoofing
- Test Before Publishing — Verify site loads in extension before setting DNS
- Optimize Media — Compress images and fonts for faster loading
For Users
- Use Latest Extension — Keep extension updated for bug fixes and features
- Verify Authors — Check site pubkey matches expected author
- Report Issues — Help improve the ecosystem by reporting bugs
- Clear Cache — If site looks outdated, try clearing cache
- Check Console — Use browser console (F12) to diagnose issues
Example: Complete Publishing Flow
0. Setup Nostr Web Publisher
First, install the Nostr Web publisher:
# Install from npm (recommended)npm install -g nw-publisher
# Or clone from sourcegit clone https://github.com/Shugur-Network/nw-publisher.gitcd nw-publishernpm installnpm link1. Create Your Site
# Navigate to a working directorycd ~
# Create your site directorymkdir my-sitecd my-siteCreate index.html:
<!DOCTYPE html><html><head> <title>My Nostr Web Site</title> <link rel="stylesheet" href="style.css"></head><body> <h1>Welcome to Nostr Web!</h1> <p>This site is hosted on Nostr relays.</p> <script src="app.js"></script></body></html>Create style.css:
body { font-family: sans-serif; max-width: 800px; margin: 50px auto; padding: 20px;}
h1 { color: #7b16ff;}Create app.js:
console.log('Hello from Nostr Web!');2. Configure Publisher
Create .env:
NOSTR_SK_HEX="your_private_key_here"RELAYS="wss://shu01.shugur.net,wss://shu02.shugur.net"NWEB_HOST="example.com"3. Publish
nw-publisher deploy .Output:
✓ Using keypair from NOSTR_SK_HEX📝 Processing assets...[NEW] index.html -> event_id_1[NEW] style.css -> event_id_2[NEW] app.js -> event_id_3✅ Assets: 0 reused, 3 published
📋 Processing manifests...[MANIF] / -> event_id_4 (new)
🗂️ Updating site index...[INDEX] version 0.0.1 -> event_id_5 (new)
🚀 Updating entrypoint...[ENTRY] -> event_id_6 (updated)
📄 Wrote _nweb.txt4. Configure DNS
Add TXT record from _nweb.txt:
Host: _nweb.example.comType: TXTValue: {"v":1,"pk":"npub1...","relays":["wss://..."]}5. Test
- Wait for DNS propagation (5-30 minutes)
- Type
example.comin browser with extension installed - Site loads automatically!
Links and Resources
- Extension Repository: GitHub
- Publisher CLI: GitHub
- Nostr Protocol: nostr.com
- Shugur Relays: shugur.net
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
License
MIT License — See LICENSE file for details.
Nostr Web by Shugur — Decentralizing the web, one site at a time 🌐