S3 で静的 Web サイトをホスティングする

概要

HTML/CSS/JS だけで完結する静的サイト(ランディングページ、製品ドキュメント、SPA のビルド成果物など)は、サーバーを立てずに Amazon S3 でホスティングできる。本記事では次の 2 パターンをハンズオン形式で構築する。

  1. S3 の「静的ウェブサイトホスティング」機能による最小構成(学習・検証向け)
  2. CloudFront + OAC(Origin Access Control)でバケットを非公開のまま配信する本番向け構成(HTTPS・独自ドメイン対応)

結論として、本番運用なら 2 を選ぶ。1 は HTTPS に対応せず、バケットを公開設定にする必要があるためだ。まず 1 で仕組みを理解し、そのうえで 2 へ移行する流れで進める。

環境

  • AWS CLI v2(aws --version で確認)
  • 認証情報が設定済み(aws configure 済み、または SSO ログイン済み)
  • リージョンは ap-northeast-1(東京)を例に使用
  • バケット名は全世界で一意である必要があるため、本記事では example-static-site-20260912 のようなプレースホルダを使う

パターン1: S3 静的ウェブサイトホスティング(最小構成)

1. バケットを作成する

aws s3api create-bucket \
  --bucket example-static-site-20260912 \
  --region ap-northeast-1 \
  --create-bucket-configuration LocationConstraint=ap-northeast-1

ap-northeast-1 など us-east-1 以外のリージョンでは --create-bucket-configuration LocationConstraint=... が必須。us-east-1 の場合は逆に付けるとエラーになる。

2. コンテンツを用意してアップロードする

mkdir -p site
cat > site/index.html <<'HTML'
<!doctype html>
<html lang="ja">
<head><meta charset="utf-8"><title>Static on S3</title></head>
<body><h1>Hello from S3</h1></body>
</html>
HTML

cat > site/error.html <<'HTML'
<!doctype html>
<html lang="ja">
<head><meta charset="utf-8"><title>Not Found</title></head>
<body><h1>404 Not Found</h1></body>
</html>
HTML

aws s3 sync ./site s3://example-static-site-20260912

3. 静的ウェブサイトホスティングを有効化する

aws s3 website s3://example-static-site-20260912 \
  --index-document index.html \
  --error-document error.html

4. パブリックアクセスを許可する

新規バケットはデフォルトで全公開がブロックされている。パターン1 では直接アクセスを許可する必要があるため、ブロックを解除してからバケットポリシーを付与する。

aws s3api put-public-access-block \
  --bucket example-static-site-20260912 \
  --public-access-block-configuration \
  BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false
