CDN Deployment Notes
To speed up access, many deployments put a CDN in front of the site (Tencent EdgeOne, Cloudflare, self-hosted nginx caching, etc.). DaxPay's backend APIs are dynamic JSON endpoints, and some of them return one-time credentials (anti-replay nonces, image captchas). Once a CDN caches them, you get site-wide login failures, invalid captchas, stale order statuses, and other serious problems.
Real incident: in 2026-08, the demo site
admin.open.daxpay.cn— fronted by Tencent EdgeOne — cached the response ofGET /api/nonce/generate, so every user received the same nonce and nobody on the site could log in. Within the 5-minute cache window the error was "request verification expired"; past 5 minutes it became "request timestamp expired".
Core Principles
- Never cache API paths: the backend forwarding paths (commonly
/api/*) and the/unipay/*,/client/*paths on the gateway API domain must be explicitly configured as no-cache in the CDN - Long-cache static assets only: frontend build artifacts (js/css/images/fonts under
assets/*) and purely static content such as doc sites are what CDN caching is for - Do not rely on "follow origin": backend dynamic endpoints do not return a
Cache-Controlresponse header by default. Each CDN vendor behaves differently when the origin sends no header (some cache 200 responses with their own default TTL), so you must configure explicit no-cache rules — do not count on the default behavior
Why Dynamic APIs Must Not Be Cached
| Risk | Mechanism | Consequence |
|---|---|---|
| Shared one-time credentials | Nonces and image captchas are stored in Redis for one-time consumption; once the response is cached, every user gets the same one | Site-wide login failure; the captcha anti-abuse mechanism is defeated — recognized once, then reused indefinitely |
| Polling gets stale status | Order-status endpoints (e.g. the QR terminal's /client/device/qrcode/order-status) request the same URL at high frequency and are guaranteed cache hits | The page keeps showing "unpaid" even after the user has paid |
| Cross-user data leakage | CDN cache keys usually contain only the URL, not request headers; 200 responses of different users (different tokens) at the same URL can hit each other's entries | User A sees user B's data |
| Stale configuration / keys | The public key (/token/public-key) or cashier configuration gets cached; after the origin updates, the CDN keeps serving the old value | Login decryption fails after a key rotation, and the cause is hard to trace |
Tencent EdgeOne Configuration
Create a rule in the Rule Engine (rules take priority over the default cache behavior):
Match condition:
URL Path+ matches regex, value:^/(api|server|unipay|epay)(/|$)This covers the API reverse-proxy prefixes on each domain:
Prefix Domain Purpose /api/Admin / merchant Web / payment H5 Backend API reverse proxy (unified across clients) /unipay/Gateway API domain Merchant open APIs and channel callbacks /epay/Gateway API domain EasyPay-compatible APIs The trailing
(/|$)ensures only whole path segments match; URL Path matching excludes the query string, so parameters need no handlingAction: Node Cache TTL = no cache (recommended: set browser caching to no cache as well)
The host condition is not needed — without a host filter, the rule applies uniformly to every proxied domain of the site, covering the admin, merchant, H5, and gateway API domains with a single rule; only a purely static host is worth scoping separately.
Frontend static assets (js/css/images under assets/) do not match this regex, keep flowing through CDN caching, and retain the speedup.
Cloudflare Configuration
Create a rule in Cache Rules:
- Match: URI Path starts with
/api/ - Cache eligibility: Bypass cache
For self-hosted nginx reverse-proxy caching, do not configure proxy_cache for API paths, or exclude them with proxy_cache_bypass / proxy_no_cache.
Verifying the Configuration
Use curl to watch the cache status header — take the public-key endpoint, which is still a GET (request it several times and compare):
curl -sD - -o /dev/null https://your-domain.com/api/token/public-key | grep -i "eo-cache-status"EO-Cache-Status: MISS— healthy origin fetchEO-Cache-Status: HIT— cached, the API is broken
Different CDNs use different status headers: Cloudflare uses CF-Cache-Status; self-hosted nginx commonly uses X-Cache. A more direct check: request the same GET endpoint twice in a row — identical response bodies mean it is cached (this method does not apply to one-time-credential endpoints that have switched to POST; verify with GET endpoints such as the public key or configuration queries).
SSE Long-Lived Connections
When the CDN edge cuts the notification SSE stream on a timeout, the client disconnects and reconnects in a loop (the browser reports ERR_HTTP2_PROTOCOL_ERROR 200). This is not a caching issue — see Notification (SSE) Disconnects for the fix and self-check.
Backend One-time-Credential Endpoints (Development Convention)
| Endpoint | Method | Notes |
|---|---|---|
/nonce/generate | POST | Login anti-replay nonce; changed from GET to POST (2026-08) |
/captcha/image | POST | Image captcha; changed from GET to POST (2026-08) |
Convention for new endpoints: if an unauthenticated GET endpoint returns one-time credentials, random values, or user-sensitive data, either switch it to POST or explicitly set a Cache-Control: no-store response header — do not rely on deployments always remembering to configure CDN rules. Idempotent GET query endpoints that stay GET (public key, configuration, order-status polling, etc.) rely on the CDN no-cache rules as the safety net.