Chapter 5A - Web Hosting
5A
Nostr Implementation Possibility - Hex № 5A

Static Websites (nsites)

Publish a whole static website as one signed event: a manifest that maps paths to Blossom file hashes, served by any host server.

1 Overview

This NIP defines the nsite: a static website hosted from Blossom assets. Blossom is a file storage protocol. It stores files as blobs and addresses each blob by its sha256 hash.

An author uploads the site files to Blossom servers. Then the author publishes a site manifest event. The manifest maps each absolute path, such as /index.html, to the sha256 hash of the file at that path. A host server is a HTTP server that serves these pubkey static websites. It reads the manifest from relays, fetches the blobs from Blossom, and serves them to normal web browsers.

The site lives in the author's signature, not on any one server. Any host server can serve it. Any Blossom server can store the files. The spec also defines named sub-sites, immutable snapshots, a deterministic aggregate hash for each site version, and rules for copying a site into another author's namespace.

Hint!

Think of the manifest as a tiny signed file-system table. The website itself never touches a relay. Relays carry only the map; Blossom carries the bytes.

Author Relay Blossom Host srv Visitor 15128 PUT blob REQ GET <sha256> HTTPS
Fig. 5A-1 — The nsite pipeline. The author publishes the manifest to relays and the files to Blossom. The host server joins the two for the visitor.

2 Site Manifest Kinds

A site manifest event must be a regular, replaceable, or addressable event as defined in NIP-01. The spec uses one kind from each storage class:

Manifest kinds
KindStorage classPurpose
15128replaceableRoot site. One per pubkey. Must not include a d tag. This is the pubkey's main site.
35128addressableNamed site. A smaller site under the same pubkey; think of it as a subdomain. Must include a d tag with the site identifier.
5128regularManifest snapshot. An immutable copy of a root or named site at one point in time. See §5.

Named sites and the d tag

A named site uses kind 35128 and must include a d tag. The d tag value is the site identifier inside the author's namespace.

For canonical named-site URLs, the d value must match ^[a-z0-9-]{1,13}$ and must not end with -. The length limit comes from DNS: a DNS label holds at most 63 characters, and the base36 pubkey uses 50 of them. That leaves 1 to 13 characters for the identifier.

Named site manifest - minimum shape
{
  "kind": 35128,
  "content": "",
  "tags": [
    ["d", "blog"],
    ["path", "/index.html", "186ea5fd…ca1c99"],
    ["path", "/post.html", "a1b2c3d4…123456"],
    ["x", <site aggregate sha256>, "aggregate"]
  ],
  …pubkey, created_at, id, sig as in NIP-01…
}

3 Manifest Tags

The manifest's content carries no site data; everything lives in the tags. The full tag set:

Nsite event tags
TagRoleRequired
pathMaps an absolute path to the sha256 hash of the file served at that path.Yes, one or more
xCarries the aggregate hash of the full manifest, so the site version is indexable and discoverable.Optional, recommended (mandatory in snapshots, see §5)
dShort identifier of a named site. Used only on kind 35128.Named sites only
aReferences the immediate parent nsite that this copied site was made from.Copied sites only (and snapshots, see §5)
AReferences the origin nsite of a copied site's lineage.Copied sites only
serverHints which Blossom servers can serve the blobs.No
titleHuman-readable site title.No
descriptionShort human-readable description of the site.No
sourcePoints to the site's source code repository or source archive.No
appReferences an upstream app descriptor event that the manifest is part of. See §6.No

Tag formats

  • path — the event must include one or more path tags. Each has the exact format ["path", "/absolute/path", "sha256hash"]. The first element is the literal string "path". The second element is an absolute path that ends with a filename and extension. The third element is the sha256 hash of the file served under that path.
  • x — the event should include one x tag with the site aggregate hash (§4). Its format is ["x", "<sha256-hex>", "aggregate"], where the hash is lowercase hexadecimal.
  • server — the event may include server tags that hint which Blossom servers hold the blobs.
  • title / description — the event may include these tags for simple site information.
  • source — the event may include one source tag with the format ["source", "<url>"]. For a source code repository, the URL may be a nostr:// git URL as defined by NIP-34, or an absolute https:// git URL. For a source archive, the URL must be an absolute https:// URL.
Tech Note

