A reproducible look at how many “decentralized” Nostr relays actually share one company’s infrastructure — and what that trade-off really buys an operator.

— @ifuensan / hacknodeslab

TL;DR

  • Nostr’s decentralization is usually counted in relays. The number that matters for censorship resistance is how much infrastructure they share.
  • On a May 2026 nostr.watch snapshot of 1,067 active, clearnet relays, 28.2% sit behind Cloudflare — and Cloudflare is virtually the only CDN in use (CloudFront 0.2%, Fastly 0%). Centralization in Nostr is, almost entirely, a Cloudflare story.
  • That means a single Cloudflare-level event — an outage, a regional block, or a court order against CF IP ranges — hits ~1 in 4 active relays at once, without anyone targeting Nostr.
  • Worse: for some operators the CDN provides no protection at all. Using only free, public data (DNS, certificate transparency, mail records) and no API keys, I recovered and cryptographically validated the real origin IP of 4 of 185 CF-fronted relays that publish a pubkey. With paid recon tooling that number is a floor, not a ceiling.
  • Everything here is reproducible with two small Python scripts and a public relay list. Re-run it on today’s landscape in under thirty minutes.

Why this matters

Nostr is marketed as a censorship-resistant gossip network. The intuition is simple: if there are hundreds of independent relays, no single actor can silence the network. But “independent” is doing a lot of work in that sentence. Two relays with different domains, different operators, and different pubkeys are not independent if they both terminate behind the same Cloudflare edge. A single decision at that edge takes both down simultaneously.

This is not hypothetical. In the ongoing LaLiga case in Spain, commercial courts have ordered ISPs to block Cloudflare IP ranges during football matches — on the order of ~3,000 IPs per matchweek, collaterally knocking out 13,500+ unrelated sites (including, in one widely-reported instance, the public health service Madrid Salud). The order never mentions Nostr. It doesn’t have to: if ~1 in 4 active relays answers on a Cloudflare IP, a Spanish user loses a quarter of the network for the duration of a match, as collateral.

The relay count says the network is decentralized. The infrastructure says a meaningful slice of it has a shared, jurisdiction-exposed single point of failure. This piece quantifies that gap and names the trade-off precisely.

Step 1 — How many relays hide behind a CDN?

Method. Take a relay list, resolve each hostname to its A/AAAA records, and check every IP against the published address ranges of the major CDNs:

  • Cloudflare — https://www.cloudflare.com/ips-v4 + ips-v6
  • CloudFront — https://ip-ranges.amazonaws.com/ip-ranges.json (service=CLOUDFRONT)
  • Fastly — https://api.fastly.com/public-ip-list

No probing, no exploitation — just DNS resolution and a range check. The CDN range snapshots are cached so a third party can reproduce the exact verdicts. The tool is check_cf.py.

Input. A nostr.watch xlsx export (nw-relays-all-20260503.xlsx), filtered to the active relay set (in_rstate=True) and clearnet only — 1,079 unique hosts, 1,067 of which resolved. The unfiltered export is dominated by long-dead relays and inflates the denominator; the active-clearnet subset is the honest population.

Result (May 2026 snapshot):

VerdictCount% of resolved
cloudflare30128.2%
cloudfront20.2%
fastly00%
direct76471.6%
dns_error12

~28% of active relays sit behind a CDN, and that CDN is Cloudflare. Adding CloudFront and Fastly barely moves the needle. So for everything that follows, “behind Cloudflare” and “behind a CDN” are effectively the same question.

Step 2 — Does the CDN actually hide the origin?

Sitting behind Cloudflare is only protective if your real server IP stays secret. If an adversary can find the origin, they can attack or block it directly and the CDN becomes decorative. So the second question is: for a CF-fronted relay, can the origin IP be recovered from public data alone — and proven correct?

Candidate sources (free, no API keys):

  1. Direct DNS — the apex domain and common sibling subdomains (ssh., mail., direct., origin., …). Operators routinely put the relay behind Cloudflare but leave the apex or an admin subdomain pointing straight at the box.
  2. Certificate Transparency (crt.sh) — sibling hostnames on the same certificate chain, then resolved.
  3. SPF / TXT ip4: tokens — mail configuration that names the server’s IP.
  4. MX hosts — the mail server often runs on the same machine as the relay.

DNS lookups for TXT/MX and CT names go through DNS-over-HTTPS (1.1.1.1’s JSON API), so the whole tool stays standard-library-only and reproducible without any account. Any candidate IP that falls inside Cloudflare’s published ranges is discarded — that’s the front, not the origin.

Validation — the part that makes this evidence, not a guess. Every Nostr relay can serve a NIP-11 document, and many include the operator’s pubkey. For each candidate IP, the tool opens a TLS connection to that IP while keeping the SNI/Host set to the relay’s real hostname (the curl --resolve pattern), fetches the NIP-11 document, and compares its pubkey to the one served through the Cloudflare front. If the pubkeys match, the origin is confirmed cryptographically — not inferred from ASN, geography, or a hunch. When a relay doesn’t publish a pubkey, the tool falls back to a structural fingerprint (name + software + sorted supported NIPs + contact) and reports that as a weaker “likely” match. The tool is find_origin.py.

