Nostr Is Centralizing. By Design.

Centralized infrastructure. Default relay lists. Aggregators. Relay-level spam filters.
Nostr Is Centralizing. By Design.

I’ve been building on Nostr for a few weeks. Long enough to dig through the NIPs, run infrastructure, and notice the gap between what the protocol promises and what the ecosystem actually delivers.

The primitives are correct. Keypair, signed event, relay. Simple. The problem isn’t there.

The problem is the spec shipped before the tooling existed to implement it properly. Every builder since has rationally filled that gap with whatever was available. Which happened to be centralized infrastructure. No single decision was wrong. The compounding was.

> “Decentralized” is a spectrum measured by the cost to a motivated adversary to degrade or surveil the network. Right now, that cost is embarrassingly low — not because the protocol failed, but because each missing primitive got replaced by a shortcut that stuck.


01. Default Relay Lists

Damus, Primal, Amethyst — they all ship with hardcoded relay lists. When they launched, relay discovery didn’t exist. So developers hardcoded whatever was up and reliable. Rational. Temporary.

Temporary became permanent. The practical network is 10–15 well-known servers. Operators know this. Governments know this. Three relays comply with a court order and I lose write access to the social graph I thought was mine.

A decentralized system with centralized defaults is a centralized system — just with extra latency.


02. NIP-65 Is Correct. Also Widely Ignored.

NIP-65 defines outbox and inbox relay lists on my profile. If everyone publishes to their own declared relays, the network topology mirrors the social graph. No central pool. No single load-bearing relay.

In practice: outbox partially works. The inbox side is worse — most clients query the same 10–15 big relays and assume replication got there. Sometimes it did. Often only partially.

The deeper problem: NIP-65 depends on relays gossiping with each other. That gossip layer was never properly built. So clients can’t trust declared relays return a complete picture — and fall back to the big ones.

Which is self-fulfilling. Big relays accumulate everything because clients keep writing there as backup, making them more complete, making clients depend on them more. Strict outbox enforcement has been tried — notes went missing, people complained, enforcement got reverted. More than once.

NIP-65 gets lip service in readmes and silent override in production.


03. Algorithmic Feeds Need Someone to Run the Algorithm

The moment a client offers smart feeds or discovery, it needs to index the entire network — or outsource it to someone who does. Primal does its own indexing. So does Yakihonne. Clients are becoming front-ends to these services.

My keypair doesn’t protect me from a feed curated by an infrastructure provider with its own business model and jurisdictional exposure.

Without proper relay distribution, discovery from the edges is impossible — you need someone in the middle with a full index. The aggregator fills the gap, becomes load-bearing. The algorithm is back. It just speaks WebSocket.


04. Paid Relays Recreate Platform Economics

Paid relays make sense as spam defense. They’re also natural aggregation points. Quality content gravitates to relays with uptime and filtering. Readers follow content. Network effects kick in.

The paid relay market will consolidate into a handful of dominant providers — exactly as happened with email hosting, podcast infrastructure, and every other federated protocol that touched commercial incentives.

The relay operator becomes the new platform. They have my IP, my payment, my social graph. I’ve rebuilt Substack on a different wire format.


05. Spam Got Solved at the Wrong Layer

Relay-level spam filtering — proof of work, payment, invite-only — made complete sense in 2022. WoT tooling didn’t exist. Social graph data was sparse. Operators needed to protect their resources now. So they built gates. It worked.

That’s precisely the problem. A working solution removes the pressure to build the correct one. The correct solution is protocol-level WoT filtering — cryptographic, user-sovereign, portable across relays. It never got built because relay-level gating already “solved” spam.

> The good-enough answer didn’t just delay the right answer. It made it structurally unnecessary — until the whole architecture depended on the workaround.

WoT remains underspecified. Most clients treat it as optional. Every new relay defaults to the same gatekeeping pattern that calcified three years ago.


06. Clients Compete on Retention, Not Sovereignty

Client developers face the same incentives as every social app: DAU, session length, revenue.

Features that matter for decentralization — relay diversity, WoT filtering, local event caching — are invisible to users and impossible to pitch. Features that drive retention — algorithmic feeds, push notifications, polished onboarding — are easy to ship and easy to justify.

Sovereignty doesn’t have a metric. So it gets deprioritized every sprint until it’s a toggle in settings that nobody opens. The incentive structure makes this choice — not the developers.