The source spec's own tag table is out of sync with its prose in three places. It omits the app tag, which the app-descriptor section defines. It marks x as optional, but snapshots (kind 5128) require exactly one. It marks a as "copied sites only", but snapshots also require exactly one a tag. The table above restores all three.

4 The Aggregate Hash

The aggregate hash is a deterministic hash of the site's path tags. It identifies one specific site version. Two site manifest events are equivalent if and only if they produce the same aggregate hash, even when different pubkeys publish them.

The hash must be computed from the path tags only. It must not depend on the order of tags in the manifest. It must ignore every other tag and every other event field, including app tags. Compute it like this:

  1. Collect every path tag.
  2. For each tag, produce one line in the exact format <sha256hash> <absolute-path>\n.
  3. Sort all lines in ascending lexicographic order.
  4. Concatenate the sorted lines as UTF-8 bytes.
  5. Compute the SHA-256 hash of the bytes.

Encode the digest as lowercase hexadecimal. Put it in the manifest as ["x", "<sha256-hex>", "aggregate"] so it is indexable for discovery and lookup. Root and named sites should carry this tag. Snapshot events of kind 5128 must carry exactly one, and it must exactly match the parent nsite's aggregate x tag.

Aggregate input - sorted lines
186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99 /index.html
fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321 /favicon.ico
Computing the aggregate - shell and JavaScript
# shell, using nak + jq
nak req -k 15128 -a <pubkey> --limit 1 wss://relay.example.com \
  | jq -r '.tags[] | select(.[0] == "path") | "\(.[2]) \(.[1])"' \
  | sort | sha256sum

// JavaScript
const aggregateHash = sha256(
  event.tags.filter(t => t[0] === "path")
    .map(([, path, hash]) => `${hash} ${path}\n`)
    .sort().join(""))

5 Manifest Snapshots

A manifest snapshot uses kind 5128 and is a regular event. It captures the state of a root site or named site at one point in time. Root and named sites are replaceable, so old versions vanish; a snapshot keeps a historical version addressable by its own event id. Authors may publish snapshots alongside site updates to preserve version history.

A snapshot of an existing root or named site follows these rules:

  • It must copy the source manifest's path tags.
  • It must include exactly one x tag whose value exactly matches the source manifest's aggregate x tag.
  • It must include exactly one a tag that references the source root site or named site.
  • If the source manifest includes an A tag, the snapshot must copy that A tag unchanged. If the source has no A tag, the snapshot should omit it.
  • Other valid nsite tags — title, description, source, app, server, relay hints — may be copied if the author wants to preserve them. They are optional.

The a tag identifies which site the snapshot captures. The snapshot's own created_at is the version timestamp of that snapshot.

Manifest snapshot - kind 5128
{
  "kind": 5128,
  "pubkey": "266815e0…cd408a5",
  "created_at": 1727373475,          // the version timestamp
  "content": "",
  "tags": [
    ["a", "35128:266815e0…cd408a5:blog"],   // the snapshotted site
    ["path", "/index.html", "186ea5fd…ca1c99"],
    ["path", "/post.html", "a1b2c3d4…123456"],
    ["x", <site aggregate sha256>, "aggregate"],
    ["server", "https://blossom.example.com"],
    ["title", "My Blog v1"],
    ["description", "An immutable snapshot of a blog hosted on Nostr"],
    ["source", "https://github.com/example/my-nostr-blog"]
  ]
}

6 Upstream App Descriptors

An nsite may include one or more app tags. Each references an upstream app descriptor event that the manifest is part of. The format is ["app", "<kind>:<pubkey>:<d-tag>", "<relay>"]. The second element is an addressable event reference to the app descriptor. The third element is a relay hint for locating that event.

This linkage is optional. It makes room for application-level discovery and packaging flows without extending the nsite manifest itself. The descriptor kind is intentionally generic: an nsite can reference NIP-89 applications and other present or future descriptor forms. App store metadata, discoverability, and runtime compatibility belong in those descriptor events. The nsite manifest stays focused on describing the site's files.

7 Copying nsites

An nsite may be created by copying another root site or named site. The copy pins that site under a different author's namespace at a specific state. The new author then maintains it independently. Authors who copy a site should copy all tags from the source whenever they remain applicable: path, x, app, server, title, description, and source.

A copy does not have to keep the original kind or identifier. A copied root site may become a named site. A copied named site may become a root site. A copied named site may use a different d value than its parent. Because kind and identifier can change, the lineage tags below are the stable, indexable references:

  • A copied site must include exactly one lowercase a tag that references the immediate parent nsite it was copied from.
  • A copied site must include exactly one uppercase A tag that references the origin nsite of the copy lineage.
  • For a direct copy of an original site, a and A may reference the same nsite.
  • For a copy of a copied site, the A tag must be copied unchanged from the parent site.
  • Sites that were not copied should not include a or A tags.
Origin (Alice) 35128 - d:blog Copy #1 (Bob) 35128 - d:blog Copy #2 (Carol) 35128 - d:blog a a A A
Fig. 5A-2 — Copy lineage. The a tag names the immediate parent. The A tag always names the origin site, copied unchanged down the chain.
Copied site - Carol copies Bob's copy of Alice's blog
{
  "kind": 35128,
  "pubkey": "7a6815e0…cd4bbbb",          // Carol
  "content": "",
  "tags": [
    ["d", "blog"],
    // immediate parent: Bob's copied site, not the origin
    ["a", "35128:9f6815e0…cd4000ff:blog"],
    // origin of the lineage: Alice's site, copied unchanged from Bob
    ["A", "35128:266815e0…cd408a5:blog"],
    ["path", "/index.html", "186ea5fd…ca1c99"],
    ["path", "/post.html", "c1b2c3d4…123456"],
    ["x", <site aggregate sha256>, "aggregate"],
    ["title", "My Derived Blog"]
  ]
}

A snapshot may also capture a copied site. The snapshot's a tag then points at the copied site being captured, and the copied site's A tag is carried over unchanged, so the origin lineage survives in the snapshot.

8 Host Server Behavior

A host server is a HTTP server responsible for serving pubkey static websites. This section defines how it maps hostnames to manifests and paths to blobs.

Address formats

For interoperability, host servers should use these canonical URL formats. Each site fits in a single DNS label, which avoids wildcard-certificate limitations with multi-level subdomains.

Canonical subdomain formats
Site typeLabel formatExample
Root site<npub>.nsite-host.comnpub10phxfsms72rhafrklqdyhempujs9h67nye0p67qe424dyvcx0dkqgvap0e.nsite-host.com
Snapshotv<snapshotIdB36>.nsite-host.comv<50-char-snapshotIdB36>.nsite-host.com
Named site<pubkeyB36><dTag>.nsite-host.com<50-char-pubkeyB36><dTag>.nsite-host.com
  • pubkeyB36 is the author's raw 32-byte pubkey in base36: lowercase, digits 0-9 then letters a-z, no padding. It is always exactly 50 characters.
  • snapshotIdB36 is the raw 32-byte snapshot event id in the same base36 encoding, also always exactly 50 characters.
  • dTag is the d tag value as plain text, appended directly after pubkeyB36 with no separator.

A host server that uses subdomain routing may serve anything at its own root domain, nsite-host.com — a landing page, for example.

Parsing the label

For canonical subdomain formats, the host server must parse the left-most DNS label in this order:

  1. If the label is a valid npub (NIP-19), decode it and resolve the root site manifest (kind 15128).
  2. Otherwise, if the label matches ^v[0-9a-z]{50}$, treat it as a snapshot label. The final 50 characters after the leading v are snapshotIdB36. Decode them to the 32-byte event id. Resolve the kind 5128 snapshot with that id.
  3. Otherwise, if the label matches ^[0-9a-z]{50}[a-z0-9-]{1,13}$ and does not end with -, treat it as a named-site label. The first 50 characters are pubkeyB36; decode them to a 32-byte pubkey. The remaining 1-13 characters are the dTag. Resolve the kind 35128 manifest for that pubkey and identifier.
  4. If parsing fails, the host server must treat the site as not found.

Resolving paths

Once the host server finds the manifest event, it must extract the path-to-hash mappings from the path tags. It should look for a path tag whose second element matches the requested path. When it finds the match, it uses the sha256 hash in the third element to retrieve the file.

Index pages. If the request path does not end with a filename, the host server must fall back to the index.html filename. For example, / becomes /index.html and /blog/ becomes /blog/index.html.

Retrieving blobs

  • The host server should prioritize the manifest's server tags as hints. If the manifest has server tags, try those Blossom servers first.
  • If the pubkey has a kind 10063 BUD-03 user servers event, the host server must attempt to retrieve the file from the listed servers, using the GET /<sha256> path defined in BUD-01.
  • If the pubkey has no 10063 event and the manifest has no server tags, the host server must respond with status code 404.

