Pinata vs self-hosted IPFS: choosing your Web3 storage
We've all been there. You ship a contract, the metadata URI checks out in every staging environment you have, and then production traffic starts to roll in — and renders nothing. The CIDs are valid. The gateway responds with a 200.

But the bytes behind that hash aren't there, because nobody is pinning them anymore. That gap between "it works on my machine" and "it works for our users" is where the Pinata vs self-hosted IPFS decision actually starts to matter.
We don't pick storage backends for fun. We pick them because the user journey on top of our dApp depends on whether a metadata blob, a profile picture, or a chain of attestations will be there when somebody clicks. Before we sign up for another SaaS line item or commit to running yet another daemon on a box under someone's desk, let's walk through what each option genuinely demands from us.
The core distinction: who keeps the bytes alive
The first thing we have to internalize is that Pinata and a self-hosted Kubo node are not the same kind of tool dressed up differently. They're answering different questions.
Pinata is a managed pinning service. When we upload through Pinata's API, the file lands on their infrastructure — they keep it pinned across their own fleet and serve it through dedicated gateways backed by a CDN. Under the hood, Pinata runs Elastic IPFS, their cloud-native layer that scales the underlying node fleet to absorb millions of uploads without us having to think about it. We hand over content, we get back a CID and something close to an SLA that the bytes will be reachable.
A self-hosted IPFS node — typically Kubo, the Go implementation that used to be called go-ipfs — is something we run. On our laptop. On a VPS. On a beefy box in a closet somewhere. When we add a file and pin it, our node is responsible for staying online and announcing the content to the rest of the network so peers know where to fetch it from.
The trade-off isn't really "centralized vs decentralized" — it's "who owns the pager when the file disappears at 3 a.m."
A common misconception we run into: people say "Pinata is centralized, so it's bad." That framing isn't quite right. Pinata is a pinning service. It hosts files on its own infrastructure so they stay reachable. It is not, by itself, a fully decentralized storage network. And equally, a self-hosted node doesn't make a file permanent — it just means we are the one who has to keep it pinned.
Resource requirements: what Kubo actually costs to run
Let's talk numbers, because this is where "we'll just run our own node" either becomes exciting or a sad Slack thread very quickly.
A base Kubo installation — the daemon, the swarm, the MFS plumbing, none of your actual data yet — wants around 6 GiB of RAM and 2 CPU cores to run comfortably. The install footprint itself is roughly 12 MB on disk. That's the floor. Realistically, once we start pinning anything substantive, the disk number moves a lot faster than the RAM number, and the RAM starts to creep upward once we have thousands of pinned items and a non-trivial DHT.
| Resource | Kubo (self-hosted) | Pinata (managed) |
|---|---|---|
| Minimum RAM | ~6 GiB | N/A — handled by Pinata |
| CPU baseline | 2 cores | N/A — handled by Pinata |
| Disk for base install | ~12 MB | N/A |
| Disk for content | Grows with what we pin | Allocated per plan |
| Uptime responsibility | Ours, 24/7 | Pinata's |
| Patching and upgrades | Ours | Pinata's |
The "N/A" in the Pinata column is doing a lot of work. We aren't off the hook — we're paying Pinata to take the pager. But the operational overhead of provisioning, patching, monitoring, and babysitting a daemon is exactly what we're buying back.
A typical small team running a handful of dApps might pin somewhere between a few hundred megabytes and a few gigabytes of metadata: NFT images, profile pictures, protocol docs, attestations. None of that breaks a well-provisioned VPS. The complexity arrives when we want redundancy, when we want the same CID pinned across multiple geographic regions, or when we want a gateway to hold up under load testing. That's where the next section bites.
Scaling: Elastic IPFS vs. a fixed cluster
This is where the rubber meets the road, and where most of the "we'll just run our own IPFS" optimism meets its match.
When we self-host for any kind of production workload, we rarely stop at one Kubo node. We run a small cluster — a few nodes across regions — so that if one box dies, the others keep the content pinned and serveable. The standard tool here is IPFS Cluster, which orchestrates pinning across a fixed set of nodes we control. It's solid. It's also a fixed set of nodes we control, which means scaling it is a manual, capital-expenditure kind of decision: provision, deploy, monitor, repeat.
Pinata approaches this differently. They run Elastic IPFS — a cloud-native layer that abstracts the underlying node fleet and lets the service scale horizontally under load. The same API call that uploads a single JSON blob in dev works at the scale of millions of uploads without us provisioning anything new. Their dedicated gateways sit behind a CDN, which means reads are served from edge nodes close to our users rather than from a single daemon in a single region.
To be fair about what we don't know: Pinata doesn't publish the exact bandwidth capacity of their gateways or the size of their global fleet, so we can't benchmark them apples-to-apples against our own cluster. And we don't have great public data on the performance overhead of a self-hosted Kubo node under high concurrent load — it's the kind of thing we only learn when our own gateway falls over at 2 a.m. on a Friday. That's worth keeping in mind when we read benchmarked comparisons: most of them test the happy path.
Hard limits: what each option will and won't let us do
Now the part where the marketing copy gets replaced by actual numbers.
Pinata's free tier gives us 1 GB of storage, 500 pinned files, 1 dedicated gateway, 10 GB of monthly egress, and 10,000 API requests per month. For a hobby project, a hackathon, or a prototype that won't see real volume, that's genuinely useful. The moment we're serving a live dApp with non-trivial reads, we hit the request ceiling faster than we expect — gateways are chatty, and a single page load that pulls a handful of CIDs adds up.
Their entry-level paid tier — Picnic, at $20 per month — lifts most of those ceilings substantially: 1 TB of storage, 500 GB of bandwidth, 1 million API requests, 5 million files, and one gateway backed by a CDN. That's a workable level for a small production dApp or a startup still measuring usage before committing to enterprise contracts.
Then the file-size limits, which catch teams off guard more often than the storage numbers. Pinata caps individual file or folder uploads at 25 GB, and recommends staying under 15 GB for reliability. Their pinJSONToIPFS endpoint — the one we use to pin small metadata blobs — tops out at 10 MB per call. If we're uploading long-form video, large 3D assets, or anything in the multi-hundred-megabyte range, we have to plan around that.
Self-hosted Kubo doesn't have these specific limits in the same way — it's bounded by our disk and our network, not by an API quota. But that freedom comes with the implicit constraint that we are the API. Every upload, every pin, every gateway request flows through infrastructure we maintain.
| Constraint | Pinata (free) | Pinata (Picnic, $20/mo) | Self-hosted Kubo |
|---|---|---|---|
| Storage | 1 GB | 1 TB | Whatever our disk is |
| Pinned files | 500 | 5,000,000 | Whatever we can store |
| Monthly bandwidth | 10 GB | 500 GB | Our network's problem |
| API requests / month | 10,000 | 1,000,000 | N/A — no API quota |
| Max single upload | 25 GB (15 GB recommended) | 25 GB (15 GB recommended) | Limited by disk + RAM |
| pinJSONToIPFS limit | 10 MB | 10 MB | N/A — no such endpoint |
| Cost | Free | $20/month | Hardware + ops time |
A practical note from our own deployments: the pinJSONToIPFS ceiling is one of the most common things teams don't notice until they've already architected around it. If our metadata blobs are under 10 MB — and for almost every dApp they are — we won't notice. If we ever need to pin large attestation bundles, lengthy credential documents, or anything unusually chunky, this is the line where we'll need a different approach.
Availability: the 24/7 problem nobody warns you about
This is the section we'd most like to have written for us before our first production incident.
A self-hosted IPFS node is only useful while it's online. Content stays reachable across the network as long as at least one peer is pinning it. If our node goes offline — patch reboot, host migration, that one cloud bill we forgot about — and it's the only peer holding a given CID, the file effectively becomes invisible to anyone who isn't already caching it. The hash is still valid. The bytes are still sitting on the disk. But the network has no path to them, and gateways will start returning empty. That's the worst kind of graceful degradation: nothing throws, nothing alerts, the state just silently desyncs.
This is where IPNS — InterPlanetary Name System — becomes relevant, especially for content we update periodically. IPNS lets us publish a mutable pointer to an immutable CID, with a Time to Live that we configure. The community-recommended TTL values range from around 12 hours for content we update frequently, up through 24 hours and 7 days for more stable records, and as long as 30 days for content that rarely changes. A short TTL means our records refresh often — useful for active content — but it also means our publishing node needs to be online to refresh them. A long TTL is more forgiving, but it slows down how quickly updates propagate to readers.
Pinata sidesteps this whole category of risk by keeping the pinning layer online for us. We don't have to think about TTLs as a survival mechanism for our own availability — they become a UX choice about how fresh our pointers feel, rather than a heartbeat we have to keep alive.
The honest framing: with self-hosted IPFS, uptime isn't a feature — it's the entire product.
We've seen teams try to engineer around this with redundancy — two nodes in two regions, automatic failover, monitoring that pages us when a daemon stops announcing. All of that works. All of that is also real engineering work, real monitoring budget, and a real on-call rotation. For some teams, that's worth it. For most dApp teams shipping product, it's not the highest-leverage place to spend our time.
So how do we actually choose?
Here's the part where we stop cataloging trade-offs and commit to a position.
If we're a small team shipping a dApp, with metadata volumes in the gigabyte range, traffic patterns we can predict, and no appetite for being woken up because a daemon crashed on a VPS we're renting from a provider whose status page we forgot to bookmark — Pinata is the pragmatic answer. The free tier covers prototyping. The $20 Picnic tier covers a real production launch. The Elastic IPFS layer handles the scaling we don't want to engineer. The dedicated gateway with CDN handles the user-experience side of reads.
If we're operating at a scale where Pinata's pricing curves start to hurt, where we have compliance or sovereignty reasons to keep our own infrastructure, or where we genuinely need to own every part of the stack — including the pager — then self-hosted Kubo with IPFS Cluster becomes the right call. We get full control over pinning policy, gateway configuration, IPNS TTL behavior, and where the bytes physically live. We also accept that we now run an infrastructure team, or at least an infrastructure rotation, on top of being a dApp team.
There's also a middle path that works for more teams than we think: use Pinata for the latency-sensitive, user-facing reads where CDN delivery matters, and self-host a small Kubo node for the long-tail content we want to keep reachable but that doesn't justify the per-request economics. It's not pure, but pure is overrated when the user journey is what we're actually optimizing for.
What we'd encourage everyone to skip is the "we must run our own everything because Web3" instinct. Decentralization is a spectrum, and the goal is a dApp that works for the people using it. The storage layer is a means to that end, not the end itself. Pick the option that lets us ship, that keeps the bytes reachable, and that doesn't put us on a path where one missed reboot takes down our users' experience. Everything else is a debate we can have after we've shipped.