A walked-through case (anonymized: “Relay A”)

Per responsible-disclosure practice, the relay and its origin IP are redacted here. The method is fully reproducible against your own relay.

Relay A is a khatru relay served at relay-a.example and answering on a Cloudflare IP — by the Step-1 measurement, it is “behind a CDN.”

Running find_origin.py relay-a.example:

  1. The tool fetches Relay A’s NIP-11 through the Cloudflare front and records the reference pubkey (a 64-hex key).
  2. It gathers candidate IPs. Relay A’s apex domain and an ssh. subdomain both resolve to a single IP at a budget cloud provider — outside Cloudflare’s ranges. The operator fronted the relay subdomain but left the rest of the zone pointing at the real machine.
  3. The tool connects to that candidate IP with SNI/Host set to relay-a.example, fetches the NIP-11 document, and reads its pubkey.
  4. The pubkey is identical to the one served via Cloudflare. Origin confirmed.

Total elapsed: a few seconds. No exploitation, no port scan — just public DNS and one HTTPS GET. For Relay A, the Cloudflare front protects nothing: the origin is one dig away.

How widespread is it?

Across the 185 CF-fronted relays that publish a live pubkey (the cryptographically-validatable subset of the 301), the free-sources scan confirmed the true origin for 4 relays across 3 operators — 2.2%, via two distinct leak vectors:

  • DNS leak — the apex / a sibling subdomain points straight at the origin (Relay A’s vector).
  • Mail leak — the relay’s MX host resolves to the same box that runs the relay.

2.2% is a floor, and an honest one. It counts only origins provable from free data, only on relays that publish a pubkey, on a single snapshot. Add paid recon (Censys, Shodan, ZoomEye historical DNS) and the recoverable fraction rises. The point is not the exact percentage — it is that for a non-trivial slice of relay operators, the CDN provides the cost and complexity of Cloudflare with none of the origin protection it implies.

The trade-off, named

Putting a relay behind Cloudflare is a real engineering decision with real upsides. It is worth being precise about which adversary it helps against and which it quietly hands more power to.

What Cloudflare protects you against:

  • Volumetric DDoS — absorbed at the edge before it reaches your box.
  • Casual origin exposure — your server IP isn’t in the relay’s A record, so the laziest attacker can’t find it (until DNS/mail/CT leak it — see above).
  • Operational toil — TLS, caching, and a global anycast front for free.

What Cloudflare exposes you to — and this is the part the marketing omits:

  • Jurisdictional blocking you didn’t opt into. A court order against Cloudflare ranges in any jurisdiction takes your relay down there, even though the order was aimed at football piracy. You inherit every fight Cloudflare’s address space is in.
  • Correlated failure. You are not an independent relay anymore; you are one of ~300 sharing a fate. The network’s resilience math is built on an independence that isn’t there.
  • A single moderation/deplatforming chokepoint. One company’s terms-of-service decision can remove a quarter of the active network’s edge in one action.

And for the leaking operators, the worst case: the downside without the upside. Their origin is public, so they get the jurisdictional exposure and the correlated-failure risk of being behind Cloudflare, while an attacker who wants their specific relay gone just targets the origin directly.

This is not an argument that relay operators shouldn’t use Cloudflare. It is an argument that the choice is a trade-off against a specific threat model, not a free win — and that a network whose censorship-resistance story depends on relay count should know that ~1 in 4 of those relays answers at the same company’s door.

Reproduce it yourself

All the tooling is open source — hacknodeslab/nostr-cf-recon:

python3 -m venv .venv
.venv/bin/pip install rich openpyxl

# 1. From a fresh nostr.watch xlsx export, extract active clearnet relays:
.venv/bin/python extract_relays.py nw-relays-all-YYYYMMDD.xlsx relays.txt --online --clearnet

# 2. Measure CDN presence (Step 1):
.venv/bin/python check_cf.py relays.txt --json out.json

# 3. Try to unmask one relay's origin (Step 2):
.venv/bin/python find_origin.py <some-cf-fronted-relay-host>

If a new LaLiga-shaped blocking order lands, re-run Step 1 against the current relay list to quantify the same-day collateral impact on Nostr.

Method limits & ethics

  • Public data only. No exploitation, no auth bypass, no port scanning, no probing beyond standard DNS resolution and an HTTPS GET against public NIP-11 endpoints. The --resolve-style validation connects to a candidate IP that is already published in DNS.
  • Reproducibility is mandatory. Inputs, CDN range snapshots, and per-host verdicts are persisted so a third party can re-run end-to-end.
  • No operator doxxing. The unmasking result is methodological evidence, presented anonymized. Confirmed origin leaks were disclosed privately to the affected operators; this piece names no relay-to-IP mapping.
  • Snapshot, not surveillance. This is a point-in-time measurement of a public relay list, not ongoing monitoring of any operator.

Tooling: check_cf.py (Step 1), find_origin.py (Step 2), extract_relays.py. Inspired by anti-Cloudflare recon technique from CF-Hero; here used only to measure a centralization trade-off, not to attack. Full code: github.com/hacknodeslab/nostr-cf-recon.