07. Relay Operators and I Want Different Things

An operator’s incentive is uptime, cost control, spam prevention. None of that requires serving my data reliably. It requires serving enough data to keep people from leaving. A relay that silently drops events from low-traffic accounts is still commercially viable. To the operator: acceptable loss. To me: my content disappearing without explanation.

> I want my data on my terms. The operator wants a sustainable business. In the absence of protocol enforcement, their incentives win — they control the infrastructure.

Paid relays don’t fix this. What I’m buying is write access, not guaranteed availability. Free relays have the inverse problem — they run on goodwill, and when that runs out, the relay goes down and takes its history with it.

In both cases, no cryptographic guarantee my data persists. I’m trusting infrastructure the same way I trust AWS — which is exactly what I was supposed to be escaping.


08. Many Developers Are Missing the Point Entirely

This one is uncomfortable to say but needs to be said.

Nostr is a convenient backend. No database to maintain. No GDPR headaches. No auth system. Publish a signed event, let relays handle storage, done. There are threads on Stacker News with developers seriously asking if they can replace their Postgres database with a Nostr relay. No interest in censorship resistance. Just “it’s easier.”

The result shows in the ecosystem. Apps connecting to a single hardcoded relay. Clients skipping key management UX because “users don’t care.” Projects using Nostr for coordination but storing actual data on S3.

There’s a GitHub repo — awesome-nostr-possibilities — that exists specifically because people noticed Nostr was being treated as Yet Another Social Media Protocol. The repo’s own description is the warning: Nostr will fail if it stays just another social media protocol. That’s from 2023. The ecosystem didn’t course-correct.

> The protocol’s openness — the feature that makes it powerful — is being harvested for convenience while the properties that make it meaningful get quietly discarded.

Nostr is not a backend shortcut. It’s infrastructure for a new trust model — signed data, portable identity, user-controlled social graphs. Every app that ships ignoring sovereignty makes the network a little more normal. A little more like what we already have.


09. True Sovereignty Is a Power User Feature

Right now, genuine data sovereignty on Nostr requires running your own relay. A server. A domain. Maintenance. Cost. Achievable — but only for people with the technical depth and motivation to do it.

Everyone else trusts the default relay list and calls it decentralized.

> The protocol promised sovereignty to all. What it delivered is sovereignty for those willing to operate infrastructure — and a false sense of it for everyone else.

This might actually be the honest model going forward. Not every user needs the same sovereignty level, and not every user should bear the same infrastructure burden. But the ecosystem doesn’t make that tradeoff legible — it presents casual relay usage as equivalent to self-sovereign storage. It isn’t.

The honest version of Nostr is tiered: power users run their own relays and get cryptographic guarantees. Everyone else picks a relay they trust and gets portable identity plus censorship resistance at the key layer — not at the storage layer. Still meaningfully better than Web2. But it requires honesty that full sovereignty is something you build for yourself, not something the default client config gives you.

What’s missing: a one-click personal relay. Verifiable storage commitments without running your own infrastructure. WoT-based relay reputation so I can make an informed trust decision instead of defaulting to whoever the client hardcoded.

The ceiling for power users is high. The floor for everyone else is lower than it should be. The gap is where most of the ecosystem lives — and where most of the centralization hides.


What Needs to Change

WoT filtering as default, not an option. NIP-02, NIP-51, graph distance scoring — the tools exist. The gap is prioritization.

Relay diversity as a visible metric. Show me how many unique relays my notes replicate across. I respond to signals I can see.

Outbox Model fallbacks must be auditable. If a client writes to a default relay because mine was slow, that should be logged and visible — not a silent decision I can’t see or fix.

Lower the floor for self-sovereign storage. One-click personal relays. Verifiable storage commitments. Relay reputation via WoT. The ceiling is already there. The floor needs work.


The Verdict

Every centralization point here was a rational response to a missing tool. Default relay lists because relay discovery wasn’t ready. Aggregators because client-side indexing was too hard. Relay-level spam filters because WoT didn’t exist. Developers building on convenience because the deeper value wasn’t visible to them.

> Each solved problem removed the pressure to build what would have prevented the next one. Slow structural decisions, each reasonable, compounding into something broken. That’s harder to fix than bad intent — because there’s nothing to point at.

The protocol is worth building on. But not by pretending it delivers what it doesn’t yet.

Decentralization is not a feature you add later. It’s a constraint you build under from day one — or you spend years retrofitting it into an ecosystem that already optimized around its absence.


