Configuring Nginx proxy_cache_use_stale with retry directives to avoid duplicate writes
0 reputation · 10 Jun 2022, 17:18 UTC
0 reputation · 10 Jun 2022, 17:18 UTC
The goal is to prevent duplicate writes when Nginx retries an upstream request while a stale response is being served via proxy_cache_use_stale.
Nginx documentation does not define how proxy_next_upstream retry settings interact with proxy_cache_use_stale, leaving uncertainty about whether retries will bypass the cache or trigger new upstream calls for unsafe HTTP methods.
How can proxy_next_upstream retry configurations be combined with proxy_cache_use_stale to guarantee exactly‑once semantics for POST, PUT, or DELETE operations? Is there a documented method to ensure that a retry does not cause a duplicate write when a stale response is cached?
35775 reputation · 11 Jun 2022, 04:10 UTC
No. Nginx has no documented combination of proxy_cache_use_stale and proxy_next_upstream that guarantees exactly-once semantics for POST, PUT, or DELETE. Serving a stale response does not cancel an in-flight or retried upstream request, and Nginx cannot know whether the upstream already performed the write. You must enforce deduplication in the application or client, not in nginx.conf.
Confirmed: proxy_next_upstream selects failure conditions for passing a request to another upstream. proxy_cache_use_stale controls when stale cached responses are served. They are orthogonal. proxy_cache_lock serializes cache population on a miss only; it does not serialize retries while a stale response is being served.
Likely explanation for duplicate writes: If the first upstream attempt reaches the application and then fails, a retry can send the same unsafe request again. For POST, enabling non_idempotent makes this worse because Nginx may retransmit after connection errors. PUT and DELETE are idempotent by HTTP convention, but idempotence is not automatic exactly-once delivery.
proxy_next_upstream non_idempotent for POST. Keep retries disabled for unsafe methods, or allow them only before any request body bytes have been sent.proxy_cache_use_stale only for cacheable GET/HEAD refresh scenarios, not as a write-deduplication mechanism for POST/PUT/DELETE.proxy_cache_methods unless you have verified Cache-Control, Authorization, and Vary headers; side-effecting responses are usually unsafe to cache.Run nginx -V to confirm the installed build and module version before relying on directive behavior. Then test against a logging upstream that echoes a client-supplied request ID and increments a write counter only on first occurrence of that ID. Repeat POST/PUT/DELETE with the same ID while forcing an upstream error after headers; the counter should stay at one and Nginx should not retransmit the body after bytes were sent.
Does your backend already validate an idempotency key or use conditional writes? If yes, configure Nginx to forward the key unchanged and treat Nginx retries as safe only if the backend deduplicates. If no, no Nginx configuration can meet the exactly-once requirement; change the application or client first.
This assumes standard open-source Nginx behavior; directive availability and defaults such as non_idempotent can vary by build and version. Treat version-specific behavior as requiring review until confirmed with nginx -V and the staging test above.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 10 Jun 2022, 17:37 UTC
The core issue is that proxy_cache_use_stale and proxy_next_upstream operate on orthogonal concerns. proxy_cache_use_stale (especially with updating) manages stale-read serving during cache refresh for cacheable responses—by default only GET/HEAD. proxy_next_upstream retries failed requests to another upstream server, but it has no visibility into whether the first upstream already mutated state.
If you explicitly add POST|PUT|DELETE to proxy_cache_methods to make use_stale apply, you violate HTTP semantics and risk serving stale responses for state-changing operations. Even then, a retry triggered by proxy_next_upstream will re-send the request body, causing a duplicate write if the original upstream processed it before failing.
Exactly-once for non-idempotent methods requires application-layer idempotency keys (e.g., Idempotency-Key header), conditional requests (If-Match), or transactional APIs. Nginx can enforce proxy_no_cache $request_method ~* "POST|PUT|DELETE" to prevent accidental caching, but it cannot deduplicate retries.