Check if you can find your content on IPFS
A debugging tool that verifies whether IPFS peers can retrieve your data.
go install github.com/ipfs/ipfs-check@latest builds and installs the server binary into your Go binary directory (typically ~/go/bin).
The GitHub container registry publishes Docker images automatically:
- Releases
latesttracks the most recent stable releasevN.N.Npins a specific release tag
- Unreleased developer builds
main-latesttracks theHEADof themainbranchmain-YYYY-DD-MM-GITSHApins a specific commit onmain
⚠️ Experimental, unstable buildsstaging-latesttracks theHEADof thestagingbranchstaging-YYYY-DD-MM-GITSHApins a specific commit onstaging- Developers use these for internal testing; end users should not rely on them
Pass configuration via -e:
$ docker pull ghcr.io/ipfs/ipfs-check:main-latest
$ docker run --rm -it --net=host -e IPFS_CHECK_ACCELERATED_DHT=true ghcr.io/ipfs/ipfs-check:main-latestRun ./ipfs-check --help to list the available variables.
go build produces the ./ipfs-check binary in the current directory.
The Go binary embeds everything under ./web and serves it directly. Deployment needs no build step: the pre-built CSS and static files live in the repository.
The main branch deploys automatically to https://check.ipfs.network.
To modify the web interface styles:
- Edit
web/input.css. - Run
npm ciandnpm run buildinside thewebdirectory (seeweb/README.md). - Commit the updated
web/output.css.
Important
Preserve backward compatibility of the HTTP API and frontend. A new ./web must keep working against older backend versions.
Production deployments should terminate HTTPS at a reverse proxy in front of the Go server.
The public backend at https://ipfs-check-backend.ipfs.io is shared and rate-limited, so it can be slow or busy. Run your own instance for faster, private checks.
A single ipfs-check process serves both the HTTP API (/check) and the web UI (/web/). Start it (see Install or Docker), then either open its own /web/ page or open https://check.ipfs.network and set Backend URL under Backend Config to your instance (for example https://ipfs-check.example.com).
The /check endpoint sends permissive CORS headers so any web frontend can call it from a different origin, and it answers OPTIONS preflight requests:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: *
Behind a reverse proxy, preserve these headers; do not strip or override Access-Control-Allow-Origin.
The server does not send X-Frame-Options, so the web UI can be embedded in an iframe (IPFS WebUI's Diagnostics page does this). Behind a reverse proxy, keep it embeddable by not adding X-Frame-Options or a restrictive Content-Security-Policy: frame-ancestors directive.
$ go build
$ ./ipfs-check
Starting ipfs-check
...
2024/08/29 20:42:34 Please wait, initializing accelerated-dht client.. (mapping Amino DHT may takes 5 or more minutes)
2024/08/29 20:42:34 Accelerated DHT client ready
2024/08/29 20:46:59 Backend ready and listening on [::]:3333
2024/08/29 20:46:59 Test fronted at http://localhost:3333/web/?backendURL=http://localhost:3333
2024/08/29 20:46:59 Ready to start serving.A test frontend runs at http://localhost:3333/web/?backendURL=http://localhost:3333.
To serve /web from a separate tool instead of the built-in HTTP server, any static file server works (or open the HTML file directly in a browser):
npx -y serve -l 3000 web
# Then open http://localhost:3000?backendURL=http://localhost:3333
Control log verbosity via the go-log environment variables.
For example, enable debug logs for specific subsystems:
$ GOLOG_LOG_LEVEL=info,dht=debug,net/identify=debug ./ipfs-checkMake an HTTP call with the cid and multiaddr query parameters:
$ curl "localhost:3333/check?cid=bafybeicklkqcnlvtiscr2hzkubjwnwjinvskffn4xorqeduft3wq7vm5u4&multiaddr=/p2p/12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK"The multiaddr takes two forms:
- Peer ID only (
/p2p/PeerID): the server resolves the Peer ID through the DHT and dials any returned address. - Full multiaddr with transport and Peer ID, e.g.
/ip4/140.238.164.150/udp/4001/quic-v1/p2p/12D3KooWRTUNZVyVf7KBBNZ6MRR5SYGGjKzS6xyiU5zBeY9wxomo/p2p-circuit/p2p/12D3KooWRBy97UB99e3J6hiPesre1MZeuNQvfan4gBziswrRJsNK: the Bitswap check uses the supplied multiaddr directly.
The checks performed depend on whether the request includes a multiaddr or only a cid.
The cidCheckOutput type describes the results:
type cidCheckOutput *[]providerOutput
type providerOutput struct {
ID string
ConnectionError string
Addrs []string
ConnectionMaddrs []string
DataAvailableOverBitswap BitswapCheckOutput
DataAvailableOverHTTP HTTPCheckOutput
BrowserCheck BrowserCheckOutput
Source string
}Fields of providerOutput:
ID: peer ID of the provider.ConnectionError: error message when the connection to the provider failed.Addrs: multiaddrs of the provider from the DHT.ConnectionMaddrs: multiaddrs used to reach the provider.DataAvailableOverBitswap: result of the Bitswap check.DataAvailableOverHTTP: result of the HTTP check.BrowserCheck: result of the browser reachability check.Source: origin of the provider record (IPNIorAmino DHT).
The peerCheckOutput type describes the results:
type peerCheckOutput struct {
ConnectionError string
PeerFoundInDHT map[string]int
ProviderRecordFromPeerInDHT bool
ProviderRecordFromPeerInIPNI bool
ConnectionMaddrs []string
DataAvailableOverBitswap BitswapCheckOutput
DataAvailableOverHTTP HTTPCheckOutput
BrowserCheck BrowserCheckOutput
}
type BrowserCheckOutput struct {
Enabled bool
WebBrowserCompatible bool
ServiceWorkerCompatible bool
VerifiedAddr string
CandidateAddrs []string
Error string
}
type BitswapCheckOutput struct {
Enabled bool
Duration time.Duration
Found bool
Responded bool
Error string
}
type HTTPCheckOutput struct {
Enabled bool
Duration time.Duration
Endpoints []multiaddr.Multiaddr
Connected bool
Requested bool
Found bool
Error string
}The check answers six questions:
-
Does the given peer advertise the CID in the DHT or in IPNI?
ProviderRecordFromPeerInDHT: the peer has a provider record in the DHT.ProviderRecordFromPeerInIPNI: the peer has a provider record in IPNI.
-
Are the peer's addresses discoverable?
PeerFoundInDHT: map of discovered addresses to how often each appeared in the DHT.
-
Does the peer respond at the supplied address?
ConnectionError: empty on success; otherwise the error.ConnectionMaddrs: multiaddrs used for the dial (includes both relay and direct addresses when NAT traversal occurred).
-
Is the data available over Bitswap?
DataAvailableOverBitswapcontains:Enabled: the Bitswap check ran.Duration: how long the check took.Found: the block was returned.Responded: the peer replied.Error: the error, if any.
-
Is the data available over HTTP?
DataAvailableOverHTTPcontains:Enabled: the HTTP check ran.Duration: how long the check took.Endpoints: the HTTP multiaddrs attempted.Connected: the connection succeeded.Requested: the request was sent.Found: the block was returned.Error: the error, if any.
-
Can a web browser retrieve from the peer?
BrowserCheckcontains:Enabled: the browser check ran.WebBrowserCompatible: a browser-usable address was reached.ServiceWorkerCompatible: the same address works inside a Service Worker, which has no WebRTC.VerifiedAddr: the address that worked. A libp2p handshake completed over it, or, for an HTTPS endpoint, it allowed a cross-origin read.CandidateAddrs: the announced addresses a browser could have used.Error: why none of them worked.CORS: for peers with an HTTPS endpoint, whether a page from another origin may read its responses.Enabledsays an endpoint was asked,Allowedsays a browser may read the block, andErrorsays why not. This is the whole question for an HTTP provider: the endpoint can answer perfectly and the browser still discards the bytes without anAccess-Control-Allow-Originheader covering the page.
Both verdicts describe what was reached, not what was advertised. An expired certificate on a
/tls/wsaddress, a/webrtc-directport that never answers, and an HTTPS endpoint that sends no CORS header all advertise a browser transport, and none of them let a page fetch a block.Browser-usable libp2p addresses are dialed on a separate host, over those addresses and nothing else. HTTPS endpoints cannot be settled by dialing, so they get a CORS preflight and, if that does not allow the read, the block request a gateway fetch actually makes. The second request matters: it is a simple request, which browsers never preflight, so an endpoint can refuse
OPTIONSand still be perfectly usable from a page.Four kinds of address qualify: Secure WebSockets (
/tls/ws,/wss), WebTransport, WebRTC Direct, and HTTPS trustless gateway endpoints (/tls/http,/https). A browser cannot open a raw socket, so a plain/wsaddress, or a bare/tcpor/quic-v1one, is out of reach, which is why a peer can serve every other client and still be unusable from a browser. WebTransport and WebRTC Direct need no certificate authority, since the certificate hash travels in the address; the other two need a TLS certificate, either your own or one from a broker such as AutoTLS.
The server exposes Prometheus metrics at /metrics, covering go-libp2p metrics and HTTP metrics for the check endpoint.
Protect /metrics with HTTP basic auth via the --metrics-auth-username and --metrics-auth-password flags:
./ipfs-check --metrics-auth-username=user --metrics-auth-password=pass
The IPFS_CHECK_METRICS_AUTH_USER and IPFS_CHECK_METRICS_AUTH_PASS environment variables work as well.
Important
There is no dedicated maintainer at the moment.
After Protocol Labs nucleation, the Shipyard team maintained this project in 2024, 2025 and 2026. Shipyard's IPFS maintenance work ended with 2026 Q3.
Bring support and transition questions to the community forum.