Skip to content

CDN Deployment Notes ​

Updated: 10/15/26, 5:44:21 PM

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 of GET /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 ​

  1. 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
  2. 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
  3. Do not rely on "follow origin": backend dynamic endpoints do not return a Cache-Control response 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 ​

RiskMechanismConsequence
Shared one-time credentialsNonces and image captchas are stored in Redis for one-time consumption; once the response is cached, every user gets the same oneSite-wide login failure; the captcha anti-abuse mechanism is defeated — recognized once, then reused indefinitely
Polling gets stale statusOrder-status endpoints (e.g. the QR terminal's /client/device/qrcode/order-status) request the same URL at high frequency and are guaranteed cache hitsThe page keeps showing "unpaid" even after the user has paid
Cross-user data leakageCDN 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 entriesUser A sees user B's data
Stale configuration / keysThe public key (/token/public-key) or cashier configuration gets cached; after the origin updates, the CDN keeps serving the old valueLogin 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):

  1. Match condition: URL Path + matches regex, value:

    ^/(api|server|unipay|epay)(/|$)

    This covers the API reverse-proxy prefixes on each domain:

    PrefixDomainPurpose
    /api/Admin / merchant Web / payment H5Backend API reverse proxy (unified across clients)
    /unipay/Gateway API domainMerchant open APIs and channel callbacks
    /epay/Gateway API domainEasyPay-compatible APIs

    The trailing (/|$) ensures only whole path segments match; URL Path matching excludes the query string, so parameters need no handling

  2. Action: 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):

bash
curl -sD - -o /dev/null https://your-domain.com/api/token/public-key | grep -i "eo-cache-status"
  • EO-Cache-Status: MISS — healthy origin fetch
  • EO-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) ​

EndpointMethodNotes
/nonce/generatePOSTLogin anti-replay nonce; changed from GET to POST (2026-08)
/captcha/imagePOSTImage 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.