心配性なシステム運用日記
【CloudFront】複数オリジン構成でS3をディレクトリごとに分割配信する
2026.07.03 更新

【CloudFront】複数オリジン構成でS3をディレクトリごとに分割配信する

こんにちは。
今回は、複数オリジンでCloudfrontを構成し、階層ごとに違うオリジンを構成する手順を紹介します。
具体的には、sample.comの場合にS3-トップ用、sample.com/blogの場合にs3-ブログ用を参照するというものです。

すでに複数のS3静的サイトが存在し、それを単一ドメインに統合したい場合に有効となります。

やったこと概要

前提として、S3バケットを2つ用意します。
このブログでは、ルートドメイン(sample.com)用とサブディレクトリ(sample.com/blog)用の2つのS3バケットをもつと仮定します。
それぞれ <s3パス>/<フォルダ>/index.html のように、フォルダ配下にindex.htmlを持つ状態としています。

しかし、そのまま設定すると、S3②へのリクエスト時に /blog というパスがそのままS3側のパスとして評価されてしまいます。
したがって、そのままではS3側に blog フォルダを格納するか、しない場合はなんらかの措置が必要になる事象が発生しました。

また、sample.com/aaaにリクエストする場合に、暗黙として存在するindex.htmlを勘案せず、<s3>/<フォルダ>にアクセスしようとし、アクセスエラーとなる事象も発生します。

cloudfront-problem

そこで、sample.com/blogへのパストラブル対応をCloudfront Functionsで行います。
sample.com/blog/aaaの場合は、<s3パス-ブログ用>/aaa/index.html のように、blog のパスを取り除くようにマッピングさせる対策をとります。

add-cloudfront-functions

以降は詳細の手順を紹介します。

手順

複数オリジンでCloudfrontを構成し、sample.comの場合にS3-トップ用、sample.com/blogの場合にs3-ブログ用を参照するような手順を示します。

S3の作成

必要な2つのS3バケットを用意します。
ここではそれぞれのバケット・展開したいパスごとにindex.htmlを配置しておきます。

s3
├── バケット-トップ用
│   ├── aaa
│   │   └── index.html
│   └── bbb
│       └── index.html
└── バケット-ブログ用
    ├── ccc
    │   └── index.html
    └── ddd
        └── index.html

Cloudfrontディストリビューション・オリジン設定

Cloudfrontの基本的な立ち上げを行います。
ディストリビューションを新規作成し、オリジン設定から、s3-トップ用とs3-ブログ用の2つをオリジンとして作成します。
ディストリビューション作成時、一個オリジンを指定できます。

add-distribution

もう一個はディストリビューション作成後にオリジン作成から追加をしていきます。

add-origin

また、CloudfrontからS3へのアクセスを許可するため、バケットポリシーの設定を行います。
通常オリジンにS3を設定すると、自動でアクセス許可の設定がされますが、設定されていない場合は下記を追加します。
S3→アクセス許可→バケットポリシーにて、アクセス許可ポリシーを追加します。

{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "<S3バケットARN名>/*",
            "Condition": {
                "ArnLike": {
                    "AWS:SourceArn": "<cloudfrontARN名>"
                }
            }
        }
    ]
}

ビヘイビアの設定

sample.comの場合にS3-トップ用へ、sample.com/blogの場合にS3-ブログ用へ分岐されるように、ビヘイビアを設定していきます。
2つのビヘイビアを設定し、パスパターンとオリジンを以下のように割り当てます。

  • デフォルト:S3-トップ用
  • /blog:S3-ブログ用

edit-behavior

ただしここまでの設定では、S3参照時におけるblogパス問題とindex.html不足問題が解決せず、sample.com/blog/aaa<s3-ブログ用>/blog/aaaにマッピングされてしまいます。
次の項で<s3-ブログ用>/aaa/index.htmlとなるように補正していきます。

Cloudfront Functionを設定

2つの問題を解消するために、Cloudfront Functionを設定していきます。

  • ルール1(index.html)
    sample.com/aaaを呼び出したときは、<s3-トップ用>/aaa/index.html を割り当てます。
  • ルール2(blogパス)
    sample.com/blog/aaaの場合は、<s3-ブログ用>/aaa/index.htmlのように、blogのパスを取り除くようにマッピングさせる対策をとります。
function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // --- 【事前処理】URIの正規化 (301リダイレクト) ---
    
    // パターンA: 完全な「/blog」アクセスの場合は「/blog/」へリダイレクト
    if (uri === '/blog') {
        return {
            statusCode: 301,
            statusDescription: 'Moved Permanently',
            headers: {
                'location': { value: '/blog/' } 
            }
        };
    }

    // パターンB: 下層ページで末尾にスラッシュも拡張子もない場合はスラッシュを付与
    if (!uri.includes('.') && !uri.endsWith('/')) {
        return {
            statusCode: 301,
            statusDescription: 'Moved Permanently',
            headers: {
                'location': { value: uri + '/' } 
            }
        };
    }

    // --- 【ルール2】 blogパスの除外 ---
    
    // 「/blog/」から始まるURIの場合、S3へのリクエストパスから「/blog」を取り除く
    if (uri.startsWith('/blog/')) {
        uri = uri.replace('/blog/', '/');
    }

    // --- 【ルール1】 index.htmlの補完 ---
    
    // 拡張子(.htmlや.cssなど)が含まれている場合はそのままリクエスト
    if (uri.includes('.')) {
        request.uri = uri;
    }
    // 末尾がスラッシュで終わっている場合は「index.html」を付与
    else if (uri.endsWith('/')) {
        request.uri = uri + "index.html";
    }

    return request;
}

このFunctionを2つビヘイビアに割り当てることで、意図した通りのパスでアクセスできるようになります。

おわりに

今回は、Cloudfrontで複数オリジンを構成し、階層ごとに違うオリジンを割り当てる手順を構築しました。
パスがそのまま引き継がれる仕様に対しては、Cloudfront Functionsを用いることで対応可能です。
オリジンとウェブサイトが多対多の複雑な構造でも拡張可能であり、非常に便利だと思います。

それでは。

Xでシェア
Menu