【AWS】S3の静的ウェブサイトホスティング完全ガイド|CloudFrontとの組み合わせ設定

「EC2でサーバーを動かし続けるのはコストがもったいない」「静的サイトならもっと安く運用できるはず」

そう思って S3 の静的ホスティングを調べ始めたものの、S3 単体の設定だけでは HTTPS が使えない、ディレクトリ形式の URL でアクセスするとエラーになる、といった問題に直面した方も多いのではないでしょうか。

この記事では、S3 の静的ウェブサイトホスティングの基本的な設定から、CloudFront と組み合わせた本番運用に耐える構成まで、実際に構築した経験をもとに解説します。

S3 静的ホスティングを使う構成のメリット

EC2 のような常時稼働サーバーと比べた場合、S3 + CloudFront 構成には以下のメリットがあります。

  • コスト削減:S3 のストレージ費用と CloudFront のデータ転送料のみ。EC2 インスタンスの固定費が不要になる
  • スケーラビリティ:CloudFront のエッジキャッシュにより、大量アクセスにも自動対応
  • 可用性:S3 は高い耐久性を持ち、サーバーダウンの心配がない
  • メンテナンス不要:OS パッチ適用やサーバー管理が不要

一方で注意点もあります。PHPなどのサーバーサイド処理はそのままでは動かないため、動的処理が必要な場合は Lambda や API Gateway との併用が必要です。

S3 バケットの作成と静的ホスティングの設定

バケットの作成

まず S3 バケットを作成します。バケット名はグローバルで一意である必要があります。ドメイン名と同じにしておくと管理しやすいです。

バケット名例:example.com
リージョン:ap-northeast-1(東京)

重要:後述の CloudFront 経由でのアクセスを前提にする場合、バケットの「パブリックアクセスのブロック」はすべて オン(ブロックする) のままにします。パブリックアクセスを直接許可する必要はありません。

静的ウェブサイトホスティングの有効化

S3 コンソールから対象バケット → プロパティ静的ウェブサイトホスティング を編集します。

  • 静的ウェブサイトホスティング:有効にする
  • インデックスドキュメントindex.html
  • エラードキュメント404.html(任意)

設定後、バケットウェブサイトエンドポイントが発行されます。このエンドポイントは CloudFront のオリジンとして使用します。

http://example.com.s3-website-ap-northeast-1.amazonaws.com

CloudFront ディストリビューションの作成

オリジンの設定

CloudFront コンソール → ディストリビューションの作成 を開きます。

オリジンドメインには、先ほどの S3 バケットウェブサイトエンドポイントを指定します。ここが重要なポイントです。

S3 バケットをオリジンとして設定する方法は2種類あります。

方式オリジンに指定するもの特徴
S3 REST API エンドポイントexample.com.s3.amazonaws.comOAC/OAI による認証が使える。ディレクトリアクセスで index.html を返せない
S3 ウェブサイトエンドポイントexample.com.s3-website-ap-northeast-1.amazonaws.comバケットのウェブサイト機能をそのまま利用。HTTP のみ対応

静的サイトホスティングとして使う場合は ウェブサイトエンドポイント を指定します。ただし、ウェブサイトエンドポイントはHTTPのみのため、CloudFront との間の通信もHTTPになります(CloudFront ↔ ユーザー間は HTTPS)。

プロトコルポリシーHTTP のみ

キャッシュの設定

  • ビューワープロトコルポリシーRedirect HTTP to HTTPS(HTTP アクセスを HTTPS にリダイレクト)
  • キャッシュポリシーCachingOptimized(デフォルトのまま)

カスタムドメインと SSL 証明書

独自ドメインを使う場合、以下を設定します。

  • 代替ドメイン名 (CNAME)example.comwww.example.com
  • カスタム SSL 証明書:ACM(AWS Certificate Manager)で発行した証明書を選択

ACM の証明書は us-east-1(バージニア北部)リージョン で発行する必要があります。CloudFront はグローバルサービスのため、東京リージョンで発行した証明書は選択できません。

S3 バケットポリシーの設定

CloudFront 経由のアクセスのみを許可するポリシーを設定します。

ウェブサイトエンドポイントを使う場合

