NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #183 by repository stars
Last release 8 days ago
27 Sep 2026
Ships on a steady schedule
a new release about every 1 weeks
Rarely documented
notes for 6 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
12 years old
1380 releases · first in 2015
Nothing published for this version
Nothing published for this version
Nothing published for this version
One column per quarter.
ipfs routing provide keeps working but is deprecated. See ipfs provide once --help for usage and migration notes.
Note
This release was brought to you by the Shipyard team.
ipfs provide once--local-onlyProvide.DHT.Interval=0 no longer disables providingSwarm.AddrFiltersipfs provide onceipfs provide once <cid>... announces CIDs to the routing system immediately, without waiting for the next scheduled reprovide. Use it when you want fine-grained control over when specific CIDs are announced.
CIDs can be streamed in on stdin, so you can pipe arbitrarily large lists without growing daemon memory:
# Announce every locally pinned CID.
ipfs pin ls | awk '{print $1}' | ipfs provide once# Announce every block reachable from a root (here, ~350 GiB of Wikipedia).
ipfs refs -r bafybeiaysi4s6lnjev27ln5icwm6tueaw2vdykrtjkwiphwekaywqhcjze | ipfs provide onceIn a terminal, the command shows a running count of queued CIDs. With --enc=json it emits one {"Queued":"<cid>"} line per CID, so downstream scripts can consume events as they arrive.
ipfs routing provide keeps working but is deprecated. See ipfs provide once --help for usage and migration notes.
--local-onlyipfs dag export --local-only writes a CAR with only the blocks you have locally; any missing blocks (and their subtrees) are skipped instead of failing the export. ipfs dag import --local-only reads such a partial CAR without trying to pin its roots.
This is useful when:
--local-only sets the matching companion flag automatically: on export it implies --offline; on import it implies --pin-roots=false. See ipfs dag export --help and ipfs dag import --help for details.
Provide.DHT.Interval=0 no longer disables providingProvide.DHT.Interval=0 now disables only the periodic reprovide schedule. New CIDs still announce via fast-provide-root and ipfs provide once. To fully disable providing, set Provide.Enabled=false.
Important
The daemon now refuses to start when Provide.DHT.Interval=0 is set without an explicit Provide.Enabled. Operators upgrading from an earlier kubo version must opt in to one of the two semantics:
Provide.Enabled=false to fully disable providing (the previous behaviour of Interval=0).Provide.Enabled=true to keep ad-hoc providing while skipping the periodic reprovide schedule.The startup error names both options. Pick the one that matches your intent.
ipfs pin ls, ipfs add, and other pin-touching operations could block for hours on nodes running with Provide.Strategy set to pinned, roots, or pinned+mfs (including +unique / +entities variants). The pin index held a read lock for the entire reprovide cycle, which on large pinsets takes many hours. Any pin operation issued during that window blocked, and further pin ls / ipfs add calls piled up behind it until the cycle finished.
The pinner now snapshots the index under the read lock and releases it before the reprovider starts, so pin operations are no longer blocked by the reprovide cycle. The default Provide.Strategy=all was not affected.
The one-time migration for repos from go-ipfs or Kubo older than v0.27 now retries across several gateways with HTTP timeouts, so a single slow or blocked gateway no longer hangs the daemon. Set Migration.DownloadSources to use your own gateway list.
Sending SIGTERM or SIGINT to kubo could leave the daemon stuck "half-shutdown": internal subsystems had stopped, but the process kept running and answering the RPC API. Docker and Kubernetes health checks reported the node as healthy while it had quietly stopped serving content. Recovery required a manual docker restart. Separately, the pinner could log a pebble: closed panic trace when the datastore closed before ongoing pin operations finished.
What changed:
Bounded shutdown. A new Internal.ShutdownTimeout caps how long a stuck shutdown can run, so a zombie daemon recovers instead of staying half-alive. Routine shutdowns finish in seconds; this is a belt-and-suspenders ceiling against unknown bugs and future regressions. The 12-hour default is high enough that no real-world deployment hits it and low enough to recycle a stuck node well before its DHT provider records expire (22 hours). On expiry, the daemon logs which subsystem failed and exits with status 1. Set 0 to disable.
ipfs diag healthy subcommand. Returns non-zero as soon as shutdown begins, even if the RPC API still answers. The kubo Docker image's HEALTHCHECK now uses it, so under --restart=on-failure or a Kubernetes liveness probe a half-shutdown daemon is recycled within seconds.
Pinner shuts down cleanly. The pinner cancels and waits for ongoing pin work before the datastore closes, removing the pebble: closed panic trace from shutdown logs.
DHT provider deadlines. ipfs provide stat now returns promptly when the caller cancels, instead of blocking on a slow keystore lookup (previously seen at over an hour). Each provider record sent to a peer is capped by Provide.DHT.SendProviderRecordTimeout, so an unresponsive peer cannot stall a reprovide cycle.
Swarm.AddrFiltersIf you list a specific address in Addresses.Swarm and a rule in Swarm.AddrFilters blocks it, no incoming connection reaches that listener. Kubo now logs one ERROR per such listener, naming the listener, the matching rule, and the field to remove the rule from.
The common trigger: a /ip4/127.0.0.1/tcp/.../ws listener fronted by nginx or Caddy on a server-profile node. The profile adds /ip4/127.0.0.0/ipcidr/8 to Swarm.AddrFilters, which rejects every proxy connection over loopback. See the reverse-proxy override row for the fix.
Wildcard listens (/ip4/0.0.0.0, /ip6/::) stay out of the ERROR log. Even if their interface expansion lands inside a filtered CIDR, the listener still accepts traffic on the interfaces outside that CIDR, so the filter is working as intended. These matches log at DEBUG instead, so you can still trace which interfaces an AddrFilters rule strips when you need to.
Addresses.NoAnnounce matches also log at DEBUG. Hiding addresses there is the point of the field, but the log line helps when you ask "why isn't this interface in my identify or DHT records?" and the answer is a CIDR rule you forgot you set.
The Prometheus endpoint no longer emits the otel_scope_info metric. Each metric now carries otel_scope_name, otel_scope_version, and otel_scope_schema_url labels identifying the instrumentation library that produced it. Update dashboards or queries that read otel_scope_info to consume these labels instead. See docs/metrics.md for details.
ipfs add, ipfs cat, and ipfs get now hide their progress bar when stderr is piped or redirected, so a command like ipfs add file 2> log.txt no longer fills the log with progress-bar noise. Pass --progress=true to force the bar on, or --progress=false to hide it.
ipfs dag export and ipfs dag stat now correctly recognize MSYS2 and Git Bash terminals on Windows. Previously the bar was suppressed there even when running interactively.
go-libp2p-pubsub to v0.16.0go-libp2p-kad-dht to v0.40.0 (incl. v0.39.2)go-fuse/v2 to v2.10.1 (incl. v2.10.0)cheggaaa/pb to v3.1.7boxo to v0.40.0p2p-forge/client to v0.9.0 (incl. v0.8.1)ipfs provide once and support Interval=0 mode (#11321) (ipfs/kubo#11321)st_blocks and st_blksize (#11280) (ipfs/kubo#11280)car put-block command (#629) (ipld/go-car#629)supportsPartialsupportsPartial field in SubOpts| Contributor | Commits | Lines ± | Files Changed |
|---|---|---|---|
| @lidel | 42 | +7059/-920 | 188 |
| @MarcoPolo | 43 | +5818/-2113 | 122 |
| @guillaumemichel | 8 | +1422/-165 | 17 |
| @ChayanDass | 2 | +421/-18 | 10 |
| @parkan | 1 | +339/-0 | 3 |
| @gammazero | 12 | +142/-135 | 28 |
| @Vinayak9769 | 1 | +145/-78 | 10 |
| @laciferin2024 | 1 | +209/-0 | 3 |
| @rvagg | 4 | +160/-18 | 6 |
| @Wondertan | 1 | +154/-4 | 4 |
| @cortze | 1 | +125/-19 | 5 |
| @sukunrt | 3 | +58/-27 | 5 |
| @davidebeatrici | 1 | +55/-30 | 4 |
| @hsanjuan | 1 | +33/-15 | 5 |
| @willscott | 1 | +10/-2 | 2 |
Note This Release Preview was brought to you by the Shipyard team.
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.42.md
Release status: #11227
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
It supersedes the external ipfs-update tool, deprecated since v0.37 .
Note
This release was brought to you by the Shipyard team.
ipfs cid inspect command--cid-base fixes across all commandsipfs update commandProvide.Strategy modifiers: +unique and +entitiespin add and pin update now fast-provide root CID--fast-provide-dag flag for fine-tuned provide controlProvide.Strategy parsingProvide.Strategyipfs object patch validates UnixFS node typesserver profile no longer announces loopback and non-public IPv6 addressesNodes with significant amount of data and DHT provide sweep enabled (Provide.DHT.SweepEnabled, the default since Kubo 0.39) could see their datastore/ directory grow continuously. Each reprovide cycle rewrote the provider keystore inside the shared repo datastore, generating tombstones faster than the storage engine could compact them, and in default configuration Kubo was slow to reclaim this space.
The provider keystore now lives in a dedicated datastore under $IPFS_PATH/provider-keystore/. After each reprovide cycle the old datastore is removed from disk entirely, so space is reclaimed immediately regardless
of storage backend.
On first start after upgrading, stale keystore data is cleaned up from the shared datastore automatically.
To learn more, see kubo#11096, kubo#11198, and go-libp2p-kad-dht#1233.
ipfs cid inspect commandNew subcommand for breaking down a CID into its components. Works offline, supports --enc=json.
$ ipfs cid inspect bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
CID: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi
Version: 1
Multibase: base32 (b)
Multicodec: dag-pb (0x70)
Multihash: sha2-256 (0x12)
Length: 32 bytes
Digest: c3c4733ec8affd06cf9e9ff50ffc6bcd2ec85a6170004bb709669c31de94391a
CIDv0: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR
CIDv1: bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdiSee ipfs cid --help for all CID-related commands.
--cid-base fixes across all commands--cid-base is now respected by every command that outputs CIDs. Previously block stat, block put, block rm, dag stat, refs local, pin remote, and files chroot ignored the flag.
CIDv0 values are now auto-upgraded to CIDv1 when a non-base58btc base is requested, because CIDv0 can only be represented in base58btc.
ipfs update commandKubo now ships with a built-in ipfs update command that downloads release binaries from GitHub and swaps the current one in place. It supersedes the external ipfs-update tool, deprecated since v0.37.
$ ipfs update check
Update available: 0.40.0 -> 0.41.0
Run 'ipfs update install' to install the latest version.See ipfs update --help for the available subcommands (check, versions, install, revert, clean).
IPFS Web UI has been updated to v4.12.0.
The Peers screen now resolves IPv6 addresses to geographic locations, and the geolocation database has been updated to GeoLite2-City-CSV_20260220. (ipfs-geoip v9.3.0)
Peer locations load faster thanks to UX optimizations in the underlying ipfs-geoip library.
Nodes using custom routing (Routing.Type=custom) with IPIP-526 could end up publishing unresolved 0.0.0.0 addresses in provider records. Addresses are now resolved at provide-time, and when AutoNAT V2 has confirmed publicly reachable addresses, those are preferred automatically. See #11213.
Provide.Strategy modifiers: +unique and +entitiesExperimental opt-in optimizations for content providers with large repositories where multiple recursive pins share most of their DAG structure (e.g. append-only datasets, versioned archives like dist.ipfs.tech).
+unique: bloom filter dedup across recursive pins. Shared subtrees are traversed only once per reprovide cycle instead of once per pin, cutting I/O from O(pins * blocks) to O(unique blocks) at ~4 bytes/CID.+entities: announces only entity roots (files, directories, HAMT shards), skipping internal file chunks. Far fewer DHT provider records while keeping all content discoverable by file/directory CID. Implies +unique.Example: Provide.Strategy = "pinned+mfs+entities"
The default Provide.Strategy=all is unchanged. See Provide.Strategy for configuration details and caveats.
The bloom filter precision is tunable via Provide.BloomFPRate (default ~1 false positive per 4.75M lookups, ~4 bytes per CID).
pin add and pin update now fast-provide root CIDipfs pin add and ipfs pin update announce the pinned root CID to the routing system immediately after pinning, same as ipfs add and ipfs dag import. This matters for selective strategies like pinned+mfs, where previously the root CID was not announced until the next reprovide cycle (see Provide.DHT.Interval). With the default Provide.Strategy=all, the blockstore already provides every block on write, so this is a no-op.
Both commands now accept --fast-provide-root, --fast-provide-dag, and --fast-provide-wait flags, matching ipfs add and ipfs dag import. See Import for defaults and configuration.
--fast-provide-dag flag for fine-tuned provide controlUsers with a custom Provide.Strategy (e.g. pinned, pinned+mfs+entities) now have finer control over which CIDs are announced immediately on ipfs add, ipfs dag import, ipfs pin add, and ipfs pin update.
By default, only the root CID is provided right away (--fast-provide-root=true). Child blocks are deferred until the next reprovide cycle. This keeps bulk imports fast and avoids overwhelming online nodes with provide traffic.
Pass --fast-provide-dag=true (or set Import.FastProvideDAG) to provide the full DAG immediately during add, using the active Provide.Strategy to determine scope.
Provide.Strategy=all (default) is unaffected. It provides every block at the blockstore level regardless of this flag.
Note
Faster default imports for Provide.Strategy=pinned and pinned+mfs users. Previously, ipfs add --pin eagerly announced every block of newly added content as it was written, through an internal DAG service wrapper. This release routes add-time providing through the new --fast-provide-dag code path, which defaults to false. The result is faster bulk imports and less provide traffic during add: only the root CID is announced immediately (via Import.FastProvideRoot), and child blocks are picked up by the next reprovide cycle (see Provide.DHT.Interval, default 22h). To restore the previous eager-provide behavior on ipfs add, set Import.FastProvideDAG=true (or pass --fast-provide-dag=true per command); the walker honors the active Provide.Strategy. Provide.Strategy=all (the default) is unaffected.
Provide.Strategy parsingUnknown strategy tokens (e.g. typo "uniuqe"), malformed delimiters ("pinned+"), and invalid combinations ("all+pinned") now produce a clear error at startup instead of being silently ignored.
Provide.StrategyBlocks added via the filestore or urlstore (ipfs add --nocopy) used to ignore Provide.Strategy and were always announced at write time. The filestore is now gated on the strategy the same way the regular blockstore is, so selective strategies get the same fast-provide knobs for filestore-backed content that they already had for regular ipfs add.
ipfs object patch validates UnixFS node typesAs part of the ongoing deprecation of the legacy ipfs object API (which predates HAMTShard directories and CIDv1), the add-link and rm-link subcommands now validate the root node before mutating it.
These commands operate at the raw dag-pb level and can only safely mutate small, flat UnixFS directories. They are unable to update UnixFS metadata (HAMT bitfields, file Blocksizes), so using them on files or sharded directories would silently produce invalid DAGs. This is now rejected:
Blocksizes, content lost on read-back)dag-pb nodes: rejected by defaultUse ipfs files commands (mkdir, cp, rm, mv) instead. They handle all directory types correctly, including large sharded directories.
A --allow-non-unixfs flag is available on both ipfs object patch commands to bypass validation.
ipfs files commands now correctly preserve the configured CID version and hash function (Import.CidVersion, Import.HashFunction) in all MFS operations. Previously, the CidBuilder could be silently lost when modifying file contents, creating nested directories with mkdir -p, or restarting the daemon, causing some entries to fall back to CIDv0/sha2-256.
Additionally, the MFS root directory itself now respects Import.CidVersion and Import.HashFunction at daemon startup. Before this fix, the root always used CIDv0/sha2-256 regardless of config. Because the MFS root CID format is now managed by these config options, ipfs files chcid no longer accepts the root path /. It continues to work on subdirectories.
See boxo#1125 and kubo#11273.
The FUSE implementation has been rewritten on top of hanwen/go-fuse v2, replacing the unmaintained bazil.org/fuse. This fixes long-standing architectural limitations and aligns FUSE mounts with what standard tools expect. FUSE support is still experimental. See docs/fuse.md for setup instructions, and report problems at kubo/issues.
fsync works. Editors (vim, emacs) and databases that call fsync after writing no longer get a silent no-op. Data is flushed through the open file descriptor to the DAG. The full vim save sequence (O_TRUNC + write + fsync + chmod) is tested.ftruncate works. Tools like rsync --inplace that shrink or grow files via ftruncate(fd, size) no longer get ENOTSUP. Opening existing files with O_TRUNC also works correctly.chmod and touch no longer drop file content. With Mounts.StoreMode/StoreMtime enabled, setting mode or mtime previously replaced the DAG node without preserving content links, leaving the file empty.ln -s target link now works on /mfs and /ipns. Symlinks are stored as UnixFS TSymlink nodes, the same format used by ipfs add./ipfs. Files are read sequentially from the block graph instead of re-resolving from the root on every read call.cat works. Ctrl-C or kill on a read cancels in-flight block fetches instead of hanging.fusermount -u from outside the daemon now correctly marks the mount as inactive.allow_other./ipfs or /ipns no longer returns an error./ipfs. Accessing a file by its CID directly under the /ipfs mount now works. This was a long-standing regression./mfs and /ipns. Renaming a file within the same directory no longer leaves the source behind./ipns/local/ now correctly publishes the updated DAG. Previously IPNS publishing from the FUSE mount was silently blocked./ipns file handle serializes Read, Write, Flush, and Release, matching the /mfs mount./ipns flush changes to the MFS root, preventing data loss on daemon restart./ipns inherit the parent's CID settings instead of falling back to CIDv0.0644/0444, directories: 0755/0555).Mounts.StoreMtime and Mounts.StoreMode. Writable mounts can persist mtime on file creation/write and POSIX mode on chmod for both files and directories. touch on directories also works, which tools like tar and rsync rely on. Both flags are off by default because they change the resulting CID. See Mounts.StoreMtime and Mounts.StoreMode.ipfs.cid xattr on all mounts. All three mounts expose the node's CID via the ipfs.cid extended attribute on files and directories. The legacy ipfs_cid xattr name (used in earlier versions of /mfs) is no longer supported; use ipfs.cid instead.statfs works. All three mounts report the free space of the volume backing the local IPFS repo, so /mfs correctly reflects how much new data fits. Fixes macOS Finder refusing copies with "not enough free space".st_blocks and st_blksize reflect UnixFS. All three mounts fill st_blocks from the UnixFS file size so du, ls -s, stat, and "size on disk" in file managers match ls -l. Directories report a nominal 1 block so tools that treat 0 as "unsupported" behave correctly. st_blksize advertises a chunk-aligned preferred I/O size: /mfs and /ipns use Import.UnixFSChunker, so cp, dd, and rsync buffer writes at the chunker boundary; /ipfs uses a stable 1 MiB hint since published CIDs have no single chunker.fusermount symlink; hanwen/go-fuse finds fusermount3 natively.ipfs dag import of CARv2 files now works over the HTTP API. Previously it failed with operation not supported: the HTTP multipart stream falsely advertised seek support, which go-car needs for the CARv2 payload offset. See #11253.
Kubo's outbound HTTP clients and libp2p /ws+/wss peer dials have long honored the standard HTTPS_PROXY, HTTP_PROXY, and NO_PROXY environment variables; this release extends the WebSocket transport to also accept https:// proxy URLs (TLS to the proxy itself), matching what Kubo's HTTP clients already supported. See docs/environment-variables.md.
server profile no longer announces loopback and non-public IPv6 addressesThe opt-in server profile now also blocks IPv4 loopback (127.0.0.0/8) and the IANA-reserved 0000::/3 IPv6 block. Since v0.40.0, libp2p has enumerated all local interfaces, causing public server-profile nodes (including the default IPFS bootstrappers) to leak loopback and unallocated IPv6 prefixes like 1e::/16 through libp2p identify and DHT self-records (see go-libp2p#3460).
Default-configured nodes are unaffected. To pick up the new entries on an existing server-profile node:
$ ipfs config profile apply serverThe command is idempotent. See the server profile docs for the full filter list, RFC references, and override guidance.
Warning
The server profile disables local peer discovery (Discovery.MDNS off, loopback filtered), so co-located daemons on the same host and peers on the same LAN will no longer find each other automatically. Apply only on public-internet nodes where that is intended.
Kubo first shipped with Go 1.26 in v0.40.0, but v0.40.1 had to downgrade to Go 1.25 because of a Windows crash in Go's overlapped I/O layer (#11214). Go 1.26.2 fixes that regression upstream (golang/go#78041), so Kubo is back on Go 1.26 across all platforms.
You should see lower memory usage and reduced GC pauses thanks to the new Green Tea garbage collector (10-40% less GC overhead). Reading block data and API responses is faster due to io.ReadAll improvements (~2x faster, ~50% less memory). On 64-bit platforms, heap base address randomization adds a layer of security hardening.
Long-running daemons could exit with invalid memory address or nil pointer dereference or similar memory errors while handling DHT traffic. The cause was a data race in the routing layer that had been latent for years: PublishQueryEvent handed QueryEvent.Responses to subscribers (like findprovs) by pointer while the publisher kept mutating the same AddrInfo.Addrs slices.
Two recent changes likely tipped the race into frequent visible crashes: go-multiaddr v0.15 turned Multiaddr from an interface into a slice-backed struct, and Go 1.26 added heap base address randomization and a new garbage collector. Both likely made torn concurrent reads more likely to dereference unmapped memory.
This release picks up the targeted fix in go-libp2p-kad-dht#1244. A broader fix for the whole class of routing publish races is proposed upstream in go-libp2p#3490.
go-libp2p to v0.48.0go-libp2p-kad-dht to v0.39.1 (incl. v0.39.0)ipfs-webui to v4.12.0gateway-conformance tests to v0.13 (incl. v0.12, v0.11)boxo to v0.39.0 (incl. v0.38.0)go-cid to v0.6.1 (pulls in go-multibase v0.3.0 with up to 5x faster base58 encoding for CIDv0)p2p-forge/client to v0.8.0st_blocks and st_blksize (#11280) (ipfs/kubo#11280)ipfs update command (#11203) (ipfs/kubo#11203)object patch (#11248) (ipfs/kubo#11248)ipfs name put for IPNS record republishing (#11199) (ipfs/kubo#11199)iter.Value with iter.ValueAndErr (#80) (ipfs/go-ds-pebble#80)Note truncated.
Note This Release Preview was brought to you by the Shipyard team.
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.41.md
Release status: #11082
Note This Release Preview was brought to you by the Shipyard team.
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.41.md
Release status: #11082
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →