From 5e84225535a733ece03a48281fd96b360436a2a2 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 29 Jul 2024 20:43:16 +0200 Subject: [PATCH 1/5] fix: clarify cache-control of generated html --- src/http-gateways/path-gateway.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/http-gateways/path-gateway.md b/src/http-gateways/path-gateway.md index 51d75f09a..d97569d4e 100644 --- a/src/http-gateways/path-gateway.md +++ b/src/http-gateways/path-gateway.md @@ -426,8 +426,12 @@ Returned directive depends on requested content path and format: - `Cache-Control: public, max-age=29030400, immutable` MUST be returned for every immutable resource under `/ipfs/` namespace. + - Generated `/ipfs/` responses such as UnixFS directory listings are not + byte-for-byte immutable and SHOULD have a meaningful expiration that allows + for generated responses to be updated over time. Suggested value is one week: + `public, max-age=604800, stale-while-revalidate=2678400`. -- `Cache-Control: public, max-age=` SHOULD be returned for mutable +- `Cache-Control: public, max-age=, stale-while-revalidate=2678400` SHOULD be returned for mutable resources under `/ipns/{id-with-ttl}/` namespace; `max-age=` SHOULD indicate remaining TTL of the mutable pointer such as :cite[ipns-record] or DNSLink TXT record. From d3cdb717b0301a2a09f06c03e6e3ab149b2acae8 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 29 Jul 2024 21:15:43 +0200 Subject: [PATCH 2/5] chore(gw): allow best-effort cache-control on missing ttl --- src/http-gateways/path-gateway.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/http-gateways/path-gateway.md b/src/http-gateways/path-gateway.md index d97569d4e..6db247c65 100644 --- a/src/http-gateways/path-gateway.md +++ b/src/http-gateways/path-gateway.md @@ -437,7 +437,8 @@ Returned directive depends on requested content path and format: TXT record. - Implementations MAY place an upper bound on any TTL received, as noted in Section 8 of :cite[rfc2181]. - - If TTL value is unknown, implementations SHOULD not send a `Cache-Control` + - If TTL value is unknown, implementations MAY send a best-effort `Cache-Control` + informing caches and CDNs how long a stale response is acceptable. - No matter if TTL value is known or not, implementations SHOULD always send a [`Last-Modified`](#last-modified-response-header) header with the timestamp of the record resolution. From 7d1e29da7e66b3b2ceac685259956ae37995a8ff Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Thu, 21 Nov 2024 19:40:51 +0100 Subject: [PATCH 3/5] chore: clarify week is floor, not ceiling suggestion --- src/http-gateways/path-gateway.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http-gateways/path-gateway.md b/src/http-gateways/path-gateway.md index 6db247c65..440b75284 100644 --- a/src/http-gateways/path-gateway.md +++ b/src/http-gateways/path-gateway.md @@ -428,7 +428,7 @@ Returned directive depends on requested content path and format: every immutable resource under `/ipfs/` namespace. - Generated `/ipfs/` responses such as UnixFS directory listings are not byte-for-byte immutable and SHOULD have a meaningful expiration that allows - for generated responses to be updated over time. Suggested value is one week: + for generated responses to be updated over time. Suggested value is at least one week: `public, max-age=604800, stale-while-revalidate=2678400`. - `Cache-Control: public, max-age=, stale-while-revalidate=2678400` SHOULD be returned for mutable From 6f8078b5f0a9bb4986f0fa8fd0a69aa6515a4e14 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 15 Jun 2026 18:54:05 +0200 Subject: [PATCH 4/5] fix: bound ipns cache-control to record eol A fixed stale-while-revalidate window let caches reuse an IPNS record past its Validity (EOL). After EOL the signature is expired, so validating clients reject the record, which surfaced as sporadic 500s in the service-worker gateway. Mirror the bound applied in ipfs/boxo#1166: max-age plus the stale window must end before the EOL, and an already-expired record must not be cached. - path-gateway: keep a fixed stale window for generated /ipfs/ HTML (content-addressed, no EOL), but cap the mutable /ipns/ window to the record's remaining validity; no-store past EOL - http-routing-v1: size the ipns record stale window as remaining validity minus max-age so it ends at the EOL; no-store when the record is expired or its ValidityType is unrecognized --- src/http-gateways/path-gateway.md | 48 +++++++++++++++++++++---------- src/routing/http-routing-v1.md | 10 ++++--- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/http-gateways/path-gateway.md b/src/http-gateways/path-gateway.md index 5f33c4870..638e7071e 100644 --- a/src/http-gateways/path-gateway.md +++ b/src/http-gateways/path-gateway.md @@ -4,7 +4,7 @@ description: > The comprehensive low-level HTTP Gateway enables the integration of IPFS resources into the HTTP stack through /ipfs and /ipns namespaces, supporting both deserialized and verifiable response types. -date: 2026-03-05 +date: 2026-06-15 maturity: reliable editors: - name: Marcin Rataj @@ -496,22 +496,40 @@ in caches. Returned directive depends on requested content path and format: - `Cache-Control: public, max-age=29030400, immutable` MUST be returned for - every immutable resource under `/ipfs/` namespace. - - Generated `/ipfs/` responses such as UnixFS directory listings are not - byte-for-byte immutable and SHOULD have a meaningful expiration that allows - for generated responses to be updated over time. Suggested value is at least one week: + every immutable resource under the `/ipfs/` namespace. + - Generated `/ipfs/` responses, such as UnixFS directory listings, are not + byte-for-byte stable: their markup can change between implementations and + versions, so they MUST NOT be marked `immutable`. Give them an expiration + that lets caches pick up regenerated markup. The underlying data is + content-addressed and never expires, so a stale response can only show + outdated markup, never invalid data, and a fixed stale window is safe. A + floor of one week works well: `public, max-age=604800, stale-while-revalidate=2678400`. -- `Cache-Control: public, max-age=, stale-while-revalidate=2678400` SHOULD be returned for mutable - resources under `/ipns/{id-with-ttl}/` namespace; `max-age=` SHOULD - indicate remaining TTL of the mutable pointer such as :cite[ipns-record] or DNSLink - TXT record. - - Implementations MAY place an upper bound on any TTL received, as - noted in Section 8 of :cite[rfc2181]. - - If TTL value is unknown, implementations MAY send a best-effort `Cache-Control` - informing caches and CDNs how long a stale response is acceptable. - - No matter if TTL value is known or not, implementations SHOULD always - send a [`Last-Modified`](#last-modified-response-header) header with the timestamp of the record resolution. +- `Cache-Control: public, max-age=` SHOULD be returned for mutable + resources under the `/ipns/{id-with-ttl}/` namespace, where `` is the + remaining TTL of the mutable pointer: the :cite[ipns-record] `TTL` field or + the DNSLink TXT record TTL. + - Implementations MAY place an upper bound on any TTL received, as noted in + Section 8 of :cite[rfc2181]. + - A `stale-while-revalidate` or `stale-if-error` window MAY be added to + improve CDN and offline behavior, but it MUST respect the pointer's + expiration. An :cite[ipns-record] with `ValidityType=0` is cryptographically + valid only until its `Validity` (EOL) timestamp, and a validating client + rejects it once that timestamp passes. Cap `max-age` to the remaining + validity, and size the stale window so that `max-age` plus the stale window + still ends before the EOL; a fixed stale window is unsafe because it can + push reuse past the EOL. Return `Cache-Control: no-store` for a response + backed by an already-expired record. This bound is mandatory for the raw + record response (`format=ipns-record`), whose body is the signed record + itself. A DNSLink pointer carries no signature EOL and MAY use a bounded + best-effort stale window. + - If the TTL is unknown, implementations MAY send a best-effort + `Cache-Control` telling caches and CDNs how long a stale response is + acceptable. + - Whether or not the TTL is known, implementations SHOULD always send a + [`Last-Modified`](#last-modified-response-header) header with the timestamp + of the record resolution. ### `Last-Modified` (response header) diff --git a/src/routing/http-routing-v1.md b/src/routing/http-routing-v1.md index 038adf0cf..f408fc00c 100644 --- a/src/routing/http-routing-v1.md +++ b/src/routing/http-routing-v1.md @@ -4,7 +4,7 @@ description: > Delegated routing is a mechanism for IPFS implementations to use for offloading content routing, peer routing and naming to another process/server. This specification describes an HTTP API for delegated routing of content, peers, and IPNS. -date: 2025-12-17 +date: 2026-06-15 maturity: reliable editors: - name: Marcin Rataj @@ -232,9 +232,11 @@ Each object in the `Peers` list is a record conforming to the [Peer Schema](#pee #### Response Headers - `Etag`: a globally unique opaque string used for HTTP caching. MUST be derived from the protobuf record returned in the body. -- `Cache-Control: public, max-age={ttl}, public, stale-while-revalidate={sig-ttl}, stale-if-error={sig-ttl}`: meaningful cache TTL returned with :ref[IPNS Record] - - The `max-age` value in seconds SHOULD match duration from `IpnsEntry.data[TTL]`, if present and bigger than `0`. Otherwise, implementation SHOULD default to `max-age=60`. - - Implementations SHOULD include `sig-ttl`, set to the remaining number of seconds the returned IPNS Record is valid. +- `Cache-Control: public, max-age={ttl}, stale-while-revalidate={stale}, stale-if-error={stale}`: cache lifetime for the returned :ref[IPNS Record]. + - An IPNS Record with `ValidityType=0` is valid only until its EOL, the `Validity` timestamp. A cache that reuses the record past the EOL serves a record that fails validation, so the entire cache lifetime MUST stay within the remaining validity. + - `{ttl}` (the `max-age`) SHOULD match `IpnsEntry.data[TTL]` in seconds, or default to `60` when `TTL` is absent or `0`. Either way, cap it to the remaining validity. + - `{stale}` SHOULD be the remaining validity minus `max-age`, so `max-age` plus the stale window ends at the EOL and never crosses it. + - Return `Cache-Control: no-store` instead when the record is already expired, or when its `ValidityType` is unrecognized and the EOL is therefore unknown. - `Expires:`: an HTTP-date timestamp ([RFC9110, Section 5.6.7](https://www.rfc-editor.org/rfc/rfc9110#section-5.6.7)) when the validity of IPNS Record expires (if `ValidityType=0`, when signature expires) - `Last-Modified`: an HTTP-date timestamp of when cacheable resolution occurred: allows HTTP proxies and CDNs to support inexpensive update checks via `If-Modified-Since` - `Vary: Accept`: allows intermediate caches to play nicely with the different possible content types. From bfc14e155d371bd62a9963034f658ee45816c1c0 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 15 Jun 2026 19:02:47 +0200 Subject: [PATCH 5/5] fix: scope generated-html cache window to /ipfs/ A directory listing reached through /ipns/ is a mutable response even though its body is generated markup. State that it follows the /ipns/ TTL and EOL rules, not the fixed window used for generated /ipfs/ responses, so a cache cannot serve it past the record's EOL. --- src/http-gateways/path-gateway.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/http-gateways/path-gateway.md b/src/http-gateways/path-gateway.md index 638e7071e..7018d7ac9 100644 --- a/src/http-gateways/path-gateway.md +++ b/src/http-gateways/path-gateway.md @@ -504,7 +504,9 @@ Returned directive depends on requested content path and format: content-addressed and never expires, so a stale response can only show outdated markup, never invalid data, and a fixed stale window is safe. A floor of one week works well: - `public, max-age=604800, stale-while-revalidate=2678400`. + `public, max-age=604800, stale-while-revalidate=2678400`. This fixed window + applies only under the immutable `/ipfs/` namespace. The same listing + reached through `/ipns/` is mutable and follows the rules below. - `Cache-Control: public, max-age=` SHOULD be returned for mutable resources under the `/ipns/{id-with-ttl}/` namespace, where `` is the @@ -524,6 +526,12 @@ Returned directive depends on requested content path and format: record response (`format=ipns-record`), whose body is the signed record itself. A DNSLink pointer carries no signature EOL and MAY use a bounded best-effort stale window. + - A generated response reached through `/ipns/`, such as a directory listing, + is mutable even though its body is regenerated markup. Apply the `/ipns/` + `max-age` and stale-window rules above, not the fixed window for generated + `/ipfs/` responses: the pointer it renders can move and, for an + :cite[ipns-record], expire, so the response MUST NOT be cached past the + record's EOL. - If the TTL is unknown, implementations MAY send a best-effort `Cache-Control` telling caches and CDNs how long a stale response is acceptable.