Subdomain Routing (multi-tenant)
Serve many tenants from one CDN distribution and one S3 bucket. The request subdomain becomes a folder prefix in the bucket, so abc.yourdomain.com maps to /abc/ - no per-tenant bucket, distribution, or certificate.
Subdomain routing lets a single CDN resource with a wildcard domain
(*.yourdomain.com) serve unlimited tenants out of one S3 bucket, each isolated
in its own folder. The request subdomain is rewritten to a path prefix in the
bucket at the edge.
abc.yourdomain.com/style.css -> bucket /abc/style.css
abc.yourdomain.com/ -> bucket /abc/index.html
abc.yourdomain.com/advogado -> bucket /abc/advogado/index.html
www.yourdomain.com/ and apex -> bucket /index.html (root, no prefix)
This replaces the "one bucket + one distribution + one certificate per site"
pattern. Add a tenant by writing files to a new /{subdomain}/ folder - no new
infrastructure.
How it works
A viewer-request CloudFront function (mamute-index-rewrite-subdomain) runs on
every request:
- Reads the
Hostheader, lowercased. - Takes the first label (
abcfromabc.yourdomain.com). - Clean URLs: if the path ends in
/, or has no file extension, it serves the
directory'sindex.html(/advogadoand/advogado/both map to
/advogado/index.html). Paths with an extension (.css,.js) pass through. - If the subdomain is not
wwwor the apex, prefixes/{subdomain}to the path.
It only makes sense with an S3 / storage origin - the function rewrites the
path into the bucket.
Enabling it
subdomain_routing is a boolean flag on a behavior (sibling of index_rewrite).
It is not accepted on POST /cdn/resource; the default behavior is created
with subdomain_routing = false. Enable it with a PUT on the default behavior.
The update action requires the behavior's id, so first read the resource and
grab the id of the priority-0 (default) behavior:
curl https://api.mamutecloud.com/cdn/resource/$RESOURCE_ID \
-H "Authorization: $MAMUTECLOUD_API_KEY"
# -> response.behaviors[] -> take the object with "priority": 0, read its "id"Then enable the flag, passing that id:
curl -X PUT https://api.mamutecloud.com/cdn/resource/$RESOURCE_ID \
-H "Authorization: $MAMUTECLOUD_API_KEY" \
-H "content-type: application/json" \
-d '{
"behaviors": [
{ "action": "update", "id": "<behavior-id>", "priority": 0, "subdomain_routing": true }
]
}'Read it back with GET /cdn/resource/{resourceId} - each behavior object returns
subdomain_routing as true, false, or null.
Edge function precedence (viewer-request slot)
For a behavior, the function attached is chosen in this order:
subdomain_routing = true->mamute-index-rewrite-subdomain- else
index_rewritetrue (or plain S3 origin) ->mamute-index-rewrite - else custom origin ->
mamute-block-websocket - else -> none
Purging the cache (important)
Because the subdomain is rewritten into the path before the cache key, objects
are cached under /{subdomain}/.... A purge of the public path (/advogado/*)
would not match the cached /{subdomain}/advogado/* and would silently miss.
Pass the tenant subdomain on the purge so it targets the right path:
curl -X POST https://api.mamutecloud.com/cdn/resource/$RESOURCE_ID/purge/url \
-H "Authorization: $MAMUTECLOUD_API_KEY" \
-H "content-type: application/json" \
-d '{ "subdomain": "abc", "urls": ["/advogado/*"] }'
# -> invalidates /abc/advogado/*On a subdomain-routing resource, a purge without subdomain returns a 400 telling
you to pass it. Use "urls": ["/*"] (still with the subdomain, or as a plain
purge/all) to clear an entire tenant. The same subdomain field works on
purge/pattern.
Prerequisites
- A wildcard SSL certificate
*.yourdomain.comattached to the resource's
custom domain (see Hosting a Static Site for the cert- custom-domain flow).
- The bucket laid out with one folder per subdomain:
/{subdomain}/index.html,
/{subdomain}/assets/..., etc. A request for a key that does not exist returns
the S3404/403- the client's upload must write into this layout. - Files uploaded via the S3 data plane (
https://s3-us-east-1.mamutecloud.com,
path-style, regionus-east-1).
End-to-end
- Create a bucket and S3 credentials (see Hosting a Static Site).
- Upload each tenant under its own prefix:
s3://<bucket>/<subdomain>/index.html. - Issue a wildcard cert:
POST /cdn/ssl-certificate {"domain_name":"*.yourdomain.com"},
validate the DNS records, wait forISSUED. - Create the resource with a storage origin:
POST /cdn/resource. - Attach the wildcard domain + cert:
PUT /cdn/resource/{id}withcustom_domains,
then activate it. - Turn on routing: read the resource, then
PUT /cdn/resource/{id}with the
subdomain_routingbehavior (above, with the behaviorid). - Point
*.yourdomain.com(wildcard CNAME) at the resourcedistribution_domain.
Any <sub>.yourdomain.com now serves s3://<bucket>/<sub>/.
Updated 23 days ago
