A real site is rarely one origin. Static assets belong in S3; the API belongs behind an ALB; images might come from a separate bucket. CloudFront routes all of this from a single distribution using cache behaviors - ordered rules that match a URL path and send it to a chosen origin with its own caching settings.
And because origins fail, CloudFront can pair two of them into an origin group that fails over automatically. This page shows how to build path-based routing and failover through the SDK's DistributionConfig.
Add an entry to CacheBehaviors for each path that should route somewhere other than the default. Each behavior names a PathPattern, a TargetOriginId, a ViewerProtocolPolicy, and a cache policy.
# --- Python (boto3) ---# Fragment of a DistributionConfig: two origins, one extra behavior for /api/*.CACHING_DISABLED = "4135ea2d-6df8-44a3-9df3-4b5a84be39ad" # managed policy idCACHING_OPTIMIZED = "658327ea-f89d-4fab-a63d-7e88639e58f6" # managed policy idconfig_fragment = { "Origins": {"Quantity": 2, "Items": [ {"Id": "s3-assets", "DomainName": "assets.s3.us-east-1.amazonaws.com", "S3OriginConfig": {"OriginAccessIdentity": ""}}, {"Id": "api-alb", "DomainName": "api.internal.example.com", "CustomOriginConfig": {"HTTPPort": 80, "HTTPSPort": 443, "OriginProtocolPolicy": "https-only"}}, ]}, "DefaultCacheBehavior": { "TargetOriginId": "s3-assets", "ViewerProtocolPolicy": "redirect-to-https", "CachePolicyId": CACHING_OPTIMIZED, }, "CacheBehaviors": {"Quantity": 1, "Items": [{ "PathPattern": "/api/*", "TargetOriginId": "api-alb", "ViewerProtocolPolicy": "https-only", "AllowedMethods": {"Quantity": 7, "Items": ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]}, "CachePolicyId": CACHING_DISABLED, # do not cache API responses }]},}
Pass this fragment inside a full DistributionConfig to CreateDistribution, or fold it into an existing config and UpdateDistribution. The /api/* behavior routes API traffic to the ALB with caching off, while everything else falls through to the S3 default with aggressive caching.
Ordering matters. CloudFront evaluates behaviors by specificity: the first PathPattern that matches wins, and the DefaultCacheBehavior is the fallback for anything unmatched. To add a behavior to a live distribution, read the config, insert the item, keep Quantity in sync with the list length, and update with the ETag.
# --- Python (boto3) ---import boto3cf = boto3.client("cloudfront", region_name="us-east-1")cur = cf.get_distribution_config(Id="E1234ABCDEF")config, etag = cur["DistributionConfig"], cur["ETag"]behaviors = config.setdefault("CacheBehaviors", {"Quantity": 0, "Items": []})behaviors.setdefault("Items", [])behaviors["Items"].append({ "PathPattern": "/images/*", "TargetOriginId": "s3-assets", "ViewerProtocolPolicy": "redirect-to-https", "CachePolicyId": "658327ea-f89d-4fab-a63d-7e88639e58f6",})behaviors["Quantity"] = len(behaviors["Items"]) # MUST match the list lengthcf.update_distribution(Id="E1234ABCDEF", DistributionConfig=config, IfMatch=etag)
The recurring gotcha across the whole CloudFront API is the Quantity field. Every list-shaped field (Origins, CacheBehaviors, AllowedMethods, Paths) carries both Items and a Quantity, and they must agree. Set Quantity from len(Items) every time you mutate a list, or the update is rejected.
Path patterns support a trailing * wildcard and are matched most-specific-first, not in list order. Given /api/v2/*, /api/*, and the default, a request to /api/v2/orders matches /api/v2/*. A request to /api/health matches /api/*. A request to /index.html matches nothing specific and falls to the default. Because specificity - not array position - decides the winner, you cannot reorder behaviors to change routing; you change the patterns.
Each behavior is independent, so you tune caching per path. Static assets under /images/* use CachingOptimized; the /api/* path uses CachingDisabled so responses are never cached; a search endpoint might use a custom cache policy that includes the q query string in the key.
An origin group wraps two origins - a primary and a secondary - and tells CloudFront to retry the secondary when the primary returns a configured failover status code (such as 500, 502, 503, or 504). The group has its own Id, and a cache behavior can target that id exactly as if it were a normal origin.
# --- Python (boto3) ---# Add an OriginGroups block alongside Origins in the DistributionConfig.origin_groups = {"Quantity": 1, "Items": [{ "Id": "app-failover", "FailoverCriteria": {"StatusCodes": {"Quantity": 4, "Items": [500, 502, 503, 504]}}, "Members": {"Quantity": 2, "Items": [ {"OriginId": "primary-alb"}, # tried first {"OriginId": "backup-alb"}, # used on failover ]},}]}# Then point a behavior at the group:# DefaultCacheBehavior["TargetOriginId"] = "app-failover"
// --- TypeScript (AWS SDK v3) ---// Add an OriginGroups block alongside Origins in the DistributionConfig.const originGroups = { Quantity: 1, Items: [{ Id: "app-failover", FailoverCriteria: { StatusCodes: { Quantity: 4, Items: [500, 502, 503, 504] } }, Members: { Quantity: 2, Items: [ { OriginId: "primary-alb" }, // tried first { OriginId: "backup-alb" }, // used on failover ] },}] };// Then point a behavior at the group:// DefaultCacheBehavior.TargetOriginId = "app-failover";
Both member origins must already exist in Origins. A group has exactly two members, and the TargetOriginId on any behavior can be the group id. Failover triggers only for the listed status codes (or a connection failure/timeout) and only for GET, HEAD, and OPTIONS requests - CloudFront will not replay a POST against the backup.
Quantity out of sync with Items. Every list field carries a Quantity that must equal the number of items. Recompute it after any mutation or the update fails.
Expecting array order to decide routing. Behaviors match by specificity, not position. Change the PathPattern, not the ordering, to alter routing.
TargetOriginId that matches nothing. A behavior must reference an existing origin id or origin group id exactly; a typo is rejected at update time.
Caching API responses by accident. If an /api/* behavior inherits a caching policy, dynamic responses get cached. Use CachingDisabled for non-cacheable paths.
Origin group failover on writes. Failover applies only to safe methods (GET/HEAD/OPTIONS). A failed POST is not retried against the secondary.
Only two members per group. An origin group is strictly primary plus one secondary; you cannot chain three origins.
Route 53 failover or latency routing works at DNS level and can front entirely separate stacks, but it fails over slower (TTL-bound) and does not give per-path CDN caching.
Application-level routing at the origin (an ALB or reverse proxy that fans out by path) keeps routing logic in one place, but forfeits edge caching for the paths it handles.
Multiple distributions (one per subdomain) isolate configs completely, at the cost of more DNS, more certificates, and no shared cache behaviors.
Use cache behaviors when one hostname must serve several origins with different caching, and origin groups when you need automatic edge-level failover for a read path.
How does CloudFront choose which cache behavior applies?
It matches the request path against each behavior's PathPattern most-specific-first. The first specific match wins; unmatched requests fall to the DefaultCacheBehavior.
Does the order of CacheBehaviors in the array matter?
Routing is decided by pattern specificity, not array position. You change routing by editing patterns, not by reordering the list.
What is the Quantity field for?
Every list-shaped CloudFront field carries both Items and a Quantity count that must match the list length. Recompute Quantity after every mutation or the request is rejected.
Can each behavior cache differently?
Yes. Each behavior sets its own CachePolicyId, so static paths can use CachingOptimized while API paths use CachingDisabled.
What is an origin group?
A pairing of a primary and a secondary origin with failover criteria. CloudFront retries the secondary when the primary returns a configured status code or fails to connect.
How many origins can an origin group have?
Exactly two: one primary and one secondary. You cannot chain a third origin into the same group.
Which requests trigger origin failover?
Only safe, idempotent methods - GET, HEAD, and OPTIONS - and only on the configured failover status codes or a connection failure. A POST is never replayed against the secondary.
How do I point a behavior at an origin group?
Set the behavior's TargetOriginId to the origin group's Id. CloudFront treats the group id like a normal origin target.
Do the origins in a group need to exist already?
Yes. Both member OriginIds must reference origins already defined in the distribution's Origins list.
Can I add a behavior without recreating the distribution?
Yes. GetDistributionConfig, append to CacheBehaviors.Items, fix Quantity, then UpdateDistribution with the ETag as IfMatch.