はじめに

Webサイトを運用しているとリダイレクト設定の数が増えていき、気がついたら数千、数万になっていることがあるかと思います。

本記事では、数千単位のリダイレクト設定にどのような課題があり、それをどう解決できるか、いくつかの選択肢を比較しながら、AWS の Lambda@Edge を用いた具体的な実装手順を紹介します。

従来の大量リダイレクト設定とその課題

まずは、これまでよく用いられてきたリダイレクト設定方法と、大量設定時に顕在化する課題を見ていきましょう。

Web サーバー (Apache / Nginx) での設定

従来、リダイレクト設定は Web サーバーソフトウェア (Apache httpd や Nginx など) の設定ファイルに記述する方法が一般的でした。

これらの方法は、少数のリダイレクト設定であればシンプルで有効です。しかし、リダイレクトルールが数千単位になると、以下のような課題が生じます。

  • パフォーマンスへの影響: リクエスト毎に長大な設定ファイルが評価されるため、サーバーの応答速度に影響を与える可能性があります。特に正規表現を多用する場合は注意が必要です。
  • リソース負荷:サーバー内で設定する場合、リダイレクト設定のファイルやバックアップを保存するディスクの使用や、リダイレクト処理のCPU、メモリの使用があります。ルールが大量になると無視できない範囲になる可能性もあります。
  • スケール時の対応: サーバー台数を増やす場合、全ての設定を同期する必要があり、管理が複雑になります。
  • CDN 転送コスト: リダイレクト先のコンテンツがCDNのエッジキャッシュにない場合、CDNはオリジンサーバーへコンテンツを取得しにいくため、CDNの転送料が発生します。リダイレクトが多段になるほど、これらの通信回数が増加し、結果としてCDNの転送料も増加する傾向にあります。

解決策の選択肢と比較

大量のリダイレクト設定の課題を解決するための選択肢として、CloudFront Functions 、Lambda@Edge が有力です。それぞれの特徴を比較してみましょう。

項目 Web サーバー (Apache/Nginx) CloudFront Functions Lambda@Edge
設定の柔軟性 中程度(正規表現など) 中程度 (JavaScript) 非常に高い (Node.js, Python など)
パフォーマンス 設定数や複雑性に依存 非常に高い (低レイテンシー) 高い (エッジで実行)
スケーラビリティ 手動でのサーバー管理が必要 自動 自動
パッケージサイズ制限 なし (サーバーリソースに依存) 10KB 1MB (zip、ビューワーリクエスト/レスポンス)
実行時間制限 なし (リクエストタイムアウト依存) 1ms 未満 (CPU時間) 5秒 (ビューワーリクエスト/レスポンス)
コスト サーバーインスタンス費用 実行回数 (比較的安価)
月間200万リクエストまで無料
実行回数、実行時間 (Functions より高価)
ルール更新の容易さ 設定変更後、サーバー再起動/リロード 関数コード更新、デプロイ 設定ファイルを外部化すれば容易

※ CloudFront Functions と Lambda@Edge のパッケージサイズと実行時間の制限について
https://aws.amazon.com/jp/blogs/aws/introducing-cloudfront-functions-run-your-code-at-the-edge-with-low-latency-at-any-scale/

Lambda@Edge を選定する理由

上記の比較から、数千単位のリダイレクトルールを扱う場合、Lambda@Edge が最も適した選択肢と言えます。その主な理由は以下の通りです。

  • CloudFront Functions の制限を超える柔軟性:
    • パッケージサイズ: Lambda@Edge はビューワーリクエスト/レスポンス関数で 1MB (zip 形式) までのパッケージサイズを許容します (CloudFront Functions は 10KB)。これにより、大量のリダイレクトルールをファイルとして同梱したり、より複雑な処理ロジックを実装したりすることが可能です。
    • 実行時間: Lambda@Edge はビューワーリクエスト/レスポンス関数で 5秒 までの実行時間を許容します (CloudFront Functions は 1ms 未満)。これにより、大量のデータからリダイレクト先を検索するような処理も現実的に行えます。
  • スケーラビリティとパフォーマンス: CloudFront のグローバルなエッジネットワーク上で実行されるため、ユーザーに近い場所で高速にリダイレクト処理が行われ、オリジンサーバーへの負荷も軽減されます。
  • 管理の効率化: リダイレクトルールを Lambda 関数コード内ではなく、S3 バケットなどに外部ファイル (例: JSON, CSV) として保存し、Lambda 関数からそれを読み込むように実装することで、ルール追加・変更時に Lambda 関数のコード自体を修正・再デプロイする手間を省き、運用負荷を大幅に軽減できます。

