Decentralized hosting replaces a single origin server with content-addressed storage and a peer-to-peer retrieval network, so any node holding the data can serve it. This article explains the retrieval path from content identifier to browser, gives runnable procedures for pinning, gateway verification, and peer diagnostics, and defines the measurements a team should collect before migrating a site. It also covers the failure modes that centralized hosting hides, such as unpinned content, gateway inconsistency, and mutable-name resolution. The goal is a reproducible evaluation method rather than a provider recommendation.
Scope and the mental model for decentralized hosting
Decentralized hosting is a delivery model in which a website's files are stored and served by a network of independent nodes rather than a single origin server. The two defining mechanisms are content addressing, where a file is identified by a cryptographic hash of its bytes, and peer-to-peer retrieval, where a client asks multiple nodes for the blocks behind that hash. Decentralized web hosting and decentralized website hosting usually add a naming layer on top, because humans need stable URLs while content hashes change whenever a file changes.
The practical consequence is that hosting becomes two separate problems: persistence (keeping at least one node willing to store the content) and resolution (mapping a human-readable name to the current content identifier). A site can be perfectly retrievable today and disappear tomorrow if no node is pinning it, which is the single most common operational surprise. This article treats those two problems separately and gives verification steps for each.
- Content layer: files are chunked, hashed, and addressed by content identifier (CID).
- Persistence layer: pinning services or self-run nodes keep blocks available.
- Resolution layer: mutable names (DNSLink, on-chain registries, or naming systems) point to the current CID.
- Retrieval layer: gateways or local nodes fetch blocks from peers and reassemble the file.
Assumptions and prerequisites before you migrate a site
The procedures below assume you have a static site build output (HTML, CSS, JavaScript, images) and a machine with curl, Node.js, and Python 3 available. They also assume you can run a local IPFS-compatible node or connect to a pinning service; the commands use placeholders such as RPC_URL and PINNING_API_URL rather than invented public endpoints. If your site depends on server-side rendering, authenticated APIs, or a relational database, decentralized hosting covers only the static delivery tier and you will still need conventional compute behind it.
Before migrating, inventory every asset by type and size, note which files change on each deploy, and decide whether the site needs a mutable name or can tolerate a new CID per release. Teams that skip this step usually discover that their build emits hashed filenames on every deploy, which invalidates caching assumptions and makes immutable addressing less useful than expected.
- A static build directory you can hash and upload.
- A pinning service API key or a self-hosted node with persistent storage.
- A gateway URL you control or trust for verification.
- A naming decision: immutable CID per release, or a mutable record updated on deploy.
The retrieval path from content identifier to browser
When a browser requests content through a gateway, the gateway parses the CID, checks its local block store, and if the block is missing, queries peers via a distributed hash table and direct provider records. The gateway then verifies each block against the hash embedded in the CID before returning bytes to the client. This verification is the security property that centralized hosting lacks by default: a malicious peer cannot substitute different content without breaking the hash.
The path has more hops than a CDN, so latency depends on peer availability and gateway caching. A gateway that has already cached the CID responds quickly; a cold CID may take seconds while peers are discovered. For decentralized website hosting, this means the first visitor after a deploy often pays the discovery cost, and subsequent visitors benefit from gateway caches. Measuring both cold and warm retrieval is essential before drawing conclusions about performance.
Pinning and publishing a static build with runnable commands
Pinning is the act of telling a node to retain specific content indefinitely. The following shell procedure publishes a build directory, captures the resulting CID, and pins it through a pinning service API. Replace the placeholders with your own values; do not paste production keys into shared shells.
set -euo pipefail
BUILD_DIR="./dist"
PINNING_API_URL="https://your-pinning-service.example/api/v0"
PINNING_TOKEN="$PINNING_TOKEN"
# Add the directory recursively and capture the root CID
ADD_RESPONSE=$(curl -s -X POST "$PINNING_API_URL/add" \
-H "Authorization: Bearer $PINNING_TOKEN" \
-F "file=@${BUILD_DIR}" \
-F "recursive=true")
ROOT_CID=$(printf '%s' "$ADD_RESPONSE" | python3 -c 'import sys,json; print(json.load(sys.stdin)["Hash"])')
echo "Published root CID: $ROOT_CID"
# Pin the root explicitly so it survives garbage collection
curl -s -X POST "$PINNING_API_URL/pin/add?arg=${ROOT_CID}" \
-H "Authorization: Bearer $PINNING_TOKEN"
echo "$ROOT_CID" > .last-published-cidVerifying gateway consistency and content integrity
A single successful fetch proves only that one gateway had the content at that moment. Integrity verification requires fetching the same CID from at least two independent gateways and comparing the bytes. The script below retrieves a file by CID from two gateways, computes SHA-256 of each response, and reports whether they match. It also checks the HTTP status so that a gateway returning an error page is not mistaken for a content mismatch.
import hashlib, os, sys, urllib.request
CID = os.environ.get("CID", "")
PATH = os.environ.get("PATH_IN_SITE", "index.html")
GATEWAYS = [
os.environ.get("GATEWAY_A", ""),
os.environ.get("GATEWAY_B", ""),
]
if not CID or not all(GATEWAYS):
sys.exit("Set CID, GATEWAY_A, and GATEWAY_B")
def fetch(base):
url = f"{base.rstrip('/')}/ipfs/{CID}/{PATH}"
req = urllib.request.Request(url, headers={"User-Agent": "hosting-verify/1.0"})
with urllib.request.urlopen(req, timeout=30) as resp:
body = resp.read()
return resp.status, hashlib.sha256(body).hexdigest(), len(body)
results = []
for gw in GATEWAYS:
try:
status, digest, size = fetch(gw)
results.append((gw, status, digest, size))
print(f"{gw} status={status} sha256={digest} bytes={size}")
except Exception as exc:
print(f"{gw} ERROR {exc}")
if len(results) == 2 and results[0][2] == results[1][2]:
print("MATCH: gateways returned identical bytes")
else:
print("MISMATCH or incomplete: investigate before trusting the deployment")Results table procedure for a hosting evaluation
Record every verification run in a table so that cold and warm behavior are distinguishable. Do not fill in numbers from memory; run the procedure and paste actual observations. A defensible evaluation table has these columns and at least five rows per gateway pair, taken at different times of day.
| Run | CID | Gateway | Cold/Warm | HTTP status | SHA-256 match | Time to first byte (ms) | Total bytes | Notes |
|---|---|---|---|---|---|---|---|---|
| 1 | <CID> | <GATEWAY_A> | Cold | <status> | <yes/no> | <measured> | <measured> | first fetch after pin |
| 2 | <CID> | <GATEWAY_B> | Cold | <status> | <yes/no> | <measured> | <measured> | independent gateway |
| 3 | <CID> | <GATEWAY_A> | Warm | <status> | <yes/no> | <measured> | <measured> | repeat fetch |
| 4 | <CID> | <GATEWAY_B> | Warm | <status> | <yes/no> | <measured> | <measured> | repeat fetch |
| 5 | <CID> | <GATEWAY_A> | Warm | <status> | <yes/no> | <measured> | <measured> | after 24h |
The table is only meaningful if the CID column is identical across rows for the same release. If your deploy pipeline produces a new CID on every build, label each release separately and compare within a release, not across releases.
Diagnosing unpinned content and peer discovery failures
The most frequent decentralized hosting incident is content that was added but never pinned, so it becomes unavailable after the adding node garbage-collects its block store. The second most frequent is a gateway that cannot find peers for a CID because the content was only ever stored on a node behind NAT with no provider records. Both present identically to a user: a timeout or a 504 from the gateway.
Diagnose in this order: confirm the CID is pinned on at least one always-on node, query the pinning service for the pin status, then attempt retrieval through a local node rather than a gateway. If the local node retrieves the content but the gateway does not, the problem is gateway-side caching or peering, not persistence. If neither retrieves it, the content is effectively lost and must be re-added and re-pinned.
- Check pin status through the pinning API before assuming a network fault.
- Retrieve through a local node to separate persistence from gateway behavior.
- Confirm the CID in the URL matches the CID you pinned; a single character difference changes the hash.
- For mutable names, confirm the name record points to the intended CID and has not expired.
Mutable names, caching, and deploy semantics
Immutable CIDs are excellent for integrity but awkward for sites that change. Mutable naming layers solve this by storing a pointer that is updated on each deploy. The tradeoff is that the pointer introduces a resolution step with its own caching and propagation delay, and clients may cache the old pointer for longer than expected. When you update a name record, verify resolution from a fresh client and from a client that previously resolved the old value.
Caching also interacts with content addressing in a way that surprises teams: because CIDs are immutable, gateways can cache them aggressively, which is good for repeat visitors but means a rollback to a previous CID may serve stale gateway caches. Plan deploys so that rollback targets a known-good CID and verify it through the same gateway-consistency procedure described above.
Limitations and tradeoffs versus centralized hosting
Decentralized hosting is not a drop-in replacement for every workload. Dynamic server logic, authenticated sessions, and database-backed pages still require conventional compute, and the decentralized tier typically sits in front as a static delivery layer. Performance is also less predictable than a well-tuned CDN because retrieval depends on peer availability and gateway caching, so latency-sensitive applications should measure cold-start behavior rather than assume warm-cache numbers.
Operationally, the model shifts responsibility: you must monitor pin status, name resolution, and gateway consistency yourself, because there is no single provider dashboard that covers all three. The benefit is censorship resistance and integrity verification by construction; the cost is that persistence is a policy you enforce, not a property you buy. Teams should decide explicitly which of those they need before migrating.
- Persistence is only as reliable as your pinning policy and monitoring.
- Cold retrieval latency can be significantly higher than warm retrieval.
- Mutable naming adds propagation delay and cache complexity.
- Server-side logic still needs conventional infrastructure.
Next steps for a production hosting evaluation
Start with a single static site and run the pinning, gateway-consistency, and results-table procedures for one week before committing. Instrument pin status and name resolution as monitored checks, not one-off manual steps. If your application also depends on reading chain state or serving API responses, pair the static hosting tier with reliable RPC access; the OnFinality API service and dedicated node options cover the compute side, while the network directory helps you confirm which chains you need. For endpoint selection specifically, the RPC Assistant guidance on blockchain node hosting and multi-chain RPC endpoints complements this hosting methodology without replacing it.
Finally, document your rollback path: the last known-good CID, the name record update procedure, and the verification command your on-call engineer should run. A decentralized hosting deployment is only production-ready when a new engineer can reproduce the verification steps from that document alone.