I have an ongoing personal experiment and curiosity to optionally provide this blog over IPFS (InterPlanetary File System), a decentralized content network. Some label this as a “Web3”, whereas “Web2” is the interactive internet as you know and love today, and “Web1” is the 90’s-00’s era of internet that is purely for consumption of static web pages. In this post, I attempt to give you an overview of what makes it a decentralized network, if that provides more security or less required trust, and where those boundaries might be.

A Website Is a Stack of Trust

You likely do not realize how many moving parts and trust that is involved between the content that you request from the web and the content that is delivered to you.

The chain of trust when publishing and reading Web2 content.

Above, you have just some of the components involved when publishing content (red) and reading content (green) from Web2. Each one of those components needs to be either trusted or cryptographically verified to be correctly publishing the content you intend, or delivering the content requested. This is, in my opinion, the most crude form of the visual that is useful. If you notice, it is more “onion”-like in its structure where similar components need to be trusted or verified on each end of the network. Moreso, not just in the “ends”, but even those nodes of the network alone has hardware that needs to be equivalently verified or trusted. Really, if you could even manage to make an ideal topological diagram of this structure, it would resemble the fractal-like diagrams of trees of congruence classes in p-adic number systems or an expanse of “Tower of Hanoi” pegs.

In order to point out where in the diagram above IPFS and the decentralized web, or Web3, intends to reduce trust required for publishing and reading shared content, I need to explain a few key differences between Web2 and Web3 and some key components of Web3.

How a Browser Finds This Page

The anatomy of a URL.

In order to get content from a network, we need a strategy for finding it. In the URL anatomy diagram above for this particular Web2 page, that URL roughly gives the browser three instructions: use HTTPS, find the network endpoint for treymanuszak.com, and request /blog/decentralized-blog. So, the first step for a reader making that request is for the browser to figure out which network endpoint currently answers for treymanuszak.com, unless a usable answer is already cached.

If no cached answer is available, the browser or operating system will ask its configured recursive resolver. This is usually provided by the reader’s ISP (Internet Service Provider), but one can instead use Cloudflare’s resolver at IP address 1.1.1.1, Google’s resolver at IP address 8.8.8.8, or even a locally managed service like Unbound. Below is a simplified overview of how the recursive resolver finds an endpoint for treymanuszak.com and how the browser then requests this page from it.

DNS resolution comes first to find the server which can serve the content.

The recursive resolver will first ask a DNS root server which nameservers are authoritative for the .com top-level domain. It then asks one of those .com nameservers which nameservers are authoritative for treymanuszak.com. The response delegates the domain to Cloudflare’s authoritative DNS service, which the resolver asks for the relevant DNS record. The response may contain an A record with an IPv4 address, an AAAA record with an IPv6 address, or a CNAME pointing to another hostname. If it receives a CNAME, the resolver follows that alias until it reaches an address record.

Once the recursive resolver finds that address, it can finally return it to the requesting reader’s browser. The browser will then attempt to establish an HTTPS connection. Here, the https scheme tells the browser to use HTTP over an authenticated, encrypted connection. Other schemes exist, but each tells a client how to interpret and act on the rest of the URL. Examples of other schemes browsers or other types of clients and services may use are: http, ws, ftp, ssh, mailto, mysql, ipfs ;-), and many many more!

Once the connection is established, the browser can finally send an HTTP request for /blog/decentralized-blog. The server receiving that request is running a web server, which is a deliberately broad term. It might return a prebuilt static file, as this Astro blog does, that exists in the servers local filesystem at a particular location. This is very much Web1 behavior. Or it may generate a response dynamically for the reader, which is Web2 behavior.

Addressing

Notice above, we discussed in common Web2 (and Web1) practice, to find content you must first find the location of the server in the network that can serve the content. Is it possible to not care where content comes from? If that is even possible, how can we even trust that the content we ask for is correct, if provided from an untrustworthy server? That is what a decentralized network aims to solve. There are actually many decentralized networks, but we will only focus on the IPFS network technicalities here.

The anatomy of this page's CID in the Web3 build.

To address content in IPFS, each block is given a “content identifier”, or CID. A CID includes a cryptographic hash of that block and information about how to interpret it. As the diagram shows above, a CIDv1 contains more than just the hash: it also records the version, block format (codec), hash function, and hash length. In its text form, the initial b selects base32.

With the same content and import settings, the CIDs are deterministic. An IPFS client can independently verify that the blocks it receives match the CID requested, even if the peer providing them is untrusted. Changing the content changes its CID; different import settings can change it too. Therefore, IPFS is able to trustlessly identify and address content by what it is, rather than where it is.

This build's Web3 page can be addressed through the site root and path, or directly by its own CID.