See it for yourself, and test with your own data: <https://analytics.nostr-wot.com/>\
I am building a solution for this. On Nostr. Contact me to talk about it!


Write a comment

@Nostria which I develop, was built with the purpose to help Nostr scale globally and for that we need true decentralization.

Nostria treats indexer relays (NIP-65) was “DNS”, it’s the lookup needed to find the “IP” (relays) of an “domain” (identity).

Yes, it does give new signups some default relays - but existing users, it follows this flow:

  1. Find your relays (kind 10002). Using a default set of indexer relays.
  2. Go find your events. Including your 10086, which is your own set of indexer relays - and from that point, it uses those. Indexer relays should sync between each other, doesn’t take much storage and compute to keep that event for everyone.

Most people should connect to regionally close relays, that’s where they should post. A Japanese user usually curates towards their own audience in their country, connecting to relays across the globe creates massive latency. People should post to relays that is regionally relevant to their audience.

Fragmentation of data is an issue on Nostr and it requires Nostr clients to guide users to better relay selection - and also aggregate and republish whenever it is needed. Nostria helps with migration from older relays, I think Amethyst is working on something similar too.

One-click personal relays is a great idea and if someone makes a service for it, it must support external signups, meaning that Nostr clients can activate it without leaving the app, and use NWC to perform payments.

Added integration with the latest Brainstorm service (Web of Trust) which can be activated directly in Nostria without leaving. That’s how I want to integrate with third party relay providers as well.

Maybe I’ll make such a service myself eventually, I have prepared Nostria for global regional deployment - though there is not enough monetary support for this at the current state of user adoption.

Algorithm in Nostria is local, it builds metrics based upon your actual interactions (likes, etc) on Nostr and local usage in the app, such as how often you open someone’s profile, etc. It was added long ago and needs refinements, but the foundation is there.

There is a trending feed using the same API as many others are using - and it’s mostly useless. Also added a local trending feed, which looks at all cached events and allows you to sort on likes, replies, etc of the events that is actually relevant to you (cached from your following feed, etc).

I try my best, I share your vision that decentralization is important and we should avoid compromises.

Reply to SondreB…

Agree, and to me Communities is the way out:

Chateau
Apr 9, 2026 18:23

Any Nostr key pair can become a community with no special relay or infrastructure required. A single event declares the community’s relays, blossom servers, and named content sections. Each section normally pairs allowed kinds with a profile list (a tag); Rooms is the exception — see Rooms.

Most event kinds target one or more communities with an h tag (community pubkey). Kind 9 messages in a room use c for the community pubkey instead ([[nostr-room|Room]]).

Community Creation Event (kind:10222)

A community is created when a key pair publishes a [[nostr-community|kind 10222]] event. The pubkey of this key pair becomes the unique identifier for that community. One key pair can only represent one community.

The community’s name, picture, and description are derived from the pubkey’s [[nostr-profile|kind 0]] metadata event.

{
  "id": "<event-id>",
  "pubkey": "<community-pubkey>",
  "created_at": 1675642635,
  "kind": 10222,
  "tags": [
    // at least one main relay for the community + other optional backup relays
    // optional "enforced" = relay only stores events from authors allowed by the section's lists (kind 9 in a room: community in `c`, see [[nostr-room|Room]])
    ["r", "<relay-url>", "enforced"],
    ["r", "<relay-url>"],
    // one or more blossom servers
    ["blossom", "<blossom-url>"],

    // one or more ecash mints
    ["mint", "<mint-url>", "cashu"],

    // future: other tags e.g. ["<service-type>", "<url>"] as needed

    // Interactions section for comments, reactions, and labels (recommended for all communities)
    ["content", "Interactions"],
    ["k", "1111"], // comments
    ["k", "7"],    // reactions
    ["k", "1985"], // labels
    ["a", "30000:<pubkey>:Interactions", "<relay-url>"], // profile list

    // Rooms: room definitions (kind 30223) — no `a` on this row; per-room lists live on each 30223 ([[nostr-room|Room]]). Chat in rooms is still kind 9.
    ["content", "Rooms"],
    ["k", "30223"],

    ["content", "Forum"],
    ["k", "11"],
    ["a", "30000:<pubkey>:Forum", "<relay-url>"], // profile list

    ["content", "Apps"],
    ["k", "32267"], // apps
    ["k", "30063"], // releases
    ["a", "30000:<pubkey>:Apps", "<relay-url>"], // profile list

    // Optional terms of service, points to another event
    ["tos", "<event-id-or-address>", "<relay-url>"],

    // Optional location
    ["location", "<location>"],
    ["g", "<geo-hash>"],

    // Optional language(s) — repeat for multilingual communities
    ["l", "en", "ISO-639-1"]
  ],
  "content": "",
  "sig": "<signature>"
}

