Ever needed to block access to an S3-hosted static website while keeping it ready to quickly re-open with IP restrictions? Maybe you're doing maintenance, testing, or need to comply with security requirements.
This guide shows you how to enable S3 website hosting but block access for everyone using S3 Public Access Block, while keeping an IP allowlist policy ready for when you want to selectively re-open access.
Prerequisites
Before we dive in, make sure you have:
- AWS CLI configured with appropriate permissions
- Terraform installed (v1.0+)
- Basic understanding of S3 bucket policies and Public Access Block settings
The Solution
Here's the complete Terraform configuration that achieves this:
# Define your allowed IP ranges
locals {
allowed_cidr = [
"203.0.113.10/32", # Your office IP
"198.51.100.0/24", # Your VPN subnet
"192.0.2.0/24" # Additional trusted network
]
}
# Create the S3 bucket
resource "aws_s3_bucket" "website" {
bucket = "your-static-website-bucket-name"
}
# Public Access Block: this is what *blocks* the website endpoint
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = true
block_public_policy = true # <- This is the key setting that blocks access
ignore_public_acls = true
restrict_public_buckets = true
}
# Enable website hosting (index/error pages)
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
# Bucket policy with IP allowlist (kept ready for when you unblock)
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowIPAccess"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
Condition = {
IpAddress = {
"aws:SourceIp" = local.allowed_cidr
}
}
}
]
})
depends_on = [aws_s3_bucket_public_access_block.website]
}
How It Works
Let's break down each component:
1. Public Access Block Configuration
The aws_s3_bucket_public_access_block
resource is the key to blocking access. When block_public_policy = true
, it prevents any bucket policy from granting public access, effectively making your website return AccessDenied
(403) to all visitors.
2. Website Configuration
The aws_s3_bucket_website_configuration
enables the S3 website hosting feature, defining your index and error pages. This configuration remains active even when the site is blocked.
3. IP Allowlist Policy
The bucket policy defines which IP addresses can access your content. While block_public_policy = true
prevents this policy from taking effect, it's ready to activate when you need it.
Current State
With this configuration deployed:
- ✅ Website endpoint is blocked - Returns
AccessDenied
(403) to all visitors - ✅ IP allowlist policy is ready - Present but inactive due to public policy block
- ✅ Website hosting remains enabled - Configuration stays in place
Re-opening with IP Restrictions
When you're ready to allow access from specific IPs:
- Update the Terraform configuration:
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = true
block_public_policy = false # <- Change this to false
ignore_public_acls = true
restrict_public_buckets = true
}
- Apply the changes:
terraform apply
-
Verify access:
- Website loads for IPs in
local.allowed_cidr
- Returns
AccessDenied
for all other IPs
- Website loads for IPs in
Troubleshooting
Common Issues
Issue: Website still accessible after setting block_public_policy = true
- Solution: Ensure you're testing the website endpoint URL, not the S3 bucket URL directly
Issue: IP allowlist not working after unblocking
-
Solution: Verify your IP ranges in
local.allowed_cidr
are correct and include your current IP
Issue: Terraform apply fails with policy conflicts
-
Solution: Make sure
depends_on = [aws_s3_bucket_public_access_block.website]
is set on the bucket policy
Testing Your Setup
# Test from an allowed IP
curl -I https://your-bucket-name.s3-website-us-east-1.amazonaws.com/
# Test from a blocked IP (should return 403)
# Use a VPN or different network to test
Security Considerations
-
Keep other Public Access Block settings enabled - Only change
block_public_policy
when needed - Regularly review your IP allowlist - Remove unused IPs and update ranges as needed
- Monitor access logs - Enable S3 access logging to track who's accessing your site
-
Use least privilege - Only grant
s3:GetObject
permission, not broader S3 access
Next Steps
- Consider implementing CloudFront for additional security layers
- Set up monitoring alerts for unauthorized access attempts
- Automate IP allowlist updates using AWS Systems Manager Parameter Store
Full example repository: tf-aws-s3-static-website-restricted-access
Have you used similar techniques for managing S3 website access? Share your experiences in the comments below! 🚀
Top comments (0)