Deploying a Static Website with AWS S3, CloudFront, and WAF Web ACL

In this post, I’ll walk through how I deployed a static website using Amazon S3 and distributed it globally with CloudFront. Then, I applied security hardening using AWS Web ACL to defend against common threats. Step 1: Build the Static Site I created a simple HTML and CSS layout: index.html CloudFront Static Site body { background-color: #0e0e0e; color: #f4f4f4; font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .container { text-align: center; } Hello, CloudFront! This site is served from an S3 bucket and distributed globally with AWS CloudFront. style.css body { background-color: #0e0e0e; color: #f4f4f4; font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; } .container { text-align: center; } I then uploaded both files to a new S3 bucket named: javier-static-site-2025 Step 2: Enable Static Website Hosting on S3 In the S3 bucket settings, I enabled static website hosting: Selected “Host a static website” Entered index.html as the index document Left the error document blank Step 3: Configure Bucket Policy for Public Read Access To allow CloudFront (and users) to access the files, I added a bucket policy: { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicRead", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::javier-static-site-2025/*" } ] } Step 4: Preview the Website on S3 After applying the policy, I accessed the site directly via the S3 static hosting endpoint: http://javier-static-site-2025.s3-website-ap-southeast-2.amazonaws.com The site loaded correctly, showing the custom “Hello, CloudFront!” message. Step 5: Set Up a CloudFront Distribution I created a CloudFront distribution to globally accelerate and cache content: Origin Domain: S3 website endpoint (not the default bucket domain) Origin Access: Public (since I allowed public access in the S3 bucket policy) Viewer Protocol Policy: Redirect HTTP to HTTPS Allowed Methods: GET, HEAD (default) Compression: Enabled Step 6: Link CloudFront to Web ACL (WAF) I created a Web ACL named CloudFrontWebACL and associated it with the CloudFront distribution. Adding AWS WAF Rules With the Web ACL CloudFrontWebACL created and associated with my CloudFront distribution, I added several rules to strengthen the site’s security posture. Managed Rule Sets Added AWSManagedRulesCommonRuleSet Covers a wide range of common threats like LFI, bad bots, size restrictions, and more. AWSManagedRulesAmazonIpReputationList Blocks traffic from known malicious IPs. AWSManagedRulesSQLiRuleSet Specifically targets SQL injection attempts. Custom Rate Limiting Rule – LimitRequestsByIP Blocks any IP that makes more than 10 requests in a 5-minute window. For all managed rules, I set the action override to “Block” to ensure they actively drop malicious traffic rather than just counting matches. Testing the Web ACL This is the part where studying for the eJPT certification came in handy. To validate the effectiveness of each rule, I ran several tests using curl. 1. Rate Limiting for i in {1..25}; do curl -s -o /dev/null "https://" & done wait Result: Requests beyond the 10-request threshold were blocked as expected. I confirmed this via the WAF metrics panel in CloudWatch, which showed exactly 25 blocked requests. 2. SQL Injection Attempt curl "https:///?id=1%27%3B%20DROP%20TABLE%20users--" Result: The request was blocked with a 403 error, showing that the SQLiRuleSet triggered correctly. 3. SQLMap User-Agent Fingerprint curl -A "sqlmap/1.0" https:// Result: Blocked with a 403 error. This confirmed that malicious User-Agent headers were being filtered properly by the common rule set. 4. Path-Based Recon curl https:///admin Result: Returned a 404 error from S3 (object not found), but was not blocked by WAF. This is expected — WAF only triggers on known threat patterns, not missing pages. CloudWatch Logs Verification To confirm that the WAF rules were actively blocking traffic, I reviewed the metrics and charts under the “WAF > Web ACLs > CloudFrontWebACL” section. The rate limiting rule showed 25 blocked requests at the exact timestamp of my curl loop test. SQLi and User-Agent tests were also reflected in blocked request counts under the relevant rules. Final Thoughts This project helped me understand how to: Deploy a static site via S3 and accelerate it globally with CloudFront Configure bucket permissions and static hosting Set up and customize AWS WAF rules Simulate attacks and observe real-time block metrics in CloudWatch The final result is a minimal, fast, and

May 3, 2025 - 12:50
 0
Deploying a Static Website with AWS S3, CloudFront, and WAF Web ACL

In this post, I’ll walk through how I deployed a static website using Amazon S3 and distributed it globally with CloudFront. Then, I applied security hardening using AWS Web ACL to defend against common threats.

Step 1: Build the Static Site

I created a simple HTML and CSS layout:

index.html

Image description



  
    
    
    CloudFront Static Site
    
  
  
    

Hello, CloudFront!

This site is served from an S3 bucket and distributed globally with AWS CloudFront.

style.css

Image description

body {
  background-color: #0e0e0e;
  color: #f4f4f4;
  font-family: sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
}

.container {
  text-align: center;
}

I then uploaded both files to a new S3 bucket named:

javier-static-site-2025

Image description

Step 2: Enable Static Website Hosting on S3
In the S3 bucket settings, I enabled static website hosting:

Image description

Selected “Host a static website”

Entered index.html as the index document

Left the error document blank

Step 3: Configure Bucket Policy for Public Read Access
To allow CloudFront (and users) to access the files, I added a bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::javier-static-site-2025/*"
    }
  ]
}

Image description

Step 4: Preview the Website on S3
After applying the policy, I accessed the site directly via the S3 static hosting endpoint:

http://javier-static-site-2025.s3-website-ap-southeast-2.amazonaws.com

Image description

The site loaded correctly, showing the custom “Hello, CloudFront!” message.

Step 5: Set Up a CloudFront Distribution
I created a CloudFront distribution to globally accelerate and cache content:

Origin Domain: S3 website endpoint (not the default bucket domain)

Origin Access: Public (since I allowed public access in the S3 bucket policy)

Viewer Protocol Policy: Redirect HTTP to HTTPS

Allowed Methods: GET, HEAD (default)

Compression: Enabled

Image description

Image description

Step 6: Link CloudFront to Web ACL (WAF)
I created a Web ACL named CloudFrontWebACL and associated it with the CloudFront distribution.

Image description

Adding AWS WAF Rules

With the Web ACL CloudFrontWebACL created and associated with my CloudFront distribution, I added several rules to strengthen the site’s security posture.

Managed Rule Sets Added

  1. AWSManagedRulesCommonRuleSet
    Covers a wide range of common threats like LFI, bad bots, size restrictions, and more.

  2. AWSManagedRulesAmazonIpReputationList
    Blocks traffic from known malicious IPs.

  3. AWSManagedRulesSQLiRuleSet
    Specifically targets SQL injection attempts.

  4. Custom Rate Limiting Rule – LimitRequestsByIP
    Blocks any IP that makes more than 10 requests in a 5-minute window.

For all managed rules, I set the action override to “Block” to ensure they actively drop malicious traffic rather than just counting matches.

Image description

Testing the Web ACL

This is the part where studying for the eJPT certification came in handy. To validate the effectiveness of each rule, I ran several tests using curl.

1. Rate Limiting

for i in {1..25}; do
  curl -s -o /dev/null "https://" &
done
wait

Image description

Result:
Requests beyond the 10-request threshold were blocked as expected. I confirmed this via the WAF metrics panel in CloudWatch, which showed exactly 25 blocked requests.

Image description

Image description

2. SQL Injection Attempt

curl "https:///?id=1%27%3B%20DROP%20TABLE%20users--"

Result:
The request was blocked with a 403 error, showing that the SQLiRuleSet triggered correctly.

Image description

3. SQLMap User-Agent Fingerprint

curl -A "sqlmap/1.0" https://

Result:
Blocked with a 403 error. This confirmed that malicious User-Agent headers were being filtered properly by the common rule set.

Image description

4. Path-Based Recon

curl https:///admin

Image description

Result:
Returned a 404 error from S3 (object not found), but was not blocked by WAF. This is expected — WAF only triggers on known threat patterns, not missing pages.

CloudWatch Logs Verification
To confirm that the WAF rules were actively blocking traffic, I reviewed the metrics and charts under the “WAF > Web ACLs > CloudFrontWebACL” section.

The rate limiting rule showed 25 blocked requests at the exact timestamp of my curl loop test.

SQLi and User-Agent tests were also reflected in blocked request counts under the relevant rules.

Final Thoughts

This project helped me understand how to:

  1. Deploy a static site via S3 and accelerate it globally with CloudFront

  2. Configure bucket permissions and static hosting

  3. Set up and customize AWS WAF rules

  4. Simulate attacks and observe real-time block metrics in CloudWatch

The final result is a minimal, fast, and well-protected static site — an ideal foundation for future personal projects.