本記事では、数千単位のリダイレクト設定を扱うことを想定しているため、パッケージサイズ、実行時間を考慮し、 Lambda@Edge を用いた実装手順を解説します。

Lambda@Edge を用いた大量リダイレクトの実装手順

ここからは、Lambda@Edge を利用して大量のリダイレクトルールを効率的に処理する具体的な手順を説明します。今回は、リダイレクトルールを記述したファイルを S3 に配置し、Lambda@Edge 関数がそのファイルを読み込んでリダイレクト処理を行う構成を想定します。

前提条件

  • CloudFront ディストリビューションが設定済みであること 。
  • リダイレクト元のドメインとリダイレクト先のドメインが準備できていること。
  • リダイレクト元とリダイレクト先の対応リスト (CSV や JSON 形式) が用意できること。

STEP 1: リダイレクト設定用 S3 バケットの準備

リダイレクトルールを Lambda 関数内に直接ハードコードすると、ルールの追加や変更のたびに Lambda 関数を更新・デプロイする必要があり非効率です。そのため、リダイレクトルールを S3 バケットにファイルとして格納し、Lambda 関数から読み込む方式を推奨します。

  1. S3 バケットを作成し、リダイレクトルールを記述したファイル (例: redirects.jsonredirects.csv) をアップロードします。
    今回は例として、以下のような JSON 形式のファイルを redirects.json としてアップロードします。
    {
      "/old-path/article1.html": "/new-path/article1.html"
    }
    

STEP 2: IAM ロールの作成

Lambda@Edge 関数が S3 バケットからリダイレクト設定ファイルを読み取り、CloudWatch Logs にログを書き込むための権限を持つ IAM ロールを作成します。

  1. 詳細は割愛しますが、ユースケースでLambdaを選択し、以下のような許可ポリシーで作成します。
  2. 信頼ポリシーを以下の通りに修正し、lambdaedge からも信頼されるようにします。

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Service": [
                        "lambda.amazonaws.com",
                        "edgelambda.amazonaws.com"
                    ]
                },
                "Action": "sts:AssumeRole"
            }
        ]
    }
    

STEP 3: Lambda@Edge 関数の作成

