Repository navigation
Releases: ipfs/kubo
Release list
v0.43.1
Note
This release was brought to you by the Shipyard team.
Important
v0.43 is the last Kubo by the Shipyard. Our IPFS work ends on September 30, 2026. Bring your support and transition questions to the community forum.
- 🔦 Highlights
- 🔇 Empty
Bootstraplist disables all bootstrap dialing - 🗄️ Improved datastore profiles and docs
- 🗂️ Setting the flatfs shard depth is less error-prone
- 📁
ipfs lscan print readable sizes and sort by size - 📌 Files on FUSE mounts keep their inode number
- 🧹 FUSE mounts keep what you write after a rename
- 🔐
ipfs key exportwrites are owner-only - 🛑
ipfs shutdownexits a daemon started with--enable-gc - 🔗 Gateway
Ipfs-Uriheader replacesX-Ipfs-Path(IPIP-548) - 🔄 Bitswap sees peers connected before it starts
- 🤫 No more background pings to HTTP providers
- 🔕 Telemetry is off by default
- 🌐 Future-proofing browser retrieval: WebTransport fix for Safari 26
- 💾 Provider record writes are batched again
- 🔒 Hardened CID profiles, new low-level knob
- 📦️ Dependency updates
- 🔇 Empty
- 📝 Changelog
- 👨👩👧👦 Contributors
This is mostly a bugfix release. It also carries a few small, opt-in configuration options and profiles that landed since v0.43.0.
🔦 Highlights
🔇 Empty Bootstrap list disables all bootstrap dialing
Setting Bootstrap to [] now stops the node from dialing bootstrap peers of any kind, including the backup peers it saved from earlier runs. Until now, a node that had once run with the default bootstrappers kept dialing those saved peers every 30 seconds, even with an empty list and Routing.Type=none.
The result is a node that connects only to peers you chose: Peering.Peers, ipfs swarm connect, and peers found on the local network through mDNS. For no outbound traffic at all, also set Routing.Type to none and disable AutoConf.
Backup peers still work when Bootstrap has at least one entry: the node dials the configured peers first and falls back to saved peers only while it stays below the minimum peer count.
🗄️ Improved datastore profiles and docs
Kubo keeps blocks in flatfs and everything else (pins, MFS root, IPNS and DHT provider records) in leveldb. leveldb is slow to reclaim space after bulk deletes, so on nodes that churn that metadata all day, datastore/ grows far past the live data. ipfs init --profile=flatfs-pebbleds keeps flatfs for blocks and puts the rest in pebble, which compacts deleted keys promptly. This works for new repos only. The profile is experimental, and pebble has less production use in Kubo than leveldb, so report problems in kubo issues. Details in the profile docs.
The default layout is unchanged. Its profile is now named flatfs-levelds, and flatfs and flatfs-measure stay as aliases. The datastore docs say which data lives in which store and why flatfs holds only blocks.
🗂️ Setting the flatfs shard depth is less error-prone
The flatfs shard depth (shardFunc) can only be set when a repo is created, by passing a config file to ipfs init. That route had traps. Fixed:
ipfs init <file>andipfs daemon --init --init-configrefuse a config without a private key, such asipfs config showoutput, which used to produce a repo that crashed on start.ipfs initchecksDatastore.Specbefore writing anything, so a typo inshardFuncno longer leaves a half-made repo.ipfs config profile applyrefuses a profile that would change the datastore layout of an existing repo instead of writing a config the repo can no longer open.- The
Datastore.Specvsdatastore_specmismatch error labels the two values correctly and says what to do.
docs/datastores.md has the steps for setting next-to-last/3 on a new repo, when it is worth it, and how to move an existing repo. Defaults are unchanged.
📁 ipfs ls can print readable sizes and sort by size
Two new flags on ipfs ls, both off by default. --human (-H) prints sizes in SI units such as 3.0 kB and 2.0 MB, matching ipfs repo stat -H. --sort-size (-S) lists the biggest entries first, so you can find what fills a directory without piping through sort. Run ipfs ls --help for how they combine with --stream, --size, and the JSON response.
📌 Files on FUSE mounts keep their inode number
A file on a mount made by ipfs mount used to get a new inode number roughly every second, whenever the kernel dropped and re-read its directory entry. Programs that check whether a file is still the same file read that as the file being replaced under them. Saving a file on /mfs with vim could fail with E949: File changed while writing, and pwd, find and backup tools could misbehave for the same reason.
All three mounts now hand out stable numbers. On /ipfs the number comes from the CID, so the same content is the same object whichever path reaches it, and the same number comes back after a remount. On /ipns and /mfs an entry keeps its number for as long as it exists, including across a rename, while a name that is deleted and created again is numbered afresh; those numbers are assigned per mount and start over on the next one. Deleting through ipfs files rather than through the mount is the exception: the mount does not see it, so the name keeps its old number if it comes back.
Smaller fixes come with it: mount points report an inode number instead of 0, ls -i agrees with stat, the link count is 1 rather than the 0 POSIX gives to a deleted file, and /ipns/<key> directories show their real permissions instead of d---------.
🧹 FUSE mounts keep what you write after a rename
Renaming on a mounted /mfs or /ipns left the kernel writing into the entry the rename had just taken away. mv a b followed by a write to b reported success and then quietly went back to the old contents a second later. A file created in a directory that had just been renamed vanished the same way, and the directory reappeared under the name it had been moved away from. Renaming over a directory that was not empty deleted everything in it, where POSIX asks for ENOTEMPTY.
On /ipfs, a listing no longer fails in its entirety when one child's block is missing, an entry in a codec the mount cannot decode reads back the block rather than refusing, and the ipfs.cid xattr answers with the CID from the path instead of a re-encoded form of it.
🔐 ipfs key export writes are owner-only
ipfs key export now writes the key file with owner-only permissions (0600), including when it overwrites an existing file with looser permissions. The export goes to a temporary file renamed over the target, so a failed export leaves the previous contents intact; character devices and pipes such as /dev/null are streamed to directly.
🛑 ipfs shutdown exits a daemon started with --enable-gc
A daemon started with --enable-gc closed its repo on ipfs shutdown (POST /api/v0/shutdown) but the process never exited, because the periodic garbage collection loop only stopped on a signal. Supervisors that stop Kubo over the RPC API waited forever. The loop now also stops when the node closes; SIGINT and SIGTERM behave as before.
🔗 Gateway Ipfs-Uri header replaces X-Ipfs-Path (IPIP-548)
Gateway responses now include an Ipfs-Uri header with a canonical ipfs:// or ipns:// address of the requested content path (IPIP-548). Every path segment is percent-encoded, so the address survives any file name, including names with spaces, %, #, and non-ASCII characters.
Important
Kubo no longer sends the deprecated X-Ipfs-Path header. HTTP header values cannot carry non-ASCII bytes, so a path with such a file name arrives garbled. Clients that read X-Ipfs-Path must migrate to `Ipfs...
v0.43.0
Note
This release was brought to you by the Shipyard team.
Important
This is the last Kubo release with new features from the Shipyard team. Our IPFS work ends on September 30, 2026. Until then we will ship only critical security and bug fix releases if any are needed. After that date, no one at Shipyard maintains Kubo. If you depend on Kubo, read the announcement and send us your transition questions before the end of September.
Update: there is a community discussion here
- Overview
- 🔦 Highlights
- 🔗 Native
ipfs://andipns://URIs work as input - 🛜 One-time notice when behind CGNAT
- 🩺 AutoTLS checks broker health before registration
- 🕒 Revamped TTL and expiration handling for IPNS and DNSLink
- 🛑 Clearer errors for invalid config at startup
- 🗂️
ipfs filesno longer hangs when garbage collection runs - 🔑
ipfs config replacekeeps PeerID and private key in sync - 🔐 secp256k1 key generation, export, and import
- 🔄 Sturdier DHT reprovides on large nodes
- 📡 Future-proofing browser retrieval:
webrtc-directv2 - 🌐 Future-proofing browser retrieval: WebTransport draft-15
- 🧭 Delegated routers now hand browsers an address they can dial
- 🗺️ Fewer stale addresses in the peerstore
- 🕳️ Behind NAT: faster relay recovery, dependable shutdown
- 🚮
ipfs initno longer creates an IPNS record - 📛 Unified IPNS record storage
- 🧪 Tests use new go-test and rand v2
- 🖥️ WebUI Improvements
- 🧵 A truncated CAR response now says so
- 🔒 Security fixes: update recommended
- 📦️ Dependency updates
- 🔗 Native
- 📝 Changelog
- 👨👩👧👦 Contributors
Overview
🔦 Highlights
🔗 Native ipfs:// and ipns:// URIs work as input
Commands that take a path or CID now also accept native IPFS URIs: ipfs://<cid>, ipns://<name>, and the shorter ipfs:<cid> and ipns:<name> forms. ipfs cat ipfs://<cid> now behaves the same as ipfs cat /ipfs/<cid> or ipfs cat <cid>.
ipfs:// and ipns:// are how web browsers, browser extensions, and many non-IPFS apps link to and share IPFS content. Before, you had to rewrite such an address into an /ipfs/ path before Kubo would take it. Now you can copy a URI from a browser address bar and paste it straight into the CLI or the RPC API.
This works wherever a path or CID is accepted, including cat, get, ls, refs, dag, block, pin, files, and name resolve. The scheme is case-insensitive, and the CID or name after it is left untouched.
🛜 One-time notice when behind CGNAT
Kubo now logs a one-time notice to stderr at startup when it detects it is behind carrier-grade NAT (CGNAT) or double NAT. CGNAT is common on IPv4-scarce ISPs that share one public address across many subscribers: other peers cannot reach the node directly, and a busy node can fill the shared NAT session table and disrupt internet access for every device on the local network. The notice gives that otherwise hard-to-diagnose "my whole home network drops" symptom a clear cause.
Detection is best-effort and conservative: it fires only when a private or shared-range (100.64.0.0/10, RFC 6598) address appears as a NAT-mapped WAN address (via UPnP/NAT-PMP/PCP) that is not one of the node's own interfaces. Kubo ignores addresses on a local interface, so VPN and overlay tools that use 100.64.0.0/10 (such as Tailscale) do not trigger it; when the upstream address is hidden, the node looks like any ordinary NAT and Kubo stays quiet. ipfs swarm addrs autonat reports the current classification in its nat field (--enc=json).
Silence the notice with Internal.CGNATCheck=false. The dead-listener diagnostic added in v0.42 can now be toggled too, with Internal.DeadListenerCheck.
🩺 AutoTLS checks broker health before registration
AutoTLS certificate issuance depends on the ACME DNS-01 broker at AutoTLS.RegistrationEndpoint (registration.libp2p.direct by default). Before, a publicly reachable node without a certificate would attempt ACME issuance even when that broker was unreachable (offline network, firewall, service outage) and keep retrying in the background for days, filling logs with errors that could not resolve themselves.
Now the broker's health endpoint is checked right before the first registration attempt, after the registration delay (1h by default, none when AutoTLS.Enabled=true is set explicitly) and once the node is publicly reachable. While the broker keeps failing the check, certificate setup is postponed with a single ERROR in the log and one cheap re-check per hour, and issuance starts automatically once the broker recovers. Nodes that already have a certificate are unaffected, and short-lived nodes (such as CI runners) produce no broker traffic at all.
🕒 Revamped TTL and expiration handling for IPNS and DNSLink
A record's own lifetime and TTL now govern both how Kubo creates it and how long clients may cache it, and a cache never outlives the record's validity. This applies when ipfs name publish creates an IPNS record, and when the gateway serves IPNS and DNSLink responses.
At publish time, ipfs name publish sanitizes its duration flags before creating an IPNS record, instead of emitting one that fails verification later:
--lifetimemust be greater than zero; a non-positive value would expire the record immediately.--ttlmust be non-negative. An explicit--ttlgreater than--lifetimeis rejected; an omitted--ttlis capped to--lifetime, since a record is not cached past its validity.
Important
The daemon now refuses to start when Ipns.RecordLifetime is shorter than Ipns.RepublishPeriod: records would expire before the republisher refreshes them, leaving the name unresolvable. Raise Ipns.RecordLifetime or lower Ipns.RepublishPeriod so the lifetime is at least the period.
On the serving side, the gateway derives a client's cache lifetime from the record itself:
- DNSLink websites (
/ipns/<dnslink-host>) setCache-Control: max-agefrom the DNS TXT record's own TTL, so a browser or CDN caches the site for as long as its DNS record allows and re-fetches once that expires, instead of following a fixed default that cached short-lived records too long and long-lived ones too briefly. This needs a resolver that reports TTLs: Go's built-in OS resolver does not, so pointDNS.Resolversat a DNS-over-HTTPS endpoint (a.entry covers every domain) to turn it on. Without one, DNSLink caching is unchanged. When a name resolves through several hops (a DNSLink pointing at an IPNS name, or a chain of them), the shortest TTL along the way wins. - IPNS records cap their
Cache-Control: max-ageto the time left before the record expires (its EOL), so a cache cannot serve a record past the point it stops being valid. An already-expired record, or one whose expiration is unknown, returnsno-store, and a malformed record's negative TTL is floored at zero instead of surfacing as a negativemax-age. This covers both gateway IPNS responses and/routing/v1/ipnswhen the delegated routing server is enabled (boxo#1166). - Revalidation with
If-None-MatchorIf-Modified-Sincenow returnsEtagandCache-Controlon the304 Not Modifiedresponse, matching what the200would send. A bare 304 used to leave the client's stored copy expired, forcing a fresh revalidation on every later request even when the content had not changed (boxo#1188).
For resolved names, a cache hit reports the time remaining on the cache entry rather than the record's original TTL, so a late hit near expiry no longer restarts the full caching window on the client. [`Ipns.MaxCac...
v0.43.0-rc2
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.43.md
Release status: #11298
v0.43.0-rc1
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.43.md
Release status: #11298
v0.42.0
Note
This release was brought to you by the Shipyard team.
- Overview
- 🔦 Highlights
- 🎯 Announce CIDs on demand with
ipfs provide once - 🧩 Export and import partial CARs with
--local-only - ⚙️
Provide.DHT.Interval=0no longer disables providing - 🐛 Fixed pin operations hanging under pinned reprovide strategies
- 🐛 Smoother first-run upgrades from very old repos
- 🐛 Reliable shutdown and container health checks
- 🚨 ERROR log for explicit listeners blocked by
Swarm.AddrFilters - 📊 OpenTelemetry: scope info now exposed as labels
- 🔧 Cleaner progress bars
- 📦️ Dependency updates
- 🎯 Announce CIDs on demand with
- 📝 Changelog
- 👨👩👧👦 Contributors
Overview
🔦 Highlights
🎯 Announce CIDs on demand with ipfs provide once
ipfs 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.
🧩 Export and import partial CARs with --local-only
ipfs 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:
- you want to share part of a DAG (for example an MFS tree) that is only partly cached locally
- you fetched a partial CAR from a gateway that supports IPIP-0402 and want to add what you got to your local store
--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 providing
Provide.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=falseto fully disable providing (the previous behaviour ofInterval=0).Provide.Enabled=trueto keep ad-hoc providing while skipping the periodic reprovide schedule.
The startup error names both options. Pick the one that matches your intent.
🐛 Fixed pin operations hanging under pinned reprovide strategies
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.
🐛 Smoother first-run upgrades from very old repos
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.
🐛 Reliable shutdown and container health checks
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.ShutdownTimeoutcaps 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 status1. Set0to disable. -
ipfs diag healthysubcommand. Returns non-zero as soon as shutdown begins, even if the RPC API still answers. The kubo Docker image'sHEALTHCHECKnow uses it, so under--restart=on-failureor 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: closedpanic trace from shutdown logs. -
DHT provider deadlines.
ipfs provide statnow 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 byProvide.DHT.SendProviderRecordTimeout, so an unresponsive peer cannot stall a reprovide cycle.
🚨 ERROR log for explicit listeners blocked by Swarm.AddrFilters
If 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.
📊 OpenTelemetry: scope info now exposed as labels
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.
🔧 Cleaner progress bars
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. Pa...
v0.42.0-rc1
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.42.md
Release status: #11227
v0.41.0
Note
This release was brought to you by the Shipyard team.
- Overview
- 🔦 Highlights
- 🗑️ Faster Provide Queue Disk Reclamation
- ✨ New
ipfs cid inspectcommand - 🔤
--cid-basefixes across all commands - 🔄 Built-in
ipfs updatecommand - 🖥️ WebUI Improvements
- 🔧 Correct provider addresses for custom HTTP routing
- 🔀
Provide.Strategymodifiers:+uniqueand+entities - 📌
pin addandpin updatenow fast-provide root CID - 🌳 New
--fast-provide-dagflag for fine-tuned provide control - 🛡️ Hardened
Provide.Strategyparsing - 🔧 Filestore now respects
Provide.Strategy - 🛡️
ipfs object patchvalidates UnixFS node types - 🔗 MFS: fixed CidBuilder preservation
- 📂 FUSE Mount Improvements
- 📦 CARv2 import over HTTP API
- 🌐 HTTPS proxy support
- 🛡️
serverprofile no longer announces loopback and non-public IPv6 addresses - 🐹 Go 1.26, Once More with Feeling
- 🐛 Fixed long-standing random daemon crashes during DHT lookups
- 📦️ Dependency updates
- 📝 Changelog
- 👨👩👧👦 Contributors
Overview
🔦 Highlights
🗑️ Faster Provide Queue Disk Reclamation
Nodes 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.
✨ New ipfs cid inspect command
New 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.
🔄 Built-in ipfs update command
Kubo 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).
🖥️ WebUI Improvements
IPFS Web UI has been updated to v4.12.0.
IPv6 peer geolocation and Peers screen optimizations
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.
🔧 Correct provider addresses for custom HTTP routing
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 +entities
Experimental 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 CID
ipfs 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.
🌳 New --fast-provide-dag flag for fine-tuned provide control
Users 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 22...
v0.41.0-rc2
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.41.md
Release status: #11082
v0.41.0-rc1
Note
This Release Preview was brought to you by the Shipyard team.
Draft release notes: docs/changelogs/v0.41.md
Release status: #11082
v0.40.1
Note
This patch release was brought to you by the Shipyard team.
This is a Windows bugfix release. If you use Linux or macOS v0.40.0 should be fine.
🚒 Bugfix for Windows
If you run Kubo on Windows, v0.40.0 can crash after running for a while. The daemon starts fine and works normally at first, but eventually hits a memory corruption in Go's network I/O layer and dies. This is likely caused by an upstream Go 1.26 regression in overlapped I/O handling that has known issues (go#77142, #11214).
This patch release downgrades the Go toolchain from 1.26 to 1.25, which does not have this bug. If you are running Kubo on Windows, upgrade to v0.40.1. We will switch back to Go 1.26.x once the upstream fix lands.
📝 Changelog
Full Changelog v0.40.1
- github.com/ipfs/kubo:
- chore: downgrade to Go 1.25 to fix Windows crash (ipfs/kubo#11215)
See v0.40.0 for full list of changes since v0.39.x.