The host server should verify that the sha256 hash of any served blob matches the hash in the corresponding path tag. If the hash does not match, the host server should treat the site as not found.

The host server must forward the Content-Type and Content-Length headers from the Blossom server. If neither is defined, it may set Content-Type from the file extension in the requested path.

Not found

If the host server cannot find a site manifest event, or cannot find a matching path tag for the requested path, it must use /404.html as a fallback path.

Warning!

Serve only bytes whose sha256 matches the manifest. A Blossom server can return anything; the hash check is the only thing that binds the response to the author's signature.

Browser Host srv Relay Blossom GET /blog/ parse label → pubkey - path → /blog/index.html ["REQ", {"kinds":[15128]}] ["EVENT", manifest] GET /186ea5fd…ca1c99 blob + Content-Type/Length verify sha256(blob) == path hash 200 OK + headers On any failure: fall back to /404.html.
Fig. 5A-3 — One page load. Label to pubkey, pubkey to manifest, path to hash, hash to blob, blob to browser.

9 Worked Example

Alice publishes her root site. The manifest below is the complete event she signs and sends to her relays. The three files were uploaded to blossom.example.com first.

Root site manifest - kind 15128
{
  "content": "",
  "created_at": 1727373475,
  "id": "5324d695ed7abf7cdd2a48deb881c93b7f4e43de702989bbfb55a1b97b35a3de",
  "kind": 15128,
  "pubkey": "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5",
  "sig": "f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094c…",
  "tags": [
    ["path", "/index.html",
      "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"],
    ["path", "/about.html",
      "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"],
    ["path", "/favicon.ico",
      "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"],
    ["x", <site aggregate sha256>, "aggregate"],
    ["app", "31990:266815e0…cd408a5:my-app", "wss://relay.example.com"],
    ["app", "32267:266815e0…cd408a5:com.example.webapp", "wss://relay.example.com"],
    ["server", "https://blossom.example.com"],
    ["title", "My Nostr Site"],
    ["description", "A static website hosted on Nostr"],
    ["source", "https://github.com/example/my-nostr-site"]
  ]
}

A visitor opens https://npub10phxfsms72rha….nsite-host.com/about.html. The host server resolves the request like this:

Host server session - resolving /about.html
-- browser to host server --
→ GET /about.html  Host: npub10phxfsms72rha….nsite-host.com
-- label is a valid npub: decode → pubkey 266815e0…cd408a5 --

-- host server to relay --
→ ["REQ", "site", {"kinds":[15128], "authors":["266815e0…cd408a5"], "limit":1}]
← ["EVENT", "site", {"kind":15128, "tags":[["path","/about.html","a1b2c3d4…"], …]}]
← ["EOSE", "site"]

-- host server to blossom (server tag hint tried first) --
→ GET https://blossom.example.com/a1b2c3d4e5f6…ef123456
← 200 OK  Content-Type: text/html  Content-Length: 1832
-- verify sha256(body) == a1b2c3d4e5f6…ef123456 : match --

-- host server to browser --
← 200 OK  Content-Type: text/html  Content-Length: 1832  (forwarded)

-- a request for /missing.png finds no path tag --
→ GET /missing.png
← 200 OK  (body of /404.html, the mandatory fallback path)

10 Version History

NIP-5A is young and moves fast. Notable changes, newest first:

  • 20262026-06Added the aggregate hash, the x tag, and manifest snapshots (kind 5128).
  • 20262026-06Typo fixes and code-fence formatting cleanups.
  • 20262026-04Renamed the source file to 5A.md for clarity.
  • Origin2026-03hzrd149 published NIP-5A: root sites, named sites, copying, and the host server rules.

Original Proposal - Credits

Authorhzrd149
First commit2026-03-25 — “NIP-5A: Static Websites (#1538)” - PR #1538
Statusdraft - optional
Source5A.md on GitHub - full change history

11 See Also

  • NIP-01 — regular, replaceable, and addressable events; the storage classes the three manifest kinds use.
  • NIP-19 — the npub encoding parsed from root-site subdomain labels.
  • NIP-34 — nostr:// git URLs accepted in the source tag.
  • NIP-89 — app handler events, one target of the app descriptor tag.
  • Blossom (BUD-01, BUD-03) — blob storage, GET /<sha256>, and the kind 10063 user server list.