Web Hosting Guidelines
Introduction
Our web solution is available for self-hosting. The following guidelines outline how to set up a secure and scalable hosting solution.
Server Requirements
To host the application, a performant server capable of running a Docker container with a Node.js web server is necessary. This server should be tested, monitored, and adjusted based on site traffic.
CPU:
- Minimum: 2 cores
- Recommended: 4 cores or more (for improved performance and scalability)
RAM:
- Minimum: 4 GB
- Recommended: 8 GB or more (depending on load and concurrent users)
Server specifications should reflect expected traffic and allow for auto-scaling if needed. Although a CDN will handle most of the traffic based on customer-configured cache headers (in Zapp), certain requests will be managed directly by the origin server, such as:
- User login status and actions (e.g., login, logout)
- Entitlement checks
- Player page requests (excluding streaming, which communicates directly with the streaming provider)
- Favorites management
- "Continue watching" status
- User preference updates
- Payment and billing operations
For monitoring, we recommend providing Applicaster with read-only access to server logs.
CDN Requirements
To ensure optimized content delivery, configure the CDN as follows:
Caching Model Overview (CDN, Redis, Browser, and Caching Plugin)
The web app uses multiple caching layers, each with a different role:
- CDN cache: Primary layer for HTML/documents and static assets. This is where most traffic reduction happens.
- Redis cache: Origin-side cache for configuration/feed data and user JWT storage.
- Browser cache: Client-side reuse of assets and previously fetched resources.
- Caching plugin (Feed Server Abort Timeout): First-load resilience control during SSR. It prevents slow feed requests from blocking initial page render, but it is not a cache layer and does not replace CDN cache-key design.
In practice:
- Correct CDN cache key design determines cache hit ratio and origin load.
- Redis improves origin response times when requests do reach origin.
For timeout setup and ranges, see Web App Performance Optimization.
User Country Code Changes: Invalidate the cache if the user's country code changes to serve region-appropriate content. The app supports
CloudFront-Viewer-Country(CloudFront) andx-vercel-ip-country(Vercel).User Time Zone Changes: Invalidate the cache if the user's time zone changes to ensure time-sensitive content is rendered correctly. The app supports
CloudFront-Viewer-Time-Zone(CloudFront) andx-vercel-ip-timezone(Vercel).Required Header Forwarding: When using CloudFront CDN, the CDN must forward the
CloudFront-Viewer-CountryandCloudFront-Viewer-Time-Zoneheaders to the origin server. These headers are essential for proper content regionalization and timezone-aware behavior.User-Agent Header Forwarding for DRM: For the non-cacheable
/playerroute, the CDN must forward theUser-Agentheader to the origin. This is required for DRM-protected playback flows to behave correctly across different browsers.URL Query Parameter Changes: Any URL query parameter changes should break the cache, including those for asset URLs (e.g., JS and CSS files), to ensure the correct content version is delivered.
Cache Control Headers: The CDN must respect cache-control headers from the origin server.
HTTP Methods: Ensure GET, POST, and OPTIONS methods are permitted.
Custom Cache Invalidation: Specific customer needs may require unique caching rules. In these cases, an appendix with custom settings will be added to this document.
Cookie Forwarding Policy (Critical):
- For cacheable page/document routes, configure cookie behavior as none (preferred) or blacklist mode to exclude auth/session/third-party cookies.
- Do not forward all cookies on cacheable routes.
- If user-type cache variation is required, make a single explicit exception for
caching-group-cookieonly.
Routes That Must Not Be Cached by CDN: Configure your CDN to respect
Cache-Controlheaders, or explicitly bypass the cache for the following routes:Authentication and session management:
/api/is-logged-in/api/logout/api/oauth/login/login/token/logout/oauth/users/sign_in/users/login/users/password/edit
Payment & Billing Routes:
/payment/billing/api/payment-auth/api/stripe-create-payment-session/api/stripe-billing-portal/successful-payment/paypal-purchase
User-Specific Data Routes:
/api/favorites/api/favorite-action/api/continue-watching/api/client-feed/api/preference-editor/api/maybe-redirect
Content delivery (Video player):
/player
Parental controls and content restrictions:
/lock/parent-lock
Cache Variation by User Type (caching-group-cookie)
To serve the correct content by user entitlement level (for example: anonymous, logged-in, subscribed), configure CDN cache variation by a dedicated cookie named caching-group-cookie.
When this feature is enabled
This behavior is enabled only when CACHING_GROUP_COOKIE is configured in the app environment.
- If
CACHING_GROUP_COOKIEis set: the app writes or clearscaching-group-cookiebased on an access-token claim. - If
CACHING_GROUP_COOKIEis not set: no caching-group cookie is produced, and CDN must not depend on it.
Cookie value source and lifecycle
- The cookie value is taken from the access-token claim whose key is configured by
CACHING_GROUP_COOKIE. - Claim values can be strings, numbers, or booleans and are serialized as strings.
- If the token is missing/invalid, or the claim is missing/invalid, the app sends
Set-Cookieto clear the cookie (Max-Age=0,Expires=Thu, 01 Jan 1970 00:00:00 GMT) using the matching cookie attributes (such asPath/Domain) required by the browser. - The cookie is updated on authentication-related flows (login/auth checks) and cleared on logout.
CDN configuration requirements
For cacheable page/document responses:
- Include only
caching-group-cookiein cookie-based cache variation. - Never include all cookies (the full
Cookieheader) in the cache key, as this creates highly fragmented per-user cache entries and effectively disables CDN caching. - Do not vary cache by session cookie, JWT cookie, or other per-user cookies.
- Continue varying by URL path and query string as usual.
- Continue varying by country header when regionalization is required.
Use cases: when to vary by caching-group-cookie (and when not to)
Use caching-group-cookie only when the page/document content itself changes by entitlement group.
Use variation by caching-group-cookie:
- Anonymous users see locked rails/cards, while logged-in or subscribed users see unlocked content.
- Page structure or component visibility changes by entitlement tier (for example, premium shelves shown only to subscribers).
- Server-rendered metadata/content differs by user type in a way that must be correct on first page response.
Do not use variation by caching-group-cookie:
- The page shell is identical for all users, and differences are loaded from client-side API calls.
- Personal counters or badges are user-specific but not part of cacheable SSR document output.
- Personal widgets are fetched after page load from non-cacheable endpoints.
Personalized content boundaries (Continue Watching, Favorites, and similar)
Features such as Continue Watching, Favorites, and other user-personalized data should not define the CDN cache key for cacheable pages.
- Keep these features on dedicated non-cacheable/user endpoints (already listed above, for example
/api/continue-watching,/api/favorites,/api/favorite-action). - Respect origin
Cache-Controlfor these endpoints and bypass CDN caching when needed. - Render personalized blocks client-side (or hydrate after first paint) so the cacheable page/document can stay shared.
- Do not add session/JWT/personal cookies to cache-key variation just to support these features.
Rule of thumb:
- If the document-level SSR output is different by entitlement group, use
caching-group-cookie. - If only user-personalized data changes (continue watching/favorites/profile state), keep it out of cacheable document variation.
zp2 Cookies and Request Flow
In a typical zp2 web deployment, authentication/session-related cookies are user-specific and high-cardinality by nature. These cookies are required for authentication and account operations, but they must not participate in cache keys for cacheable document routes.
Recommended flow:
- A user visits a page. CDN serves from cache if the cache key matches (path/query/country and, if enabled,
caching-group-cookie). - On login/auth-check routes, the request reaches origin, auth is validated, and origin returns
Set-Cookieheaders for session/auth state updates. - CDN forwards
Set-Cookieto the browser for these non-cacheable routes. - Subsequent cacheable page requests should still avoid per-user cookie variation; only
caching-group-cookiemay be used when enabled.
What to configure in CDN:
- Cacheable routes:
- Cookie handling: none (preferred), or blacklist all auth/session/third-party cookies.
- Cache-key cookie allowlist (only if
CACHING_GROUP_COOKIEis enabled):caching-group-cookie.
- Non-cacheable auth/user routes:
- Forward required cookies and forward
Set-Cookiefrom origin. - Respect
no-store, no-cache, must-revalidate, privateorigin directives.
- Forward required cookies and forward
This separation prevents cache fragmentation, avoids cross-user cache pollution, and keeps auth flows correct.
For non-cacheable/authentication routes:
- Respect origin
Cache-Controlheaders (especiallyno-store, no-cache, must-revalidate, private). - Forward
Set-Cookieheaders from origin so cookie writes/clears reach the browser.
Security
- SSL/TLS Support: The CDN must support SSL/TLS to ensure secure data transmission.
- DDoS Protection: The CDN should offer Distributed Denial of Service (DDoS) protection to maintain uninterrupted service.
Redis Configuration
For a high-traffic site, we recommend the following Redis configuration, which can be adjusted as necessary:
- CPU: 4 vCPUs
- Memory: 13 GiB (depends on the number of users and JWT size; by default, we store unique user keys for 1 month)
- Network Performance: Optimized for high throughput
Redis will be used for:
- Caching: Caches Zapp configuration JSONs and content feeds.
- User Data: Stores users’ JWTs. Note: Clearing Redis will log out all users.
We recommend using a SaaS provider for Redis to minimize maintenance and scaling issues. Ensure the Redis instance is in the same region as the server origin for optimal performance.
We support both TLS (Transport Layer Security) and non-TLS connections. To enable TLS, add REDIS_TLS=true as an environment variable in your deployment.
For high availability and scalability, consider using Redis Cluster or a managed Redis service that offers clustering capabilities. To enable Redis Cluster support, set REDIS_USE_CLUSTER=true as an environment variable in your deployment.
Authentication (OAuth2 Client Secret)
If your app uses an OAuth2/OIDC confidential (private) client that requires a client secret, set the secret as an environment variable in your deployment rather than in the plugin/layout configuration, so it stays server-side:
Set
OAUTH2_CLIENT_SECRET_OVERRIDE=<your client secret>as an environment variable in your deployment. This value overrides the plugin's configured secret and is used for both the authorization-code exchange and token-refresh requests.
When using a client secret, disable "Use basic authentication header" in the OAuth2 Login plugin so the secret is sent in the request body, unless your authorization server specifically requires HTTP Basic authentication.
This is only required for confidential clients. Public web clients authenticate with PKCE and need no secret. For full provider/plugin setup, see the OAuth2 / OIDC integration guide.
Staging Environment
We recommend setting up a staging environment mirroring the production environment. This provides a secure space for testing changes and evaluating performance before production deployment.
To ensure that SEO bots don’t track the staging environment, set NO_INDEX=true as an environment variable in your staging deployment.