Implementing a CloudFront Failover Architecture with S3 and ALB Origins
Setting up a high-availability website requires an architecture designed for maximum uptime.
A highly effective pattern for this involves configuring an Amazon CloudFront distribution with an Origin Group designed for failover between two distinct origins: an Amazon S3 bucket (serving as a static backup or primary cache) and an Application Load Balancer (ALB) (routing traffic to your active application server).
Table of Contents
Understanding the problem
While this dual-origin setup is ideal in theory, standard configurations quickly break down because Amazon S3 and an ALB have fundamentally conflicting requirements for handling the Host header:
- The ALB Origin: Requires the original
Hostheader to properly route requests to your backend application. Without it, CloudFront fails to establish a secure connection with the load balancer, resulting in a 502 Bad Gateway error. - The S3 Origin: Cannot accept a custom application
Hostheader. When S3 receives an incoming request containing your application’s domain instead of its own bucket domain, it rejects the request with a 403 Forbidden error.
Because CloudFront behaviors apply a single Origin Request Policy to the entire Origin Group, finding a configuration that simultaneously satisfies both origins is challenging.
Production-ready failover pipeline
Here is our comprehensive, step-by-step guide to implementing a production-ready failover pipeline that gracefully resolves conflict:
- DNS & Certificate Infrastructure: Setting up the foundational DNS routing and SSL/TLS certificates for the ALB origin.
- CloudFront Routing & Policies: Configuring the core Origin Group and the exact Origin Request Policies required to manage headers safely.
- Edge Compute Customization: Implementing a CloudFront Function to dynamically rewrite requests and verify S3 asset paths.
- Application-Side Routing: Configuring an ALB routing rule to gracefully restore the original URI structure on the application side.
- Application-Level Integration (PHP Setup): PHP snippet to dynamically restore the original host name and HTTPS protocols within the global context.
DNS & Certificate Infrastructure
Because S3 and the ALB cannot share the same Host header behavior within the same CloudFront origin group, we must decouple the user-facing domain from the direct ALB origin domain.
- User-Facing Domain: This is the domain your users visit (e.g.,
example.comorwww.example.com). This domain will point directly to your CloudFront Distribution via an ALIAS or CNAME record. - Origin-Specific Domain: Create a unique, subdomain dedicated exclusively to the ALB origin (e.g.,
alb.example.com) and use this specific domain as your ALB origin instead of the default ALB DNS.
Why drop the default ALB DNS? CloudFront uses the origin domain name to validate the SSL/TLS handshake with the backend.
Using alb.example.com instead of the AWS-generated DNS string guarantees proper SSL verification and leaves you in full control of the routing pipeline.
CloudFront Routing & Policies
With your DNS and certificate infrastructure securely in place, the next phase is configuring Amazon CloudFront to manage traffic distribution.
To handle the header conflict between S3 and the ALB, we will construct a specific routing strategy using two distinct cache behaviors, an Origin Group, and a optimized custom Origin Request Policy.
Creating the Origins and the Origin Group
Before configuring behaviors, you must define your individual origins that we will baptize S3-ALB-Failover-Group and bundle them into a failover group.
- Open the CloudFront Console and navigate to your distribution.
- Under the Origins tab, ensure you have created both individual origins:
- S3 Origin: Pointed to your Amazon S3 bucket domain.
- ALB Origin: Pointed to your custom origin domain (
alb.example.com).
- Click Create origin group.
- Select both your S3 origin and ALB origin. Set the S3 origin as Primary and the ALB origin as Secondary.
- Configure the Failover criteria by checking 404 and 403
Creating the Custom Origin Request Policy
Because the Cache (/*/) behavior routes to the entire origin group, we cannot pass the standard Host header (which breaks S3) nor can we block all headers (which breaks the ALB).
We must build a custom request policy that passes critical application data while keeping the Host header neutral.
- In the left navigation pane of the CloudFront console, go to Policies and select the Origin request tab.
- Click Create origin request policy and name it
S3-ALB-Failover-Shared-Policy. - Configure the policy settings as follows:
- Headers: Select Include the following headers and add these specific entries:
OriginAccept-CharsetAcceptAccess-Control-Request-MethodAccess-Control-Request-HeadersRefererUser-AgentX-Forwarded-HostAccept-Language
- Cookies: Select All
- Query strings: Select All
How this fixes the conflict: By omitting the standard Host header from this list, CloudFront defaults to forwarding the specific origin’s domain name to the target backend.
When routing to S3, S3 receives its own bucket name. When routing to the ALB, the ALB receives alb.example.com.
Setting Up the Dual-Behavior Routing
We are now going to segment the traffic into two behaviors.
General app traffic routes directly to the ALB, while explicit sub-paths and structural assets leverage the multi-origin failover group.
Go to the Behaviors tab of your distribution and set up the following order:
| Path Pattern | Target Origin | Origin Request Policy | Cache Policy |
/*/ |
S3-ALB-Failover-Group |
S3-ALB-Failover-Shared-Policy (Custom) |
CachingOptimized |
Default (*) |
ALB Origin (Direct) |
AllViewer (AWS Managed) |
CachingDisabled / Optimized |
Edge Compute Customization
Because we omitted the standard Host header from our custom Origin Request Policy to satisfy Amazon S3, the ALB no longer naturally receives the original domain name requested by the user.
To solve this, we must inject the original domain into a custom header at the edge before the request reaches the origins.
Furthermore, in our multi-tenant environment where a single S3 bucket serves static fallback for multiple domains, CloudFront must look for assets in a domain-specific folder structure.
We will handle both of these requirements using a high-performance CloudFront Function attached to the Viewer Request event.
The Multi-Tenant CloudFront Function
This function intercepts incoming viewer requests and performs two key tasks:
- Preserves the Host Domain: It maps the original user-facing domain name (the
Hostheader) into a customX-Forwarded-Hostheader so the ALB can eventually read it. - Rewrites Multi-Tenant S3 Paths Safely: If the user is an unauthenticated guest, it rewrites the request URI to target a specific domain subfolder inside your S3 bucket (e.g.,
/example.com/slug/index.html). If the user is logged in or explicitly bypassing the cache, the path remains unaltered so the ALB can handle the request dynamically.
Create a new CloudFront Function and add the following JavaScript code:
function handler(event) {
var request = event.request;
var cookies = request.cookies;
var querystring = request.querystring;
var uri = request.uri;
// Capture the original domain requested by the user
var host = request.headers['host'].value;
// Check if the user is a logged-in WordPress/WooCommerce user
var hasAuthCookie = Object.keys(cookies).some(function(name) {
return name.startsWith('wordpress_logged_in_');
});
// Check for an explicit cache-bypass query string argument
var hasNoCacheArg = querystring && querystring['no_cache'] && querystring['no_cache'].value === 'true';
// Multi-tenant routing rule: Route guests to the S3 domain subfolder
if (!hasAuthCookie && !hasNoCacheArg) {
request.uri = '/' + host + uri + 'index.html';
}
// Preserve the original host header for the ALB downstream
request.headers['x-forwarded-host'] = { value: host };
return request;
}
Deploying and Associating the Function
For this routing logic to take effect on our failover setup, the function must be attached to the specific path behavior that leverages the multi-origin group.
- In the CloudFront Console, navigate to Functions in the left sidebar.
- Click Create function, name it
S3-MultiTenant-ALB-Forwarder, and paste the code above into the development tab. - Save your changes and click the Publish tab to transition the function from development to production.
- Go back to your CloudFront Distribution -> Behaviors tab.
- Select the
/*/path behavior (the behavior pointing to yourS3-ALB-Failover-Group) and click Edit. - Scroll down to the Function associations section.
- For the Viewer Request event type, change the type dropdown to CloudFront Function and select
S3-MultiTenant-ALB-Forwarder.
Result: Now, whenever an asset under the /*/ pattern is evaluated, the edge function restructures the path to point to the S3 bucket’s domain folder while silently embedding the original Host into X-Forwarded-Host.
If S3 throws a 403 or 404 error, CloudFront moves to the ALB origin, which will receive this customized request configuration.
Application-Side Routing
At this stage of the pipeline, when a request fails over from S3 to the ALB, it arrives with modifications made by our edge infrastructure:
- The
Hostheader is set toalb.example.com(inherited from the Origin domain setting). - The URL path has been rewritten by the CloudFront Function to follow the multi-tenant format (e.g.,
^/example.com/slug/index.html).
Left uncorrected, your backend application server will reject this request because it does not recognize the alb.example.com hostname, nor does it have a matching directory structure for the rewritten S3 URL path.
To resolve this, we will configure an ALB Listener Rule to intercept these requests, normalize the URI structure, and route traffic to your application target group.
Configuring the ALB URL Modification & Routing Rule
We need to create a rule on your ALB’s HTTPS listener that catches incoming traffic from CloudFront, strips away the multi-tenant S3 folder prefix, removes the trailing index.html, and forwards the clean request to your application.
- Open the EC2 Console and navigate to Load Balancers.
- Select your ALB and open the Listeners and rules tab.
- Click on your HTTPS:443 listener to view its rules, and click Add rule.
- Configure the rule settings precisely as follows:
- Rule Conditions: * Add a condition type of Host header and set the value to
alb.example.com. - Rule Actions (Next Step):
- Select Transform URL path (or Redirect/Rewrite actions depending on your AWS UI layout).
- Match pattern (Regex):
^/[^/]+/(?:(.*))?index\.html$ - Replacement pattern:
/$1
- Routing Action:
- Select Forward to target group.
- Choose your active application target group (e.g.,
elb-target) and set the weight to 1 (100%). - Ensure Target group stickiness is set to Off.
How the Regular Expression works: The pattern ^/[^/]+/(?:(.*))?index\.html$ captures the middle section of the URI, which is your actual application path, while ignoring the initial domain folder created for S3 and stripping the trailing index.html.
The replacement pattern /$1 seamlessly restores the request to its native format (e.g., transforming /example.com/shop/index.html back into /shop/).
Crucial Update: Adapting Host-Dependent Application Rules
Because CloudFront is passing alb.example.com as the primary HTTP Host header to pass AWS SSL handshakes, your backend web server (e.g., Nginx, Apache) or application framework will no longer see your primary domain (like example.com) inside the standard Host variable.
⚠️ Important Migration Step: Any existing ALB rules, web server configurations, or application code dependencies that filter, validate, or route traffic based on the Host header must be updated to inspect the X-Forwarded-Host header instead.
Our CloudFront Function securely copies the viewer’s original domain name into the X-Forwarded-Host header.
Updating your downstream systems to trust this header ensures that multi-tenant routing, domain-specific logic, and link generation continue to function perfectly across your high-availability setup.
Application-Level Integration (PHP Setup)
While the ALB successfully rewrites the path and forwards the request, the PHP application itself still needs to recognize the original domain name requested by the user.
Because CloudFront forwards the custom origin domain (alb.example.com) in the core Host header to bypass the S3/ALB conflict, core global arrays in PHP (like $_SERVER['HTTP_HOST'] and $_SERVER['SERVER_NAME']) will natively reference that internal infrastructure subdomain.
For platforms like WordPress or standalone PHP frameworks, this will break routing, dynamic canonical URL generation, and link construction.
To finalize our pipeline, we must intercept the PHP initialization process and normalize the global environmental variables using the custom header injected by our CloudFront Function.
Implementing the Server Array Overrides
Add the following snippet to the absolute top of your application’s entry script. If you are using WordPress or WooCommerce, place this at the very beginning of your wp-config.php file, immediately after the opening:
if ( !empty($_SERVER['HTTP_X_FORWARDED_HOST']) ) {
// CloudFront forwarder: Extract the original user-requested domain
$forwarded_host = $_SERVER['HTTP_X_FORWARDED_HOST'];
// Reconstruct the server variables to mirror a direct connection
$_SERVER['SERVER_NAME'] = $forwarded_host;
$_SERVER['HTTP_HOST'] = $forwarded_host;
}
if ( !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) ) {
// Ensure the application accurately recognizes SSL terminations down the line
if ( $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
$_SERVER['HTTPS'] = 'on';
$_SERVER['REQUEST_SCHEME'] = 'https';
$_SERVER['SERVER_PORT'] = '443';
}
}
Fixes Host Masking: By mapping HTTP_X_FORWARDED_HOST back into SERVER_NAME and HTTP_HOST, you seamlessly trick PHP into believing it received a direct native hit from your user’s primary domain (e.g., example.com), completely hiding the internal infrastructure routing (alb.example.com).
Secures Protocol Recognition: Standardizing the HTTPS, REQUEST_SCHEME, and SERVER_PORT parameters via the HTTP_X_FORWARDED_PROTO block prevents the application from throwing infinite redirection loops, mixed-content layout issues, or trying to deliver unencrypted assets to your clients.
Conclusion & Next Steps
By combining a customized CloudFront Origin Request Policy, edge-level URI manipulation, and targeted ALB rewrite rules, you have successfully engineered a robust, high-availability architecture that completely bypasses the S3 and ALB header conflict.
As a final best practice for production environments, remember that the multi-origin failover group should primarily handle static fallbacks, assets, and general guest traffic.
For the complex, highly dynamic segments of your application, you should create dedicated CloudFront behaviors that route traffic directly to your ALB origin using the AWS-managed AllViewer policy.
Be sure to bypass the failover group for:
- Administrative Interfaces: Paths like
/wp-admin/or login portals. - State-Changing Actions: Explicit routes for
POSTrequests, checkouts, and standardAPIendpoints. - Dynamic Components: Feeds (
/feed/), paginated queries, search pages, and user-specific sessions.
Isolating these dynamic routes ensures that stateful operations bypass S3 checks entirely, minimizing edge compute latency and providing your users with a secure, snappy, and uninterrupted application experience.