次に、リダイレクト処理を行う Lambda 関数を作成します。Lambda@Edge 関数はバージニア北部 (us-east-1) リージョンで作成する必要がある点に注意してください。

  1. Lambda コンソールを開き、リージョンが バージニア北部 (us-east-1) になっていることを確認します。
  2. 関数作成画面で、ランタイムを選択し、STEP 2 で作成した IAM ロールを選択し、関数を作成をクリックします。
  3. 作成された関数のコードソース部分に、リダイレクトロジックを実装します。以下は Node.js での実装例の骨子です。
    (実際のコードは、S3 から redirects.json を読み込み、リクエストされた URI と照合し、一致すれば 301 リダイレクトレスポンスを生成する処理を記述します。)
    import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
    
    // S3クライアントを初期化 (redirects.jsonを保存したS3バケットが存在するリージョンを指定してください。)
    const s3 = new S3Client({ region: 'ap-northeast-1' });
    
    // ★★★ 設定項目: 実際のS3バケット名とオブジェクトキーに置き換えてください ★★★
    const BUCKET_NAME = 'YOUR_S3_BUCKET_NAME'; // 例: 'my-redirect-rules-bucket'
    const OBJECT_KEY = 'redirects.json';       // 例: 'prod/redirects.json'
    // ★★★ 設定項目ここまで ★★★
    
    // リダイレクトルールをキャッシュするためのグローバル変数
    let redirectRules = null;
    
    /**
    * S3からリダイレクトルールを読み込み、キャッシュする関数
    */
    async function loadRedirectRules() {
        // 既にキャッシュされていればそれを返す
        if (redirectRules) {
            console.log('Returning cached redirect rules.');
            return redirectRules;
        }
    
        try {
            console.log(`Loading redirect rules from S3: s3://${BUCKET_NAME}/${OBJECT_KEY}`);
            const params = {
                Bucket: BUCKET_NAME,
                Key: OBJECT_KEY,
            };
            const command = new GetObjectCommand(params);
            const data = await s3.send(command);
            const rulesString = await data.Body.transformToString(); // ストリームを文字列に変換
            redirectRules = JSON.parse(rulesString);
            console.log('Redirect rules loaded and parsed successfully.');
            return redirectRules;
        } catch (err) {
            console.error('Error loading redirect rules from S3:', err);
            // エラー発生時はリダイレクト処理を行わないように空のルールを返す
            // 必要に応じて、エラー時のフォールバック処理をここに追加することも可能
            redirectRules = {}; // 空のルールで初期化して次回以降のS3アクセスを抑制
            return redirectRules;
        }
    }
    
    /**
    * Lambda@Edgeのメインハンドラ関数 (ビューワーリクエストイベントを想定)
    */
    export const handler = async (event) => {
        // console.log('Received event:', JSON.stringify(event, null, 2)); // デバッグ用にイベント全体をログ出力
    
        const rules = await loadRedirectRules(); // リダイレクトルールをロード (キャッシュ利用)
    
        const request = event.Records[0].cf.request;
        const uri = request.uri;
        const querystring = request.querystring || ""; // クエリ文字列がない場合は空文字
    
        // redirects.jsonのキーと比較するためのパスを作成
        // ブログ記事の例ではクエリ文字列を含めて照合する形が示唆されていました。
        // もしクエリ文字列を除いたURIのみで照合し、クエリ文字列はリダイレクト先に引き継ぎたい場合は、
        // ここのロジックと `redirects.json` のキーの持ち方を調整してください。
        const fullPath = querystring !== '' ? `${uri}?${querystring}` : uri;
    
        console.log(`Request URI: ${uri}`);
        console.log(`Request Querystring: ${querystring}`);
        console.log(`Looking for redirect rule for: ${fullPath}`);
    
        if (rules && rules[fullPath]) {
            const newLocation = rules[fullPath];
            console.log(`Redirecting from '${fullPath}' to '${newLocation}'`);
    
            const response = {
                status: '301',
                statusDescription: 'Moved Permanently',
                headers: {
                    'location': [{
                        key: 'Location',
                        value: newLocation,
                    }],
                    'cache-control': [{ // ブラウザやCDNでのリダイレクト結果のキャッシュ期間
                        key: 'Cache-Control',
                        value: 'max-age=3600' // 例: 1時間 (3600秒)
                    }],
                    // 必要に応じてセキュリティ関連のヘッダーなどを追加できます
                    // 'strict-transport-security': [{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains' }],
                },
            };
            return response; // リダイレクトレスポンスを返す
        }
    
        // リダイレクトルールに一致しない場合は、リクエストをそのままオリジンへ渡す
        console.log(`No redirect rule found for '${fullPath}'. Passing request to origin.`);
        return request;
    };
    

    注意点:

    • Lambda@Edge は CloudFront の4つのイベント (ビューワーリクエスト、ビューワーレスポンス、オリジンリクエスト、オリジンレスポンス) でトリガーできます。リダイレクト処理は、オリジンに到達する前に処理を行う「ビューワーリクエスト」イベントでトリガーするのが一般的です。
    • S3 から設定ファイルを読み込む際、Lambda 関数の実行コンテキストが再利用 (ウォームスタート) されることを考慮し、グローバルスコープで一度読み込んだルールをキャッシュする実装にすると効率的です。
    • リダイレクトルールの数が多い場合、単純なオブジェクトキー検索 (例: redirectRules[fullPath]) ではなく、より効率的な検索アルゴリズム (例: 部分一致検索、正規表現検索など) が必要になる場合があります。その場合は、Lambda 関数の実行時間が長くなる可能性も考慮し、処理を最適化してください。

STEP 4: Lambda 関数のデプロイとバージョン発行

Lambda@Edge として CloudFront にデプロイするには、Lambda 関数のバージョンを発行する必要があります。

  1. 「アクション」から「新しいバージョンを発行」を選択します。
  2. (任意) バージョンの説明を入力し、「発行」をクリックします。
    Lambda 関数の新しいバージョン (例: $LATEST ではなく 1, 2 など) が発行されたことを確認できます。発行されたバージョンの ARNを控えておきます。

STEP 5: CloudFront トリガーの設定

作成・発行した Lambda 関数を CloudFront ディストリビューションに関連付けます。

  1. CloudFront コンソールに移動し、対象のディストリビューションを選択します。
  2. 「ビヘイビア」> 対象のビヘイビアを選択 > 「編集」 > 「関数との関連付け 」セクションまでスクロールします。
  3. 「ビューワーリクエスト」の「関数タイプ」で Lambda@Edge を選択し、「関数 ARN」に、STEP 4 で控えた Lambda 関数の ARN を貼り付けます。
  4. 「Save changes」をクリックし、CloudFront ディストリビューションの変更がデプロイされるまで待ちます。

