Skip to content
71 changes: 61 additions & 10 deletions src/http-gateways/path-gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-08-11
maturity: reliable
editors:
- name: Marcin Rataj
Expand Down Expand Up @@ -496,17 +496,68 @@ 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.
every immutable resource under the `/ipfs/` namespace, except generated
responses described below.
- A generated response is one whose body is markup produced by the gateway
rather than the data addressed by the CID: a UnixFS directory listing
rendered as HTML, or an HTML preview of a `dag-cbor`/`dag-json` document.
Deterministic format conversions (such as `dag-cbor` returned as
`dag-json`) are not generated responses and keep the `immutable`
directive.
- Generated `/ipfs/` responses are not byte-for-byte stable: their markup
can change between implementations and versions, so they MUST NOT be
marked `immutable`, and SHOULD be given 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. The RECOMMENDED window is
`public, max-age=604800, stale-while-revalidate=2678400` (one week, plus
a 31-day stale period). 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=<ttl>` SHOULD be returned for mutable
resources under `/ipns/{id-with-ttl}/` namespace; `max-age=<ttl>` 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 SHOULD not send a `Cache-Control`
- 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.
resources under the `/ipns/{id-with-ttl}/` namespace, where `<ttl>` is
derived from the TTL of the mutable pointer, expressed in seconds: the
:cite[ipns-record] `TTL` field (nanoseconds, converted to seconds) or the
DNSLink DNS TXT record TTL (already in seconds).
- When serving from a cached resolution, implementations SHOULD lower
`<ttl>` by the time elapsed since the resolution, so layered caches do
not extend the effective freshness beyond the pointer's 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. When the EOL is known, `max-age`
SHOULD NOT exceed the remaining validity, and the stale window SHOULD be
sized so that `max-age` plus the stale window ends at the EOL and never
crosses it; a fixed stale window can push reuse past the EOL. A response
backed by an already-expired record, or by a record whose `ValidityType`
is unrecognized and whose EOL is therefore unknown, SHOULD be served with
`Cache-Control: no-store`. For the raw record response
(`format=ipns-record`), whose body is the signed record itself, the
entire cache lifetime (`max-age` plus any stale window) SHOULD stay
within the remaining validity. 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 SHOULD 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. The EOL bound above still applies: when the record's EOL is
known, the freshness lifetime SHOULD NOT extend past it.
- Implementations SHOULD send a
[`Last-Modified`](#last-modified-response-header) header with the
timestamp of the record resolution alongside `Cache-Control`: the
explicit directive takes precedence over heuristic freshness, and the
timestamp enables inexpensive update checks via `If-Modified-Since`.
Sending `Last-Modified` without `Cache-Control` triggers heuristic
freshness; see the
[`Last-Modified`](#last-modified-response-header) section.

### `Last-Modified` (response header)

Expand Down
10 changes: 6 additions & 4 deletions src/routing/http-routing-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2026-07-29
date: 2026-08-11
maturity: reliable
editors:
- name: Marcin Rataj
Expand Down Expand Up @@ -233,9 +233,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]` converted to seconds, or default to the suggested default TTL from :cite[ipns-record] (300 seconds) when `TTL` is absent. A `TTL` of `0` maps to `max-age=0`, leaving the whole remaining validity as the stale window. Either way, `max-age` MUST NOT exceed 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.
- Servers SHOULD 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.
Expand Down
Loading