Tag definitions

Tag Description
r Relay URLs for community content. First is main relay. Optional second value enforced: relay only stores events whose author is allowed by the relevant profile list — usually the community pubkey appears in h, except kind 9 in a room, where it appears in c ([[nostr-room
blossom Blossom server URLs.
mint Mint URL for token/payment features.
other Communities MAY use additional tags for other server-side services; tag name = service type, first value = URL. Clients SHOULD ignore unknown tags.
content Name of content section (e.g., “Rooms”, “Forum”).
k Event kind allowed in this section.
a Profile list reference: 30000:<pubkey>:<d-tag>. Exception: the Rooms section ( k 30223 ) MUST NOT use a; per-room ACL is only on each [[nostr-room
retention Retention policy: [kind, value, type]. Type is “time” (seconds) or “count”.
tos Reference to posting policy event.
location Community location.
g Geo hash.
l Language tag: ISO-639-1 code (e.g. "en"), mark MUST be "ISO-639-1". Repeat for multilingual communities.

The a tag references a profile list ([[nostr-list|kind 30000]]) of member pubkeys (p tags) for that section — except Rooms, where lists live only on [[nostr-room|kind 30223]]. Lists also carry membership metadata and optional form for intake. See Profile List Metadata below.

The pubkey of the key pair that creates this event serves as the unique identifier for the community. This means:

  1. Each key pair can only represent one community
  2. Communities can be easily discovered by querying for the most recent [[nostr-community|kind 10222]] event for a given pubkey
  3. Community managers can update their settings by publishing a new [[nostr-community|kind 10222]] event

Profile Lists

Each content section references a [[nostr-profile-list|profile list]] via a, except RoomsRooms. The whitelist is the set of p tags on the fetched list.

Access Requests

A profile list MAY include a form tag pointing to a [[nostr-form|Form]] template (30168:<pubkey>:<form-id>) or a plain web URL. This is the community’s public intake channel:

  1. A prospective contributor fetches the form template and submits a form response (kind:1069)
  2. The community admin reviews the response and, if approved, adds the pubkey as a p tag to the profile list
  3. That pubkey can now publish to that section — no separate approval event needed

Communities may choose to automate this flow by managing the list with an on-server hot-key that instantly adds/removes profiles based on responses.

Apps targeting a specific community may bypass the form entirely and provide their own UX; with the form tag as a fallback for interoperabilty.

Multiple Tiers

Different content sections can reference different profile lists, creating natural access tiers — see [[nostr-profile-list|Profile List]].

Rooms

On 10222, Rooms is declared with content + k 30223 (room definitions), no a. Chat in those rooms is still [[nostr-community|kind 9]] with c = community pubkey and h = opaque room id (NIP-29 group id shape — not the display name); see [[nostr-room|Room]]. ACL for posting is only on each 30223. Clients MUST ignore any a on the Rooms block. Enforcing relays MAY bucket kind 9 by #h like NIP-29; scope with #c.

Targeting publications at communities

Most publications target communities via one or more h tags; each value is a community pubkey. Exception — [[nostr-room|Room]] kind 9: c = community pubkey, h = opaque room id ([[nostr-room|Room]]).

  • Forum [[nostr-community|kind 11]] is exclusive to one community: exactly one h = community pubkey.
  • Kind 9 in a room ([[nostr-room|Room]]): exactly one c = community pubkey and one h = room id; see Rooms.
  • Other publications (e.g. kind 32267, articles, etc.) may use multiple h tags to target several communities simultaneously; clients filter by these tags when listing content.
  • Comments, reactions, and zaps carry no h tag of their own — they just reference the h-tagged event they interact with.

Anyone can zap community content regardless of whitelist status. Query zap receipts on the community relays with no author filtering — external appreciation is always welcome.

Community-Exclusive Publications

[[nostr-community|Kind 9]] (in a room) and Forum [[nostr-community|kind 11]] are exclusive to one community. Forum: h = community pubkey. Kind 9 in a room: c = community pubkey, h = opaque room id ([[nostr-room|Room]], Rooms).

{
  "id": "<event-id>",
  "pubkey": "<pubkey>",
  "created_at": 1675642635,
  "kind": 9,
  "tags": [
    ["h", "<opaque-room-id>"],
    ["c", "<community-pubkey>"]
  ],
  "content": "<message>",
  "sig": "<signature>"
}

Forum posts are defined in [[nostr-community|kind 11]].

Requesting from relays

When reading community content, query the community’s relays. For most kinds, filter by #h = community pubkey; for kind 9 in a room, #c = community pubkey and #h = room id ([[nostr-room|Room]]). Relays marked enforced only store events from allowed authors per the relevant lists. Relays without enforced may store any author; clients filter by membership.

Common Content Sections

Communities can define any mix of content sections. Typical bundles are below; Rooms (k 30223 on 10222) is in Rooms.

Activity

Recommended for all communities. Covers all forms of activity on community content: reactions, comments, reposts, labels, and reports.

Kind Description
kind:7 Reactions
kind:1111 Comments
kind:6 Reposts
kind:16 Generic Reposts
kind:1985 Labels
kind:1984 Reports

Reposts (kind:6, kind:16) allow community members to bring any existing publication — including content authored outside the community — into the community’s feed without needing to be the original author.

Labels (kind:1985) can also function as a categorized repost: a member targets a label event at the community’s relay to surface and tag external content under a community-defined category without republishing the original event or requiring authorship.

When a publication targets multiple communities, members from all those communities participate together: filter by the Activity section’s profile list from each targeted community. Members meet in one shared discussion around the publication — no duplicates, no fragmented conversations across multiple places.

Communities that don’t want to be part of discussions with certain other communities can simply not accept those events.

Projects

For project planning and tracking.

Kind Description
kind:30315 Projects
kind:30316 Milestones

Apps

For app and release publishing.

Kind Description
kind:32267 Apps
kind:30063 Releases

Forum

For threaded discussion. Uses [[nostr-community|kind 11]] posts, each targeting the community via a single h tag. Forum posts are exclusive to one community and cannot be targeted at multiple communities simultaneously.

Listing a User’s Communities

Since communities are just pubkeys, existing Nostr primitives (like profile lists) can be used to list which communities a user is part of.

Pinned Content

Communities can also publish a [[nostr-pin|kind 10001]] pin list like any other profile. This gives communities a built-in way to feature content — pinned articles, apps, releases, or any other event — without any additional spec. Clients that display pinned events on profiles will automatically show them for communities too.

Implementation Notes

Communities work on any standard Nostr relay. Access control may be enforced client-side only, or optionally by relays that the community marks with enforced on the r tag.

Client filtering workflow:

  1. Fetch the community’s [[nostr-community|kind 10222]] event to get content sections and which relays (if any) are enforced
  2. Fetch lists: each section’s a tags, except Rooms (no a on 10222 — use 30223, Rooms)
  3. Query: #h = community for most kinds; kind 9 in a room uses #c = community and #h = room id ([[nostr-room|Room]]). With enforced, lists are already applied; otherwise filter authors against the right lists.

Media fallback:

Community blossom servers SHOULD back up all media files referenced in community publications — even when the original URLs point to different servers. By storing files by their content hash, the community server becomes a reliable fallback when external URLs suffer link rot. Clients can try the community’s blossom server when the original media URL fails.

Additional recommendations:

  • Clients MAY cache community metadata and profile lists to reduce relay queries
  • Clients SHOULD check membership before attempting to publish
  • Relays MAY optionally enforce profile list membership (and then list themselves with enforced in the community’s r tag); otherwise clients filter by membership. Relays MAY implement retention policies; this is not required.

Benefits

  1. No special relay required — works on any standard Nostr relay
  2. Easy onboarding — new users don’t need to set up any personal relay or media server to join Nostr via a community. They can use the community’s relay and blossom server immediately.
  3. Any existing npub can become a community
  4. Most existing publications can be targeted at communities (backwards compatible)
  5. Communities are not permanently tied to specific relays
  6. Communities can define their own content types with membership-based access control
  7. Cross-community interaction via multi-community h tags on publications
  8. Users can request access by submitting Form Responses
  9. Simple membership management — just add/remove p tags in the membership list

Reply to Niel Liesmons…