feat(gateway): improved error page with retrieval state details (#10950)
* feat(gateway): add DiagnosticServiceURL config - add Gateway.DiagnosticServiceURL to kubo config - pass diagnostic service URL to boxo gateway - document new config option in docs/config.md - default to https://check.ipfs.network * docs(changelog): add gateway error UX improvements to v0.38 - document improved 504 error pages with retrieval diagnostics - highlight new Gateway.DiagnosticServiceURL config option - include screenshot showing enhanced error page UX
Marcin Rataj committed
Sep 8, 2025 at 19:49 UTC
906ce802bf8c619f67d9ed1242d78b1010740c09
10 files changed
+41
-12
config/gateway.go
+7
@@ -9,6 +9,7 @@ const (
9
DefaultDeserializedResponses = true
10
DefaultDisableHTMLErrors = false
11
DefaultExposeRoutingAPI = false
12
+ DefaultDiagnosticServiceURL = "https://check.ipfs.network"
13
14
// Gateway limit defaults from boxo
15
DefaultRetrievalTimeout = gateway.DefaultRetrievalTimeout
@@ -98,4 +99,10 @@ type Gateway struct {
99
// Requests beyond this limit receive 429 Too Many Requests with Retry-After header.
100
// A value of 0 disables the limit.
101
MaxConcurrentRequests *OptionalInteger `json:",omitempty"`
102
+
103
+ // DiagnosticServiceURL is the URL for a service to diagnose CID retrievability issues.
104
+ // When the gateway returns a 504 Gateway Timeout error, an "Inspect retrievability of CID"
105
+ // button will be shown that links to this service with the CID appended as ?cid=<CID-to-diagnose>.
106
+ // Set to empty string to disable the button.
107
+ DiagnosticServiceURL *OptionalString `json:",omitempty"`
108
}
core/corehttp/gateway.go
+3
@@ -107,11 +107,13 @@ func Libp2pGatewayOption() ServeOption {
107
// Keep these constraints for security
108
DeserializedResponses: false, // Trustless-only
109
NoDNSLink: true, // No DNS resolution
110
+ DisableHTMLErrors: true, // Plain text errors only
111
PublicGateways: nil,
112
Menu: nil,
113
// Apply timeout and concurrency limits from user config
114
RetrievalTimeout: cfg.Gateway.RetrievalTimeout.WithDefault(config.DefaultRetrievalTimeout),
115
MaxConcurrentRequests: int(cfg.Gateway.MaxConcurrentRequests.WithDefault(int64(config.DefaultMaxConcurrentRequests))),
116
+ DiagnosticServiceURL: "", // Not used since DisableHTMLErrors=true
117
}
118
119
handler := gateway.NewHandler(gwConfig, &offlineGatewayErrWrapper{gwimpl: backend})
@@ -270,6 +272,7 @@ func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, er
272
PublicGateways: map[string]*gateway.PublicGateway{},
273
RetrievalTimeout: cfg.Gateway.RetrievalTimeout.WithDefault(config.DefaultRetrievalTimeout),
274
MaxConcurrentRequests: int(cfg.Gateway.MaxConcurrentRequests.WithDefault(int64(config.DefaultMaxConcurrentRequests))),
275
+ DiagnosticServiceURL: cfg.Gateway.DiagnosticServiceURL.WithDefault(config.DefaultDiagnosticServiceURL),
276
}
277
278
// Add default implicit known gateways, such as subdomain gateway on localhost.
docs/changelogs/v0.38.md
+11
-1
@@ -18,6 +18,16 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
18
19
### 🔦 Highlights
20
21
+#### 🚨 Improved gateway error pages with diagnostic tools
22
+
23
+Gateway error pages now provide more actionable information during content retrieval failures. When a 504 Gateway Timeout occurs, users see detailed retrieval state information including which phase failed and a sample of providers that were attempted:
24
+
25
+> 
26
+>
27
+> - **[`Gateway.DiagnosticServiceURL`](https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewaydiagnosticserviceurl)** (default: `https://check.ipfs.network`): Configures the diagnostic service URL. When set, 504 errors show a "Check CID retrievability" button that links to this service with `?cid=<failed-cid>` for external diagnostics. Set to empty string to disable.
28
+> - **Enhanced error details**: Timeout errors now display the retrieval phase where failure occurred (e.g., "connecting to providers", "fetching data") and up to 3 peer IDs that were attempted but couldn't deliver the content, making it easier to diagnose network or provider issues.
29
+> - **Retry button on all error pages**: Every gateway error page now includes a retry button for quick page refresh without manual URL re-entry.
30
+
31
### 📦️ Important dependency updates
32
33
### 📝 Changelog
@@ -26,4 +36,4 @@ This release was brought to you by the [Shipyard](https://ipshipyard.com/) team.
36
37
</details>
38
29
-### 👨👩👧👦 Contributors
\ No newline at end of file
39
+### 👨👩👧👦 Contributors
docs/config.md
+11
@@ -69,6 +69,7 @@ config file at runtime.
69
- [`Gateway.MaxConcurrentRequests`](#gatewaymaxconcurrentrequests)
70
- [`Gateway.HTTPHeaders`](#gatewayhttpheaders)
71
- [`Gateway.RootRedirect`](#gatewayrootredirect)
72
+ - [`Gateway.DiagnosticServiceURL`](#gatewaydiagnosticserviceurl)
73
- [`Gateway.FastDirIndexThreshold`](#gatewayfastdirindexthreshold)
74
- [`Gateway.Writable`](#gatewaywritable)
75
- [`Gateway.PathPrefixes`](#gatewaypathprefixes)
@@ -1174,6 +1175,16 @@ Default: `""`
1175
1176
Type: `string` (url)
1177
1178
+### `Gateway.DiagnosticServiceURL`
1179
+
1180
+URL for a service to diagnose CID retrievability issues. When the gateway returns a 504 Gateway Timeout error, an "Inspect retrievability of CID" button will be shown that links to this service with the CID appended as `?cid=<CID-to-diagnose>`.
1181
+
1182
+Set to empty string to disable the button.
1183
+
1184
+Default: `"https://check.ipfs.network"`
1185
+
1186
+Type: `optionalstring` (url)
1187
+
1188
### `Gateway.FastDirIndexThreshold`
1189
1190
**REMOVED**: this option is [no longer necessary](https://github.com/ipfs/kubo/pull/9481). Ignored since [Kubo 0.18](https://github.com/ipfs/kubo/blob/master/docs/changelogs/v0.18.md).
docs/examples/kubo-as-a-library/go.mod
+1
-2
@@ -7,7 +7,7 @@ go 1.25
7
replace github.com/ipfs/kubo => ./../../..
8
9
require (
10
- github.com/ipfs/boxo v0.34.0
10
+ github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364
11
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
12
github.com/libp2p/go-libp2p v0.43.0
13
github.com/multiformats/go-multiaddr v0.16.1
@@ -82,7 +82,6 @@ require (
82
github.com/ipfs/go-ds-measure v0.2.2 // indirect
83
github.com/ipfs/go-ds-pebble v0.5.1 // indirect
84
github.com/ipfs/go-fs-lock v0.1.1 // indirect
85
- github.com/ipfs/go-ipfs-delay v0.0.1 // indirect
85
github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect
86
github.com/ipfs/go-ipfs-pq v0.0.3 // indirect
87
github.com/ipfs/go-ipfs-redirects-file v0.1.2 // indirect
docs/examples/kubo-as-a-library/go.sum
+2
-2
@@ -287,8 +287,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
287
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
288
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
289
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
290
-github.com/ipfs/boxo v0.34.0 h1:pMP9bAsTs4xVh8R0ZmxIWviV7kjDa60U24QrlGgHb1g=
291
-github.com/ipfs/boxo v0.34.0/go.mod h1:kzdH/ewDybtO3+M8MCVkpwnIIc/d2VISX95DFrY4vQA=
290
+github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 h1:VdRdPlosNYdlENC0UsCxapHala/Q1Me6yBY5ChKUw7s=
291
+github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
292
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
293
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
294
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
go.mod
+1
-2
@@ -22,7 +22,7 @@ require (
22
github.com/hashicorp/go-version v1.7.0
23
github.com/ipfs-shipyard/nopfs v0.0.14
24
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0
25
- github.com/ipfs/boxo v0.34.0
25
+ github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364
26
github.com/ipfs/go-block-format v0.2.2
27
github.com/ipfs/go-cid v0.5.0
28
github.com/ipfs/go-cidutil v0.1.0
@@ -146,7 +146,6 @@ require (
146
github.com/huin/goupnp v1.3.0 // indirect
147
github.com/ipfs/bbloom v0.0.4 // indirect
148
github.com/ipfs/go-bitfield v1.1.0 // indirect
149
- github.com/ipfs/go-ipfs-delay v0.0.1 // indirect
149
github.com/ipfs/go-ipfs-ds-help v1.1.1 // indirect
150
github.com/ipfs/go-ipfs-pq v0.0.3 // indirect
151
github.com/ipfs/go-ipfs-redirects-file v0.1.2 // indirect
go.sum
+2
-2
@@ -354,8 +354,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.25.0 h1:OqNqsGZPX8zh3eFMO8Lf8EHRRnSGBMqcd
354
github.com/ipfs-shipyard/nopfs/ipfs v0.25.0/go.mod h1:BxhUdtBgOXg1B+gAPEplkg/GpyTZY+kCMSfsJvvydqU=
355
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
356
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
357
-github.com/ipfs/boxo v0.34.0 h1:pMP9bAsTs4xVh8R0ZmxIWviV7kjDa60U24QrlGgHb1g=
358
-github.com/ipfs/boxo v0.34.0/go.mod h1:kzdH/ewDybtO3+M8MCVkpwnIIc/d2VISX95DFrY4vQA=
357
+github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 h1:VdRdPlosNYdlENC0UsCxapHala/Q1Me6yBY5ChKUw7s=
358
+github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
359
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
360
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
361
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
test/dependencies/go.mod
+1
-1
@@ -134,7 +134,7 @@ require (
134
github.com/huin/goupnp v1.3.0 // indirect
135
github.com/inconshreveable/mousetrap v1.1.0 // indirect
136
github.com/ipfs/bbloom v0.0.4 // indirect
137
- github.com/ipfs/boxo v0.34.0 // indirect
137
+ github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 // indirect
138
github.com/ipfs/go-bitfield v1.1.0 // indirect
139
github.com/ipfs/go-block-format v0.2.2 // indirect
140
github.com/ipfs/go-cid v0.5.0 // indirect
test/dependencies/go.sum
+2
-2
@@ -332,8 +332,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
332
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
333
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
334
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
335
-github.com/ipfs/boxo v0.34.0 h1:pMP9bAsTs4xVh8R0ZmxIWviV7kjDa60U24QrlGgHb1g=
336
-github.com/ipfs/boxo v0.34.0/go.mod h1:kzdH/ewDybtO3+M8MCVkpwnIIc/d2VISX95DFrY4vQA=
335
+github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364 h1:VdRdPlosNYdlENC0UsCxapHala/Q1Me6yBY5ChKUw7s=
336
+github.com/ipfs/boxo v0.34.1-0.20250908170437-7d2493027364/go.mod h1:rXql6ncaLZZfLqDG3Cuw9ZYQKd3rMU5bk1TGXF0+ZL0=
337
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
338
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
339
github.com/ipfs/go-block-format v0.2.2 h1:uecCTgRwDIXyZPgYspaLXoMiMmxQpSx2aq34eNc4YvQ=