Skip to content

Commit be4ff12

Browse files
committed
docs(deployment): drop immutable from nginx /uploads/ cache header
The nginx snippet in docs/deployment.md recommended `Cache-Control: public, immutable` for `/uploads/`, which is wrong for iManager's FileStorage layout. FileStorage writes to `var/uploads/<itemId>/<fieldId>/<filename>` keeping the user's original filename. Re-uploading a file under the same name leaves the URL stable while the bytes change. With `immutable` set, browsers serve the stale bytes for up to 30 days (the `expires` window) and skip revalidation entirely. Switch to `Cache-Control: public, must-revalidate` and add an inline comment explaining the choice. The 30-day expires window is kept; browsers now send `If-None-Match` on every hit and nginx's built-in ETag answers 304 cheaply when the file is unchanged, 200 with the new bytes when it changed. The Caddy snippet in the same section was already correct (no explicit Cache-Control, default revalidation), so it stays untouched. CHANGELOG entry under Unreleased / Documentation. No code or behavior change. This mirrors the same fix applied to Scriptor's docker/nginx.conf in scriptor#fix/uploads-cache-immutable (the bug surfaced there when re-uploading the same-named image kept showing the old one).
1 parent 05b51a0 commit be4ff12

2 files changed

Lines changed: 27 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Documentation
11+
12+
- **`docs/deployment.md`: nginx `/uploads/` block now recommends
13+
`Cache-Control: public, must-revalidate` instead of
14+
`public, immutable`.** iManager's FileStorage writes uploads to
15+
`var/uploads/<itemId>/<fieldId>/<original-filename>`, so re-
16+
uploading a file under the same name keeps the URL stable while
17+
the bytes change. `immutable` would let browsers serve the old
18+
bytes for up to 30 days; `must-revalidate` keeps the long
19+
`expires 30d` but forces an If-None-Match check, which nginx's
20+
built-in ETag answers cheaply with 304 when the file is
21+
unchanged. Inline comment in the snippet explains the choice.
22+
Caddy snippet was already correct (no explicit Cache-Control,
23+
default revalidation). No code or behavior change.
24+
825
## [2.2.1] — 2026-05-17
926

1027
Hotfix for the 2.2.0 upgrade path: `fts:rebuild` and `optimize` now

docs/deployment.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,11 +153,20 @@ server {
153153
index index.php;
154154
155155
# Public uploads, served by nginx, no PHP.
156+
# `must-revalidate` (not `immutable`): iManager's FileStorage
157+
# writes to `var/uploads/<itemId>/<fieldId>/<original-filename>`,
158+
# so re-uploading a file under the same name leaves the URL
159+
# stable while the bytes change. `must-revalidate` makes the
160+
# browser send an If-None-Match for every hit; nginx's built-in
161+
# ETag answers 304 when the file on disk is unchanged (cheap,
162+
# no transfer) or 200 with the new bytes when it changed.
163+
# `immutable` would skip that revalidation and serve a stale
164+
# asset for up to 30 days.
156165
location /uploads/ {
157166
alias /srv/app/var/uploads/;
158167
try_files $uri =404;
159168
expires 30d;
160-
add_header Cache-Control "public, immutable";
169+
add_header Cache-Control "public, must-revalidate";
161170
}
162171
163172
# Defence-in-depth: never serve data/ or cache/.

0 commit comments

Comments
 (0)