Skip to main content

Blossom

Blobs Stored Simply on Mediaservers. Decentralized, censorship-resistant media hosting for Nostr.

Overview

Blossom is a protocol for storing and retrieving binary data (images, videos, audio) across multiple servers. Using hash-based addressing and Nostr for authorization, Blossom ensures your media remains available even if individual servers go down.

How It Works

┌─────────────────────────────────────────────────────────┐
│ Blossom Protocol │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. Upload image to Blossom server │
│ ┌───────────────────────────────────────────────┐ │
│ │ image.jpg ──► SHA256 hash ──► Store blob │ │
│ │ Result: abc123... │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ 2. Publish Nostr event with hash │
│ ┌───────────────────────────────────────────────┐ │
│ │ Event references: https://server.com/abc123 │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ 3. Any client can fetch from any Blossom server │
│ ┌───────────────────────────────────────────────┐ │
│ │ Server A down? → Try Server B with same hash │ │
│ │ Same file, verified by hash │ │
│ └───────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

Key Features

Hash-Based Addressing

Files are addressed by their SHA-256 hash:

┌─────────────────────────────────────────┐
│ File: photo.jpg │
│ Hash: a1b2c3d4e5f6... │
│ │
│ URL: https://blossom.server/a1b2c3d4 │
│ │
│ Any server with this hash = same file │
│ Hash verifies file integrity │
└─────────────────────────────────────────┘

Benefits:

  • Verification — Prove you got the right file
  • Deduplication — Same file, one hash
  • Server-agnostic — Get from any mirror

Mirroring

┌─────────────────────────────────────────────────────────┐
│ Mirror Setup │
├─────────────────────────────────────────────────────────┤
│ │
│ Primary Server: blossom.example.com │
│ Mirrors: │
│ ├── blossom.nostr.build │
│ └── cdn.backup.com │
│ │
│ Upload Flow: │
│ File → Primary → Auto-mirror to backups │
│ │
│ Retrieval Flow: │
│ Primary down? → Try Mirror 1 → Try Mirror 2 │
│ │
│ Always available, always the same file │
│ │
└─────────────────────────────────────────────────────────┘

Nostr Integration

Your Blossom settings are published to Nostr:

{
"kind": 10063,
"tags": [
["server", "https://blossom.primary.com", "primary"],
["server", "https://blossom.mirror.com", "mirror"]
],
"content": ""
}

Clients read your settings to know where to fetch your media.

Censorship Resistance

ScenarioTraditional HostingBlossom
Server bans youContent goneUse another server
Server goes downContent unavailableMirrors still work
Regional blockCan't accessTry different server

BUD Specifications

Blossom is defined by BUDs (Blossom Upgrade Documents):

BUDNameDescription
BUD-01Basic OperationsUpload, download, delete
BUD-02Blob DescriptorsMetadata for blobs
BUD-04MirroringAuto-copy to backups
BUD-05Media EndpointsStandard URLs
BUD-06HEAD RequestsCheck existence
BUD-08File MetadataExtended info

NIP-B7

The Nostr Integration Proposal for Blossom:

{
"kind": 1,
"content": "Check out this image!",
"tags": [
["imeta",
"url https://blossom.server/abc123",
"x abc123...",
"m image/jpeg",
"dim 1920x1080",
"blurhash LKJHGF..."
]
]
}

Clients supporting NIP-B7:

  • Know the file hash
  • Can verify downloads
  • Can find mirrors if needed

Public Servers

ServerProviderFeatures
blossom.nostr.buildnostr.buildGlobal CDN
blossom.primal.netPrimalDefault in Primal
Various self-hostedCommunityYour own control

Using nostr.build

Upload: POST https://blossom.nostr.build/upload
Fetch: GET https://npub...blossom.nostr.build/hash

Each user gets their own subdomain for organization.

Client Support

ClientBlossom Support
PrimalNative, default
noStrudelOptional, settings
AmethystSupported
CoracleSupported

Running Your Own

Docker

# docker-compose.yml
version: '3'
services:
blossom:
image: hzrd149/blossom:latest
ports:
- "3000:3000"
volumes:
- ./data:/app/data
environment:
PUBLIC_URL: https://blossom.yourdomain.com

Configuration

{
"port": 3000,
"dataDir": "./data",
"maxUploadSize": 100000000,
"allowedMimeTypes": [
"image/*",
"video/*",
"audio/*"
]
}

Authorization

Uploads require Nostr-signed auth events:

{
"kind": 24242,
"tags": [
["t", "upload"],
["size", "1234567"],
["x", "sha256hash"]
],
"content": ""
}

The signature proves you own the keypair, authorizing the upload.

Comparison

FeatureBlossomIPFSTraditional CDN
AddressingHashHashURL
AuthorizationNostrNoneAPI keys
MirroringNativeP2PManual
Nostr integrationNativeNoneNone
Self-hostingEasyComplexComplex

Use Cases

Social Media

  • Profile pictures
  • Post images/videos
  • Media in DMs

Content Publishing

  • Blog images
  • Podcast hosting
  • Video content

Backups

  • Redundant storage
  • Personal archives
  • Distributed hosting

See Also