When I add the generated site directory to IPFS, its files and directories are represented by linked blocks, and the site’s root directory gets a root CID. Below you see a breakdown of selected files from this Web3 build. With this build’s CIDv1, base32, and SHA-256 settings, bafybei… identifies dag-pb blocks containing UnixFS metadata and links. Here, those describe directories (shown in blue) or the root of a larger file (shown in red). The bafkrei… CIDs identify raw blocks: file bytes without a UnixFS wrapper (shown below the red file root block). This page fits in one raw block; the larger North Korea trip report has a UnixFS file root linking to multiple raw data blocks.

A view of the generated blog and its actual IPFS blocks. CID labels are abbreviated.

Homework Problem

The diagrams above show the actual CIDs of this page and the site containing it. But if a page displays its own hash, shouldn’t adding that hash change the bytes being hashed? How can I make these seemingly self-referential diagrams accurate without solving a SHA-256 preimage problem or finding a collision?

Work out which bytes are hashed and which resources your browser loads to display the diagrams.

Availability

A CID identifies content and lets IPFS verify the blocks it receives, but it doesn’t ensure those blocks are available. Nodes in the IPFS network may cache blocks they fetch, then remove unpinned blocks through garbage collection to free storage. A pin protects data from garbage collection on the node holding it. A direct pin protects one block and a recursive pin pins a root block protects its linked blocks below.

A pin helps retain a copy, while availability also depends on a node being reachable to serve the blocks a request needs. Pinning the site on multiple nodes, such as your own node and a remote pinning service, adds redundancy. Copies with separate operators reduce dependence on one service, though no pin guarantees permanent availability.

Delivery: Peers, Gateways, and CDNs

The story of how IPFS content gets delivered to readers is a story of peers, gateways, and possibly CDNs.

A peer is a process that participates in the IPFS network and can store IPFS content locally. If it does not have a requested piece of content, it can try to find it in the IPFS network by a combination of the following approaches:

A gateway is an HTTP service that can provide IPFS content in response to HTTP(S) requests. This is how your browser can interact with IPFS, if it does not have an IPFS client it can interact with locally, which could otherwise be done by a browser extension for example. There are many gateways one can use to retrieve content. You can view this dashboard, which shows public gateways and their response times to requests. One can address this blog post’s page content from a specific gateway using one of the methods pictured below.

Four gateway URL forms for the same HTML file, using this build's actual CIDs. gateway.example is a placeholder gateway domain.

Content delivery networks (CDNs) are commonly used in the Web2 world, where CDN nodes sit at the edge of the internet, meaning distributed geographically close to internet users, and cache and serve responses to common requests. This allows publishers to speed up the delivery of the most commonly requested pages and assets of their website, enhancing the end-user experience. IPFS content can be slow to retrieve, particularly if the peer you interact with has to discover a provider and fetch blocks it does not already have. An IPFS publisher can put a CDN in front of HTTP requests for IPFS content to provide fast delivery for cached responses. Note, if the CDN does not have the requested IPFS content cached (a cache miss), then it must obtain the content from its configured gateway.

I utilize the Cloudflare CDN for requests to https://decentralized.treymanuszak.com. Cache misses to that service get handled by eth.limo, which is a special gateway I will discuss further below.

The Mutability Problem: IPNS

An issue as an IPFS publisher, somewhat hinted on previously, is that if I make updates to my blog content on IPFS, that will change the CIDs of the affected blocks and their parent blocks, including the site’s root CID. Unchanged blocks can keep their CIDs. How can I effectively try to have my readers reading the most updated version of my blog? How do they even know what IPFS blocks I publish anyway?

That is where InterPlanetary Name System comes in. I can manage an IPFS client. On that client, I can hold a public/private key pair, with which I can draft and sign a record that points to a specific IPFS CID that says “I declare this content to be the root of my site” and distribute it to the IPNS DHT. Visually, it is exactly as shown below:

The anatomy of my IPNS name.

In the diagram above, I show the anatomy of my IPNS name. First, notice how the IPNS is itself a CID! Inside, it says the libp2p-key codec identifies a public key, and the identity multihash embeds my serialized Ed25519 public key directly. That serialized key includes its type and length in a small Protocol Buffers (protobuf) message. The leading k indicates that the name is encoded in base36.

This name stays the same when I update my blog. I can publish a new signed record under that name pointing to a different site root, and readers can use the embedded public key to verify the signature. The record below shows the target IPFS path, a sequence number that increases when I change the target, and the signature.

Selected fields from my signed IPNS record, captured on 20 October 2026. The signature covers the full record data.

So, you can also address this blog page by using the ipns scheme or gateway method as follows:

Three address forms for the same IPNS path. gateway.example is a placeholder gateway domain.

Human-Readable Names: ENS

Now, it’s not exactly fun handing someone the URL to my blog as https://k51qzi5uqu5dilbvdz3xwib4udboc8jk9hi7i1mtzr09x7bo6v646sfpsif8uz.ipns.gateway.example. First of all, I couldn’t remember that address if I wanted to verbally communicate directions. Second, it looks sketchy to click on as a reader.