ウェブサイトエンドポイントを使う場合、OAC(Origin Access Control)は利用できません。CloudFront のパブリック IP からのアクセスを許可する形になります。

ただし実用上、S3 バケットへの直接アクセスを遮断したい場合は、CloudFront のマネージドプレフィックスリストを使ったバケットポリシーで制御する方法があります(設定が複雑なため、要件に応じて判断してください)。

シンプルな構成として、以下のようにパブリック読み取りを許可する方法もあります。ただしこの場合、S3 エンドポイントへの直接アクセスも可能になります。

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

パブリックアクセスブロックはオフ(許可する)にする必要があります。

S3 REST API エンドポイントを使う場合(OAC 推奨)

セキュリティをより厳密にしたい場合は、REST API エンドポイント + OAC の構成が推奨です。

CloudFront コンソールでオリジン作成時に OAC を設定すると、バケットポリシーが自動生成されます。コピーして S3 に貼り付けるだけで設定完了です。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example.com/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/XXXXXXXXXXX"
        }
      }
    }
  ]
}

ディレクトリ形式の URL で index.html を返す

/articles/laravel-tips/ のようなディレクトリ形式のURLにアクセスした際、S3 REST API エンドポイントではそのまま index.html を返してくれません。AccessDenied または NoSuchKey エラーが返ってきます。

これを解消するために、CloudFront Functions を使います。

CloudFront Functions の作成

CloudFront コンソール → 関数関数の作成 から以下のコードを設定します。

function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // URIがスラッシュで終わる場合、index.htmlを追加
    if (uri.endsWith('/')) {
        request.uri += 'index.html';
    }
    // 拡張子がない場合、/index.htmlを追加
    else if (!uri.includes('.')) {
        request.uri += '/index.html';
    }

    return request;
}

関数のデプロイ後、CloudFront ディストリビューションのビヘイビアに関連付けます。

  • イベントタイプビューワーリクエスト
  • 関数 ARN:作成した関数を選択

Route 53 でカスタムドメインを設定

Route 53 のホストゾーンに以下のレコードを追加します。

レコード名:example.com
タイプ:A(エイリアス)
エイリアスターゲット:CloudFront ディストリビューション

www ありのドメインも使う場合は、同様に CNAME または A レコード(エイリアス)を追加します。

デプロイの流れ

ファイルを更新した後の手順は以下のとおりです。

  1. 更新したファイルを S3 にアップロード(AWS CLI または AWS コンソール)
  2. CloudFront のキャッシュを無効化(インバリデーション)
# ファイルのアップロード
aws s3 sync ./public s3://example.com --delete

# キャッシュの無効化
aws cloudfront create-invalidation \\
  --distribution-id XXXXXXXXXXX \\
  --paths "/*"

--paths "/*" はすべてのキャッシュを無効化します。特定のパスのみ更新した場合は、そのパスを指定するとコストを抑えられます(1か月あたり1,000パスまで無料)。

よくあるトラブルと対処法

AccessDenied が返ってくる

原因として多いのは以下のどちらかです。

  • バケットポリシーが正しく設定されていない
  • ディレクトリ形式のURLで index.html が解決されていない(CloudFront Functions が未設定)

更新したファイルが反映されない

CloudFront のキャッシュが残っています。インバリデーションを実行してください。キャッシュの TTL を短くする方法もありますが、コストと効率のバランスを考慮して設定することをおすすめします。

SSL 証明書が選択できない

ACM の証明書が us-east-1 以外のリージョンで発行されている場合、CloudFront から選択できません。us-east-1 で改めて発行してください。

まとめ

S3 + CloudFront 構成のポイントを整理します。

  • S3 のオリジンはウェブサイトエンドポイントか REST API エンドポイントかで挙動が異なる
  • セキュリティ重視なら REST API エンドポイント + OAC
  • ディレクトリ形式の URL 対応には CloudFront Functions が必要
  • ACM 証明書は us-east-1 で発行する
  • デプロイ後はインバリデーションを忘れずに実行する

EC2 からの移行であれば、サーバー管理の手間がなくなる恩恵も大きいです。静的サイトであれば、ほぼこの構成で完結します。

AWSAWS

Posted by 千原 耕司