STEP 6: 動作確認

CloudFront のデプロイが完了したら、実際にリダイレクトが正しく動作するか確認します。

  1. S3 にアップロードした redirects.json に定義されているリダイレクト元の URL にブラウザでアクセスします。
  2. ブラウザの開発者ツール (ネットワークタブ) や curl コマンドで、HTTP ステータスコードが 301 Moved Permanently であり、Location ヘッダーに正しいリダイレクト先 URL (/new-path/article1.html) が指定されていることを確認します。

    curl -I https://YOUR_CLOUDFRONT_DOMAIN/old-path/article1.html
    


curl 実行例

y-ishikawa@y-ishikawa1:~$ curl -i https://y-ishikawa-test.com/old-path/article1.html
HTTP/1.1 301 Moved Permanently
Content-Length: 0
Connection: keep-alive
Server: CloudFront
Date: Sun, 01 Jun 2025 04:08:48 GMT
Location: /new-path/article1.html
Cache-Control: max-age=3600
X-Cache: LambdaGeneratedResponse from cloudfront
Via: 1.1 xxx.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: KIX56-C2
X-Amz-Cf-Id: xxx

y-ishikawa@y-ishikawa1:~$ 

指定した古い URL へのアクセスが、redirects.json に基づいて新しい URL へ正しく 301 リダイレクトされることが確認できました。

管理のリスク・運用負荷・コストの比較

Lambda@Edge を利用した大量リダイレクト設定の管理面について、他の方法と比較しながら見ていきましょう。

項目 Web サーバー (Apache/Nginx) CloudFront Functions Lambda@Edge (S3連携)
管理のリスク ・設定ファイルの構文エラー
・多数のVirtual Host設定による複雑化
・SSH接続下での作業ミスによるサービス影響の可能性
・関数コードのバグ
・IAM権限の設定ミス
・CloudFrontの設定変更ミス
・関数コードのバグ
・IAM権限の設定ミス
・S3設定ファイルの設定ミス
運用の負荷 ・SSH接続下での作業に細心の注意(厳格な手順書等)を払う必要がある。
・アラート発生時等、リダイレクト設定の影響を都度切り分ける必要がある。
・サーバーレスでインフラ管理は不要
・作業ミスの影響範囲は、従来のやり方に比べ限定的
・サーバーレスでインフラ管理は不要
・作業ミスの影響範囲は、従来のやり方に比べ限定的
コスト ・EC2インスタンス費用 (数が多くなると、リダイレクト処理のためのスペック増が必要)
・リダイレクトのたびデータ転送費用が発生
・CloudFront Functionsの実行回数に基づく課金 (100万リクエストあたり約$0.1)
・月間200万リクエストの無料枠あり
・オリジンへの不要なトラフィック削減によるCDN転送料の削減
・ Lambda@Edge実行回数、実行時間、コンピューティング料金 (Functionsより高くなる傾向)
・S3ストレージ/リクエスト料金
・オリジンへの不要なトラフィック削減によるCDN転送料の削減

Web サーバーでの管理は、特に規模が大きくなると運用負荷が高く、設定ミスのリスクも大きくなりがちです。CloudFront Functions は手軽ですが、大量のルールには向きません。Lambda@Edge は、初期設定やコード管理の学習コストはありますが、一度仕組みを構築すれば、大量のリダイレクトルールを柔軟かつ効率的に、そしてスケーラブルに管理できる強力なソリューションとなります。

まとめ

本記事では、数千単位の大量リダイレクト設定が抱える課題と、その解決策として Lambda@Edge を活用する方法を解説しました。

Lambda@Edge は、CloudFront Functions の持つ制限 (パッケージサイズや実行時間) をクリアし、より大規模で複雑なリダイレクトロジックをエッジで実行できるため、パフォーマンスとスケーラビリティに優れたリダイレクトシステムを構築できます。特に、リダイレクトルールを S3 などの外部ストアに置くことで、運用負荷を大幅に軽減できる点は大きなメリットです。

もちろん、Lambda 関数の開発・テスト、IAM の適切な権限管理、コスト管理といった考慮点はありますが、それらを差し引いても、大量リダイレクトの課題に直面している場合には非常に有効な選択肢となるでしょう。

参考になれば幸いです。

参考ドキュメント

Lambda@Edge とは
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html
Lambda@Edge の制限事項
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html
Lambda@Edge 関数の例
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html
CloudFront Functions とは
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html
CloudFront Functions の制限事項
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-function-restrictions.html