S3 static website hosting is surprisingly simple to set up, but most people miss the critical DNS configuration step that makes it actually work.

Let’s get a static site running on S3. We’ll use a simple HTML file and configure S3 to serve it.

First, create an S3 bucket. The bucket name must exactly match your domain name. If you want to host example.com, your bucket must be named example.com. If you want to host www.example.com, your bucket must be named www.example.com.

aws s3api create-bucket --bucket example.com --region us-east-1

Next, upload your index.html file. This is the default page that will be served when someone visits your domain.

aws s3 cp index.html s3://example.com/index.html

Now, we need to enable static website hosting on the bucket. This tells S3 to treat the bucket as a web server.

aws s3 website s3://example.com --index-document index.html --error-document error.html

You’ll also need an error.html file for 404 errors. Upload that too.

aws s3 cp error.html s3://example.com/error.html

At this point, you might think you’re done, but if you try to access http://example.com.s3-website-us-east-1.amazonaws.com/ (the endpoint S3 gives you), you’ll likely get an access denied error. This is because S3 buckets are private by default. We need to make the objects public.

The correct way to do this is with a bucket policy. This policy grants public read access to all objects in the bucket.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::example.com/*"
        }
    ]
}

Apply this policy to your bucket:

aws s3api put-bucket-policy --bucket example.com --policy file://policy.json

Now, if you access http://example.com.s3-website-us-east-1.amazonaws.com/, your index.html should load.

The crucial step for making this work with your domain, example.com, is configuring DNS. You need to create a CNAME record in your DNS provider’s settings. This CNAME record points your domain name to the S3 website endpoint.

For example, if your domain is example.com, you would create a CNAME record:

  • Name/Host: example.com
  • Type: CNAME
  • Value/Target: example.com.s3-website-us-east-1.amazonaws.com (replace us-east-1 with your bucket’s region)

If you’re using www.example.com, the setup is similar:

  1. Create a bucket named www.example.com.
  2. Upload index.html and error.html.
  3. Enable static website hosting.
  4. Apply the public read bucket policy.
  5. Create a CNAME record in your DNS:
    • Name/Host: www.example.com
    • Type: CNAME
    • Value/Target: www.example.com.s3-website-us-east-1.amazonaws.com

A common pitfall is trying to use an A record with an Alias for S3 websites, especially when you’re trying to host a root domain like example.com. While Route 53 supports Alias records for S3, other DNS providers might not, or they might require specific configuration. The CNAME is the most universally applicable method.

The most surprising thing about S3 static website hosting is that the aws s3 website command doesn’t actually make the content publicly accessible; it only configures S3 to act like a web server. The bucket policy is what grants the actual read permissions to the objects within.

Once your DNS has propagated, visiting http://example.com (or http://www.example.com) should now serve your index.html file.

The next hurdle is typically securing your site with HTTPS, which requires integrating CloudFront.

Want structured learning?

Take the full S3 course →