Luckily, the Ethereum Name Service (ENS) is a solution to addressing arbitrary content with a human-readable name in a secure and decentralized manner on the Ethereum blockchain. I registered treymanuszak.eth through ENS’s smart contracts, which represent ownership of the name as a non-fungible token (NFT). Because I control treymanuszak.eth, I can set its resolver’s contenthash record to my IPNS name. This doesn’t have to just be a record of my IPNS. Since ENS is arbitrary addressing, I can point people to my Ethereum address or Bitcoin address, if they want to send me money. I can point people to my social handles. My ENS name can identify me in decentralized applications (DApps) where I sign in with my Ethereum wallet, a topic deserving of more detail another time. NFTs do have real use cases! They aren’t just for digital art!

Why would I want to do this? These addressing pointers are stored on and secured by the Ethereum blockchain, which is a decentralized computer. I am going to resist the urge to go into much more depth on that statement for brevity of this already long article. From a high-level, Ethereum secures my name’s ownership and record updates, allowing readers to inspect and verify the relevant addressing pointers themselves.

Trying to stay just on the topic of ENS, here are the five entities involved in managing the name and addressing:

The ENS diagrams below use my publishing IPNS name as an example, assuming I have updated my ENS contenthash to point to it.

A sequence diagram for resolving a request for an ENS name to a website pointer.

Homework Problem

How does the client turn the resolver’s contenthash bytes shown above into the readable website pointer it returns to the reader?

Using the contenthash format, identify the protocol prefix and explain how the remaining bytes become the address. Show the decoding steps, including the final text encoding.

Notice how the client uses an Ethereum RPC node to read the resolver’s website pointer. Given that pointer, the client still has to resolve IPNS if necessary and fetch the content from IPFS or a gateway.

Now, how can we connect the common web’s DNS with ENS and IPFS? That is what gateways such as eth.link and eth.limo provide! A request for treymanuszak.eth.limo/blog/decentralized-blog/ reaches the gateway through ordinary DNS and HTTPS. The gateway reads the contenthash record for treymanuszak.eth, which gives it my IPNS name. Combining that name with the requested page path gives ipns://k51qzi5uqu5dilbvdz3xwib4udboc8jk9hi7i1mtzr09x7bo6v646sfpsif8uz/blog/decentralized-blog. It resolves that name to an IPFS root, retrieves the requested page, and returns the content over HTTPS. This lets readers access my blog’s IPFS content in an ordinary browser.

There was some drama in the history of the gateway though, that questioned the reliability of this solution. In 2022, Virgil Griffith (an early Ethereum Foundation developer), was the registrant of the eth.link domain and was incarcerated at the time for 5 years for violating US sanctions against North Korea by presenting at the Pyongyang Blockchain and Cryptocurrency Conference in April 2019. By the way, check out my North Korea 2016 trip report if you haven’t yet! Anyway, he was not able to renew the registration and it expired. GoDaddy auctioned off the name, which caused the whole *.eth.link gateway and ecosystem to not work.

The eth.limo project had already launched in August 2021 as a public good alternative to eth.link. Its creators say reliability concerns helped motivate the project, which has received funding from the ENS DAO and other organizations. In September 2024, the eth.limo team also took over operating the eth.link gateway service. ENS Labs controls the eth.link domain, while eth.limo operates the gateway behind both hostnames. Readers therefore have two addresses for accessing the content, with shared gateway infrastructure.

I briefly mentioned previously that I have Cloudflare’s CDN in front of treymanuszak.eth.limo, using decentralized.treymanuszak.com as the reader-facing hostname. Cloudflare can serve cached responses directly; on a cache miss, it forwards the request to the gateway. This gives my readers a familiar address under a domain I control, while allowing the content to come from IPFS, and it provides a faster Web 3.0 browsing experience. I arbitrarily chose the LIMO gateway as the fallback.

So, all in all, you get the full sequence diagram of your request you make below:

The full request sequence in the event of all missed caches to `decentralized.treymanuszak.com/blog/decentralized-blog`.

Open the full-size sequence to inspect the diagram more clearly.

An Honest Layer-by-Layer Verdict

Now, to finally answer the big question: “How decentralized is my blog?”.

Well, it depends on how the reader chooses to read my blog. The following address methods can return this blog page, roughly ranked from relying on more intermediaries to relying on fewer. The exact tradeoffs depend on the software and services the reader uses:

Even following the most decentralized methods, you still aren’t protected from everything. You are still at the whims of the lower-level OSI components in my first diagram. My publishing key or your device could be compromised, or the content could simply be unavailable, to name a few. So, all in all, it is choose what method is most tolerable and trustworthy to you. I imagine everyone will carry on with the centralized viewing method, so why do I choose to even host on Web 3.0?

I don’t know. ¯\_(ツ)_/¯