The Professional Way to Host Static Content on AWS using Terraform
In an idea world, deploying a static website content into a S3 bucket, clicking “Make Public” and share the link. In that world, there are no security audit, no latency issues for user across the globe and no such thing as an “unsecured connection” warning in the browser.
In real time environment, “Public S3 Buckets” are often one way ticket to security meeting you do not want to attend. For professional grade deployment, you need:
- Zero Public Access: Keeping our storage origin locked down and private.
- Global Speed: Serving content from the edge, closer to the user.
- Enforced Encryption: Redirecting every visitor to https:// automatically.
In this post, I’m moving away from basic Terraform syntax and diving into a Real-Time Scenario: architecting a secure, private S3 origin fronted by Amazon CloudFront using Origin Access Control (OAC).
The Architecture
To solve these real-world requirements, we aren’t just deploying one resource. We are building a “handshake” between:
- The Vault (S3): Where our files live, completely private.
- The Gatekeeper (CloudFront OAC): The specialized permission that lets only our CDN inside.
- The Messenger (CloudFront Distribution): The service that delivers our site via HTTPS.
The Implementation
- Amazon Simple Storage Service (Amazon S3) Amazon Simple Storage Service (Amazon S3) is an object storage service offering industry-leading scalability, data availability, security, and performance. Millions of customers of all sizes and industries store, manage, analyze, and protect any amount of data for virtually any use case, such as data lakes, cloud-native applications, and mobile apps. With cost-effective storage classes and easy-to-use management features, you can optimize costs, organize and analyze data, and configure fine-tuned access controls to meet specific business and compliance requirements.
Lets create a S3 bucket in terraform
| |
Remember:
- Bucket name is unique
- Bucket name must be lowercase and less than or equal to 63 characters in length.
- force_destroy - (optional, default = false): Boolean that indicates all object (including any locked objects) should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error.
Now that, we have a S3 bucket, lets understand about the blocking public access
Locking the Front Door | Make the S3 Bucket Private
Making the S3 bucket private is good but to make it impossible to be public makes it awesome. Even if someone tries to change it to public, the resource will override and block it.
| |
Modern Handshake (CloudFront OAC)
Amazon CloudFront is a global content delivery network that securely delivers applications, websites, videos, and APIs to viewers across the globe in milliseconds. Leverage CloudFront’s origin access identity (OAI) to secures S3 origin access to CloudFront only. When using OAC, a typical request and response workflow will be:
- A client sends HTTP or HTTPS requests to CloudFront
- CloudFront edge locations receive the requests. If the requested object is not already cached, CloudFront signs the requests using OAC signing protocol
- S3 origins authenticate, authorize, or deny the requests.
- When configuring OAC, “Do not sign requests”, “Sign requests”, and sign requests. For this case, do not choose, Do not override authorization header.
Create a Cloudfront Distribution
Below are the details required to create a cloud front distribution
- Choose a plan & Distribution Type
- Distribution Name, Type and Route 53 Integration
- Defining the Origin S3 Bucket & Path
- Settings (Allow Private S3 bucket access to cloudfront)
- Security Handshake Origin Settings
- Cache & Behavior Settings
- Web Application Firewall
1. Choose a plan and Distribution Type in cloud front
Let’s create a s3 distribution and configure it
- Origin Details
- Domain Name: Use the regional domain name of the S3 bucket as the origin
- Origin Id: S3 Origin ID
- Origin Access Control ID: Id of the Origin Access Control to set the bucket only accessible via the access control id.
Since we need the OAC ID (Origin Access Control), lets create it first.
Create OAC
| |
Now, lets use this in the S3 Distribution
| |
Define Cache Behavior
As part of defining cache behavior,
- Target Origin ID : should point to S3 Origin ID
- Viewer Protocol Policy: Redirect HTTP request to HTTPS request
- Allowed Methods: Configure which methods are allowed to cache
- Cache Methods: Configure what requests can be cached
Along with this, configure Forwarded Values, such as query string and cookies. Based on the application setup you can determine these values.
| |
This goes part of the Cloud Front Distribution.
Additionally you can define restriction on the distribution such as Geo Restriction,
| |
A Viewer Certificate is the configuration block in CloudFront that determines how your distribution handles SSL/TLS encryption for your end users. When a user visits your website (e.g., https://www.yourdomain.com), the Viewer Certificate is what provides the digital handshake to ensure the connection is secure and that the user is actually talking to your server, not an imposter. Why is it needed? In the world of modern web hosting, HTTPS is non-negotiable. Browsers will flag your site as “Not Secure” without it. The viewer_certificate block tells AWS which SSL certificate to use to prove your site’s identity.
| Option | Use Case | Cost |
|---|---|---|
| CloudFront Default Certificate | Used for the default AWS domain (e.g., d1234.cloudfront.net) | Free |
| ACM Certificate | Used for custom domains (e.g., www.example.com). Managed via AWS Certificate Manager. | Free (for public certs) |
| |
Custom Domain Setup with Route 53
When you create a CloudFront distribution, AWS gives you a random, autogenerated domain like d1234567890.cloudfront.net. An Alias allows you to use your own professional domain, such as www.yourcompany.com or assets.example.com, to serve that same content.
| |
Configuring OAC when creating a new CloudFront distribution
Once the distribution is successfully created, you must update the s3 bucket policy. Before that, lets create OAC with terraform.
Note: CloudFront distributions take about 15 minutes to reach a deployed state after creation or modification. During this time, deletes to resources will be blocked. If you need to delete a distribution that is enabled and you do not want to wait, you need to use the retain_on_delete flag.