cat > policy.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-static-site-20260912/*"
    }
  ]
}
JSON

aws s3api put-bucket-policy \
  --bucket example-static-site-20260912 \
  --policy file://policy.json

5. アクセスして確認する

ウェブサイトエンドポイントは次の形式になる(リージョンによってドメインが異なる)。

http://example-static-site-20260912.s3-website-ap-northeast-1.amazonaws.com/
curl -I http://example-static-site-20260912.s3-website-ap-northeast-1.amazonaws.com/
# HTTP/1.1 200 OK が返ればOK

このパターンの限界

  • HTTPS 非対応。ウェブサイトエンドポイントは HTTP のみ
  • バケットを公開する必要がある。セキュリティ観点では避けたい
  • アクセスログ・キャッシュ・地理制限などの制御ができない

学習用途としては十分だが、本番では次のパターン2 に進む。

パターン2: CloudFront + OAC(バケット非公開のまま HTTPS 配信)

構成の考え方

  • S3 バケットは非公開のままにする(パブリックアクセスブロックは全部 ON のまま)
  • CloudFront ディストリビューションを前段に置き、OAC で CloudFront からのみ S3 を読めるようにする
  • HTTPS は CloudFront のデフォルト証明書、独自ドメインなら ACM 証明書を使う

かつては OAI(Origin Access Identity)が使われていたが、現在は OAC が推奨。SSE-KMS 暗号化バケットや新しいリージョンにも対応する。新規構築では OAC を選ぶ。

1. バケットは非公開のまま作る

パターン1 の手順4(パブリックアクセス許可とバケットポリシー)は実施しない。パブリックアクセスブロックは ON のままにしておく。オリジンとして使うため、静的ウェブサイトホスティング(手順3)も不要で、通常の S3 バケットとして扱う。

2. OAC を作成する

aws cloudfront create-origin-access-control \
  --origin-access-control-config \
  Name=example-static-site-oac,SigningProtocol=sigv4,SigningBehavior=always,OriginAccessControlOriginType=s3

返ってくる Id(OAC の ID)を控えておく。

3. CloudFront ディストリビューションを作成する

マネジメントコンソールで作る場合は次を指定する。

  • オリジンドメイン: example-static-site-20260912.s3.ap-northeast-1.amazonaws.com(ウェブサイトエンドポイントではなく REST API エンドポイントを選ぶ)
  • オリジンアクセス: Origin access control settings を選び、手順2 の OAC を指定
  • ビューアプロトコルポリシー: Redirect HTTP to HTTPS
  • デフォルトルートオブジェクト: index.html

CLI で作る場合は distribution-config.json を組み立てて aws cloudfront create-distribution --distribution-config file://distribution-config.json を実行する。項目が多いため、初回はコンソールで作って設定を把握するのがおすすめ。

4. バケットポリシーで CloudFront からのアクセスだけ許可する

CloudFront ディストリビューションを作ると、コンソールに「S3 バケットに貼るべきポリシー」が表示される。内容は次の形になる。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontServicePrincipal",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-static-site-20260912/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::<ACCOUNT_ID>:distribution/<DISTRIBUTION_ID>"
        }
      }
    }
  ]
}

このポリシーにより「特定の CloudFront ディストリビューション経由のリクエストだけ」が GetObject を許可される。バケット自体は非公開のままだ。

5. 独自ドメインと HTTPS(任意)

  1. ACM で証明書を発行する。CloudFront で使う証明書は us-east-1(バージニア北部)で発行する必要がある(東京リージョンではない)
  2. CloudFront ディストリビューションの Alternate domain name (CNAME) に独自ドメインを追加し、証明書を紐付ける
  3. Route 53 で A レコード(Alias)を CloudFront ディストリビューションに向ける
aws acm request-certificate \
  --domain-name www.example.com \
  --validation-method DNS \
  --region us-east-1

実装例: デプロイと更新の自動化

# 1. ビルド成果物を同期(不要ファイルは削除、キャッシュ制御ヘッダを付与)
aws s3 sync ./dist s3://example-static-site-20260912 \
  --delete \
  --cache-control "public, max-age=31536000, immutable" \
  --exclude "index.html"

# 2. index.html は短めのキャッシュにする(内容が変わるため)
aws s3 cp ./dist/index.html s3://example-static-site-20260912/index.html \
  --cache-control "public, max-age=60"

# 3. CloudFront のキャッシュを無効化
aws cloudfront create-invalidation \
  --distribution-id <DISTRIBUTION_ID> \
  --paths "/*"

ハッシュ付きのアセット(app.abc123.js など)は長期キャッシュ、index.html は短期キャッシュにするのがセオリー。無効化リクエストは無料枠を超えると課金対象なので /* の乱発には注意する。

確認方法

  • パターン1: ウェブサイトエンドポイントに curl -I して 200 OK を確認。存在しないパスで error.html が返るかも確認する
  • パターン2: CloudFront ドメイン(dxxxxxxxxxxxxx.cloudfront.net)または独自ドメインに HTTPS でアクセスし、200 が返ること、S3 の REST エンドポイントに直アクセスすると AccessDenied になること(=バケットが非公開)を確認する
# 非公開が効いているかの確認(AccessDenied が返れば正しい)
curl -I https://example-static-site-20260912.s3.ap-northeast-1.amazonaws.com/index.html

注意点

  • バケット名は世界で一意。プレフィックス+日付などで一意性を確保する
  • CloudFront 用の ACM 証明書は us-east-1 のみ。ここで詰まる人が多い
  • SPA のルーティングを使う場合、存在しないパスで 403/404 になる。CloudFront の「カスタムエラーレスポンス」で 403/404 を /index.html・ステータス 200 に書き換える
  • パブリックアクセスブロックの扱い。パターン2 では絶対に解除しない。解除すると OAC で守っている意味がなくなる
  • キャッシュ無効化のコスト。デプロイのたびに /* を無効化すると無料枠を超える可能性がある。パスを絞るか、ファイル名にハッシュを含めて無効化自体を減らす

まとめ

  • 静的サイトのホスティングは、まず S3 の静的ウェブサイトホスティングで仕組みを理解できる
  • HTTPS・独自ドメイン・バケット非公開を満たすなら CloudFront + OAC が本番の定番構成
  • ACM 証明書は us-east-1、SPA はカスタムエラーレスポンス、キャッシュ無効化のコスト、この 3 点が主なハマりどころ
  • デプロイは aws s3 sync --delete +キャッシュ制御ヘッダ + CloudFront invalidation をセットで自動化する

サーバーレスで低コスト、スケールを気にせず配信できるのが S3 ホスティングの強み。最小構成から始めて、要件に応じて CloudFront を前段に足していくのが実務的な進め方だ。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。