Build Log · Infrastructure

Standing up automate-networks.com

A static site on Route 53, ACM, S3, and CloudFront — built by hand in the console as a hands-on run at AWS SAA-C03, and as the first real estate for this site itself.

Stack: Route 53 → ACM → S3 → CloudFront Monthly cost: ~$0.50 + domain Support plan: Basic
Hop 01 · Account & access

New account, root locked down

Signed up a dedicated AWS account, separate from anything work-related. MFA on root first, then an IAM user (Kosichd) with AdministratorAccess for daily work — root only comes out for account-level actions from here on. A $5 monthly budget alarm went on before anything else got built, purely as a guardrail against a misconfiguration turning into a surprise bill.

Gotcha New AWS accounts now start on a Free Plan that blocks paid services outright — domain registration included. Upgrading to Paid Plan doesn't need a new payment method, but the upgrade action itself is root-only; it fails silently for IAM users even with full admin rights.
Hop 02 · Domain

Registering the domain in Route 53

Route 53 registers the domain and provisions the hosted zone in one step. First attempt failed on contact validation — Route 53 requires phone numbers in strict +1.XXXXXXXXXX format (E.164, period after the country code), not the usual dashed or parenthesized format most forms accept.

Gotcha A rejected phone format fails all four contact roles at once — admin, owner, tech, and billing pull from the same form.
Hop 03 · Certificate

TLS via ACM — in the right region

CloudFront only reads certificates issued in us-east-1, regardless of where anything else in the account lives. Requested a public certificate covering both automate-networks.com and www.automate-networks.com as Subject Alternative Names on a single cert — one certificate, both names, no need to issue twice. DNS validation, with Route 53 writing its own validation records via a one-click button.

Hop 04 · Origin

A private S3 bucket as the origin

Bucket named to match the domain, Block Public Access left fully on. The bucket is never meant to be reachable directly — only CloudFront, and only on behalf of this specific distribution, should ever read from it. index.html uploaded to the root as a placeholder to validate the pipeline end to end before writing real content.

Hop 05 · Edge

CloudFront distribution

S3 origin, Origin Access Control for the private-bucket handshake, both domain names attached as alternate domain names, the ACM cert selected, HTTP forced to HTTPS. The newer console wizard also offered to write the Route 53 alias records directly from the distribution's settings page — a nice shortcut over creating them by hand.

Distribution deployed · d2dgvai9l2ag8e.cloudfront.net
Hop 06 · DNS

Routing both names to the edge

Alias A/AAAA records for both the apex and www, pointed at the CloudFront distribution — no IP addresses involved, Route 53 resolves straight to the edge and updates automatically if the distribution's endpoint ever changes.

Hop 07 · Incident

Chasing an AccessDenied that wasn't what it looked like

First real request to the domain came back as a bare AccessDenied XML straight from S3 — no styling, no CloudFront framing, meaning the request reached the origin but got rejected there. Four hypotheses, checked in order:

$ diagnose --target automate-networks.com
1Origin Access Control signing configurationOK
2S3 bucket policy grants CloudFront's OAC principalOK
3Origin domain is the REST endpoint, not the website endpointOK
4index.html present at bucket rootOK
5Default root object set on the distributionFAIL
Root cause: the distribution had no default root object configured. A request for the bare domain carries no filename, and without index.html declared as the default, CloudFront forwards an empty key to S3. Since the bucket policy intentionally doesn't grant ListBucket, S3 answers a missing key with AccessDenied rather than a plain 404 — so a routing gap and a permissions failure produce an identical symptom. Setting the default root object resolved it immediately, no cache invalidation required.

The field is labeled "optional" in the console, which is true in general — plenty of architectures don't need it. For a static site meant to answer requests at the bare domain, it's effectively load-bearing.