The Great Wall protected the country for hundreds of years, and it remains one of the few human creations visible from space with the naked eye. In the digital world, its virtual counterpart continues that same idea of protection, shaping how information flows and what can pass through.
Our client expanded their business into mainland China, where their applications need to load images and documents stored in Amazon S3. However, the default S3 endpoint quickly became a bottleneck, with unstable connectivity and unpredictable latency inside the region. Latency spikes, packet loss, or outright blocking can break the user experience. Yet the company domain is already approved through internal compliance procedures, meaning traffic routed through that domain is stable and trusted.
This creates a simple but important requirement:
Serve S3 files through the company domain without changing the app and without exposing the S3 bucket publicly.
Background
The API server must support mobile and web clients operating inside mainland China. Direct access to:
https://<bucket>.s3.ap-east-1.amazonaws.com/...
is unreliable. Even when it works, the latency is unpredictable.
However:
- The company domain is already approved
- Traffic to this domain is stable
- The API layer can enforce authentication and security checks
So instead of exposing S3 directly, the API domain becomes the gateway.
Solution Options Considered
To solve the problem of unstable S3 access from mainland China while keeping the company domain approved and trusted, three architectural options were evaluated:
- Cloudflare
- CloudFront
- NGINX Reverse Proxy
Each option solves part of the problem, but only one satisfies all requirements with minimal change and cost.
Option One: Cloudflare
Cloudflare already sits in front of the API domain, providing:
- TLS termination
- Global routing
- Edge caching
- DDoS protection
- China friendly stability
Cloudflare can proxy requests to S3, but it cannot :
- Access private S3 using IAM roles
- Perform backend business logic checks
- Enforce per request authorization
- Keep traffic inside AWS backbone
- Read private S3 objects without presigned URLs
Cloudflare Workers can add logic, but they introduce:
- New code
- New runtime
- No IAM role support
- No VPC endpoint support
Cloudflare is excellent for public static files, but insufficient for private S3 access with custom security checks.
Option Two: CloudFront
CloudFront is AWS’s CDN and can:
- Map your company domain
- Cache S3 content
- Use Origin Access Control to secure S3
- Improve global routing
However, CloudFront cannot natively perform cookie based authorization.
To check cookies, CloudFront requires:
- CloudFront Functions (very limited)
- Lambda@Edge (powerful but complex)
CloudFront also cannot:
- Use IAM roles to read private S3
- Access VPC endpoints
- Run your existing backend logic
- Guarantee optimal China performance
This makes CloudFront significantly complex, and doesn’t support IAM roles for least privilege.
Why CloudFront Cannot Assume an AWS IAM Role
CloudFront cannot assume an AWS IAM role because it is not a compute service. Only compute services such as EC2, Lambda, ECS, and EKS can call STS to assume roles and obtain temporary credentials. CloudFront has no execution environment, no credential provider, and no ability to sign AWS API requests using IAM role credentials.
Instead, CloudFront uses Origin Access Control (OAC), which is a fixed CloudFront service identity, not an IAM role. OAC can only sign requests to S3 and cannot access other AWS services, cannot run business logic, and cannot integrate with VPC endpoints.
Because CloudFront cannot assume IAM roles, it cannot:
- Use IAM role permissions to read private S3
- Access S3 through your VPC endpoint
- Perform authenticated backend logic
- Enforce per user authorization
This limitation is one of the key reasons CloudFront cannot replace an EC2 based NGINX proxy.
Option Three: NGINX Reverse Proxy
NGINX running on EC2 inside AWS provides:
- Full control of request logic
- Cookie based authentication
- Business rule checks
- IAM role access to private S3
- VPC endpoint routing
- Zero client side changes
- Zero new AWS services
- Minimal cost
NGINX can:
- Serve public files directly
- Serve private files only after validating cookies
- Access S3 privately through AWS backbone
- Enforce least privilege IAM policies
- Keep all traffic internal to AWS
This satisfies all requirements:
- minimal change
- minimal cost
- least complexity
NGINX becomes the most cost effective and least disruptive solution.
How the NGINX Proxy Works
1. Path Locations for Public and Private Buckets
NGINX exposes two categories of paths:
/app/upload/public/...
/app/upload/private/...
Public files proxy directly.
Private files require cookie checks or business logic before proxying.
2. Proxy Mapping to S3
NGINX rewrites the incoming path and forwards it to the correct S3 bucket endpoint.
location /app/upload/public/ {
proxy_pass https://your-public-bucket.s3.ap-east-1.amazonaws.com;
proxy_set_header Host your-public-bucket.s3.ap-east-1.amazonaws.com;
}
location /app/upload/private/ {
if (your-checking) { return 403; }
# Remove /app from the path
rewrite ^/app/(.*)$ /$1 break;
# S3 bucket
proxy_pass https://your-private-bucket.s3.ap-east-1.amazonaws.com;
# Required for S3
proxy_set_header Host your-private-bucket.s3.ap-east-1.amazonaws.com;
}
3. IAM Role for the NGINX Host
The EC2 instance running NGINX is granted an IAM role with only the required S3 read permissions.
{
"Sid": "AllowObjectReadWritePublic",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::your-private-bucket/*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "ap-east-1"
}
}
}
4. VPC Endpoint for S3
A Gateway VPC Endpoint ensures all S3 traffic stays inside AWS backbone, improving stability and security:
- Ensure you are in the correct AWS Region
- Open VPC Console : Navigate to the VPC Dashboard in your AWS Management Console.
- Create Endpoint : Click Endpoints in the left sidebar, then click Create endpoint.
- Service Category : Select AWS services.
-
Service Name : Search for
s3and select the Gateway type (e.g.,com.amazonaws.ap-east-1.s3). - VPC Selection : Choose the specific VPC where your private resources live.
- Route Tables : Select the Route Tables associated with your private subnets to automatically inject the S3 route.
- Policy : Leave it as Full Access for standard setups, or attach a custom IAM policy to restrict bucket access.
- Finish : Click Create endpoint
The vpc policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "nginx2S3ReadOnly",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-private-bucket",
"arn:aws:s3:::your-private-bucket/*"
],
"Condition": {
"ArnEquals": {
"aws:PrincipalArn": "arn:aws:iam::your-aws-id:role/ec2-production"
}
}
}
]
}
Results
1. All S3 Files Accessible Through the Company Domain
The app continues using the same URL pattern with no changes.
2. Private Bucket Security Without Presigned URLs
NGINX performs the authentication and authorization checks.
3. All File Transfers Stay Inside AWS Backbone
The VPC endpoint ensures no public internet routing.
Comparison Table
| Option | Strengths | Limitations | Best For |
|---|---|---|---|
| Cloudflare | Stable global routing, China friendly, SSL handled, caching, DDoS protection | Cannot access private S3 via IAM, cannot run backend logic, cannot keep traffic inside AWS backbone | Public static files, global CDN |
| CloudFront | AWS native CDN, OAC for S3, caching, custom domain | Cannot assume IAM roles, cookie checks require Lambda@Edge, no VPC endpoint routing, more cost and complexity | Public S3 distribution with CDN caching |
| NGINX Reverse Proxy | IAM role access, VPC endpoint routing, custom security checks, simple logic, no client changes | No global CDN, relies on Cloudflare for edge routing | Mixed public and private S3 access with business logic |
Key Takeaways
- NGINX is the only option that supports IAM role access , which is essential for secure private S3 reads without presigned URLs.
- CloudFront cannot assume IAM roles , making it unsuitable for private S3 access with backend logic.
- Cloudflare provides global routing and SSL , but cannot enforce your business logic or access S3 privately.
- VPC endpoints keep all S3 traffic inside AWS , improving stability for mainland China and eliminating public internet exposure.
- This architecture requires minimal changes to the application , making it ideal for production systems that cannot afford disruption.
- NGINX plus Cloudflare gives you the best of both worlds: global routing at the edge and secure, controlled access inside AWS.
About the Author
Jonathan Wong is an IT and AI consultant with 20+ years of experience leading engineering teams across Vancouver and Hong Kong. He specializes in modernizing legacy platforms, cloud security, and building AI-ready systems for startups and large enterprises while advising leadership on using strategic technology to drive business growth.
Connect with me on LinkedIn
The post Security Meets Reality: The Great Firewall appeared first on Behind the Build.
Top comments (0)