Alibaba Cloud OSS (Object Storage Service) is a solid storage backend, but exposing it directly to the internet has drawbacks: the bucket domain is visible, traffic pricing is higher than ESA in many regions (ESA Pro plan traffic price is 0.03USD/GB globally and no request cost), and you miss out on edge-layer security. Putting ESA (Edge Security Acceleration) in front of your private OSS bucket addresses all three, but it introduces a non-obvious challenge around authentication and caching.
This guide walks through the complete architecture, the configuration steps, and the trade-offs you need to understand.
Hide your origin. With ESA as the front door, clients only see your custom domain (e.g., cdn.example.com). The actual OSS endpoint (mybucket.oss-ap-southeast-1.aliyuncs.com) is never exposed, reducing the attack surface and preventing direct bucket enumeration.
Lower egress cost for global traffic. OSS internet egress pricing varies by region but is generally higher than ESA's traffic unit price, especially on ESA Pro and above plans. For workloads serving significant overseas traffic (e.g., global SaaS platforms, game asset distribution, or media delivery), routing through ESA can reduce per-GB costs. For example, ESA Pro plan traffic in many overseas regions is priced lower than OSS direct internet egress in those same regions.
Edge-layer security. ESA provides a suite of security capabilities at the edge that OSS alone does not offer:
Here is the fundamental tension. ESA is a CDN — its job is to cache content at edge nodes close to users. But if your OSS bucket is private (as it should be), every request needs to be authenticated. The two mechanisms are in conflict:
This means you must choose between two models:
| Model | Caching | Auth enforcement | Use case |
|---|---|---|---|
| Strict auth | Disabled (or bypass) | Every request authenticated at origin | Private/sensitive content |
| Edge auth + caching | Enabled | Auth checked at edge before serving cache | High-traffic private content |
We'll cover both.
Client ESA Edge OSS Origin
│ │ │
│ GET cdn.example.com/1.png │ │
│ + STS signature headers │ │
│──────────────────────────────►│ │
│ │ No cache → forward to origin│
│ │ (preserves auth headers) │
│ │────────────────────────────►│
│ │ │ Verify STS
│ │ │ signature
│ │ 200 OK │
│ │◄────────────────────────────│
│ 200 OK │ │
│◄──────────────────────────────│ │
With caching disabled, every request traverses the full path and must present valid STS credentials. ESA acts as a transparent reverse proxy, adding security and hiding the origin — but not caching.
In the RAM console:
CDNAccessingPrivateOSSRole){
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": ["oss:GetObject"],
"Resource": ["acs:oss:*:*:mybucket/*"]
}
]
}
Edit the role's trust policy to specify who can assume it:
{
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"RAM": ["acs:ram::<ACCOUNT_ID>:root"]
}
}
],
"Version": "1"
}
To restrict to a specific sub-user:
"RAM": ["acs:ram::<ACCOUNT_ID>:user/myapp-user"]
The RAM user whose AK/SK your application uses needs the sts:AssumeRole action:
{
"Version": "1",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "acs:ram::<ACCOUNT_ID>:role/CDNAccessingPrivateOSSRole"
}
]
}
Without this, the AssumeRole API returns 403 NoPermission with PolicyType: AccountLevelIdentityBasedPolicy.
Set the Bucket ACL to private.
Then audit the Bucket Policy. This is often overlooked: a bucket policy with Principal: "*" and Effect: Allow on oss:GetObject will override the private ACL and make the bucket publicly readable. Bucket Policy takes precedence over Bucket ACL in the permission evaluation logic.
Remove any statement like:
{
"Effect": "Allow",
"Principal": ["*"],
"Action": ["oss:GetObject"],
"Resource": ["acs:oss:*:*:mybucket/*"]
}
To verify your bucket is truly private, test anonymous access directly to the OSS endpoint:
curl -s -o /dev/null -w "%{http_code}" https://mybucket.oss-ap-southeast-1.aliyuncs.com/1.png
# Expected: 403
This is the critical step. In the ESA console:
cdn.example.com)mybucket.oss-ap-southeast-1.aliyuncs.com
-Protocol: HTTPS, Port: 443
Why "Custom Origin" instead of "OSS Origin"? When you select "OSS Origin", ESA uses its own service-linked role to sign back-to-origin requests. This means ESA authenticates to OSS on behalf of every request — and every request succeeds, regardless of whether the end user is authenticated. A custom origin forces ESA to forward the client's request headers as-is, preserving the STS authentication boundary at the origin.
Disable caching: for the strict auth model: set cache TTL to 0 or configure a cache rule that bypasses all requests.
import os
import oss2
from alibabacloud_sts20150401.client import Client as StsClient
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_sts20150401 import models as sts_models
ROLE_ARN = "acs:ram::<ACCOUNT_ID>:role/CDNAccessingPrivateOSSRole"
ENDPOINT = "https://cdn.example.com"
BUCKET_NAME = "mybucket"
def get_sts_credentials():
ak = os.environ["OSS_ACCESS_KEY_ID"]
sk = os.environ["OSS_ACCESS_KEY_SECRET"]
sts_config = open_api_models.Config(
access_key_id=ak, access_key_secret=sk,
)
sts_config.endpoint = "sts.ap-southeast-1.aliyuncs.com"
sts_client = StsClient(sts_config)
request = sts_models.AssumeRoleRequest(
role_arn=ROLE_ARN,
role_session_name="cdn-download",
duration_seconds=3600,
)
response = sts_client.assume_role(request)
c = response.body.credentials
return c.access_key_id, c.access_key_secret, c.security_token
def download(sts_ak, sts_sk, sts_token, object_key, local_path):
auth = oss2.StsAuth(sts_ak, sts_sk, sts_token)
# is_cname=True is required for custom domains
bucket = oss2.Bucket(auth, ENDPOINT, BUCKET_NAME, is_cname=True)
result = bucket.get_object_to_file(object_key, local_path)
print(f"OK: {local_path} ({os.path.getsize(local_path):,} bytes)")
if __name__ == "__main__":
ak, sk, token = get_sts_credentials()
download(ak, sk, token, "1.png", "output.png")
The is_cname=True parameter is mandatory with custom domains. Without it, the oss2 SDK prepends the bucket name to the domain (mybucket.cdn.example.com), which fails DNS resolution.
Run with:
pip install oss2 alibabacloud_sts20150401 alibabacloud_tea_openapi
OSS_ACCESS_KEY_ID=<ak> OSS_ACCESS_KEY_SECRET=<sk> python download.py
Here's what happens if you enable caching without thinking it through:
The response headers will confirm it: X-Site-Cache-Status: HIT and a non-zero Age value. The private bucket ACL is only enforced on cache misses.
This is not a bug — it's how CDNs work. But it means you need an explicit strategy.
If you want both caching (for performance and cost) and access control, move the authentication check to the edge. ESA offers several mechanisms:
For more complex authentication flows, deploy an Edge Routine (serverless JavaScript at the edge) that intercepts requests before the cache:
// Edge Routine: validate a Bearer token before serving
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 });
}
const token = authHeader.replace('Bearer ', '');
// Validate against your auth service or a pre-shared key
const isValid = await validateToken(token);
if (!isValid) {
return new Response('Forbidden', { status: 403 });
}
// Auth passed — proceed to fetch from cache/origin
return fetch(request);
}
async function validateToken(token) {
// Option 1: Check against a pre-shared key
// Option 2: Call an external auth API
// Option 3: Verify a JWT signature locally
const expected = "your-secret-key";
return token === expected;
}
Edge Routine runs before the cache lookup, so even cached content requires valid credentials. This gives you the best of both worlds: authenticated access at the edge, with caching for performance.
Use ESA's WAF to enforce access policies at the edge:
X-Auth-Token: <secret>)These rules are evaluated before the cache, providing an additional layer of access control.
| Requirement | Strict Auth (no cache) | Edge Routine |
|---|---|---|
| Every request authenticated at origin | Yes | No (edge check) |
| Edge caching enabled | No | Yes |
| Implementation complexity | Low | High |
| Token expiration built-in | STS token (1h default) | Custom logic |
| Suitable for | API backends, sensitive data | Complex auth flows |
| Latency (repeat requests) | Higher (always to origin) | Low (cache hit) |
| Origin traffic cost | Higher | Lower |
Putting ESA in front of a private OSS bucket gives you origin hiding, lower overseas egress costs, and edge-layer security (WAF, DDoS, bot management). The key configuration choices are:
The strict auth model is simpler but trades latency and origin cost for security. The edge auth model is more complex but lets you have both caching and access control — just enforced at a different layer.
From Prompt to Production: Shipping a Full-Stack App with Qoder and ESA Functions
6 posts | 1 followers
FollowBryan, Zhang - June 17, 2026
Community Builder - June 16, 2026
ESA Big Fan - May 14, 2026
Bryan, Zhang - May 28, 2026
Bryan, Zhang - June 23, 2026
ESA Big Fan - June 5, 2026
6 posts | 1 followers
Follow
OSS(Object Storage Service)
An encrypted and secure cloud storage service which stores, processes and accesses massive amounts of data from anywhere in the world
Learn More
Edge Security Acceleration (Original DCDN)
Edge Security Acceleration (ESA) provides capabilities for edge acceleration, edge security, and edge computing. ESA adopts an easy-to-use interactive design and accelerates and protects websites, applications, and APIs to improve the performance and experience of access to web applications.
Learn More
Content Delivery Solution
Save egress traffic cost. Eliminate all complexity in managing storage cost.
Learn More
Storage Capacity Unit
Plan and optimize your storage budget with flexible storage services
Learn MoreMore Posts by ESA Big Fan