API GatewayがName-based Virtual Hostingな動作をすることを確認してみた

API GatewayがName-based Virtual Hostingな動作をすることを確認してみた

API Gatewayのデフォルトのエンドポイント、名前解決したIPアドレスに対して異なるAPIのホスト名をHostヘッダに指定してアクセスすると、そのHostヘッダに応じたレスポンスが返ることを確認してみました。CoudFrontと類似する動作で、カスタムドメイン利用時の留意事項なども同様ですね。
2026.08.03

はじめに

清水です。Amazon API GatewayがName-based Virtual Hosting(名前ベースのバーチャルホスティング)の挙動をすることを確認してみました。2つのAPIがあり、それぞれ呼び出しURLのドメインが foo.execute-api.ap-northeast-1.amazonaws.combar.execute-api.ap-northeast-1.amazonaws.com だったとします。foo.execute-api.ap-northeast-1.amazonaws.com のIPアドレスに対して、Hostヘッダ bar.execute-api.ap-northeast-1.amazonaws.com でアクセスしてみると、Hostヘッダの bar.execute-api.ap-northeast-1.amazonaws.com のAPIとしての応答がある、というぐあいです。CloudFrontでも同じような動作をしますよね。この挙動を実際に確認しながら、関連してAPI Gatewayでカスタムドメインを使用する場合の留意事項なども抑えてみました。

動作確認用API Gatewayの作成

まずは動作確認用のAPI Gateway エンドポイントを作成します。API GatewayのバックエンドはAWS Lambda関数としました。AWSマネジメントコンソールからLambda関数を作成する流れで、Lambda関数ならびにAPI Gatewayエンドポイントを作成していきます。

Lambdaのマネジメントコンソール、関数一覧画面から(関数を作成)ボタンで進みます。「一から作成」を選択し関数名だけ設定します。1つ目を vhtest-foo としました。ランタイムはデフォルトのNode.js 24.xを選択し(関数を作成)ボタンを押下します。

vh01

関数が作成できたら、コードソースに以下の内容を入力して [Deploy] します。以下のコードは生成AIを利用して作成しました。コメントにある通りPHPのgetallheaders関数の動作をイメージしています。また21行にはレスポンスの冒頭で API-name: foo という文字列を出力するよう指定しています。この部分を2つのAPIで変え、どちらのAPIにアクセスしているかを確認します。

export const handler = async (event) => {
  try {
    console.log("Request event:", JSON.stringify(event, null, 2));

    // すべてのヘッダーを取得
    const headers = getAllHeaders(event);

    // PHPの出力形式と同様に "Name: Value" 形式の文字列を生成
    const headerLines = Object.entries(headers)
      .map(([name, value]) => `${name}: ${value}`)
      .join("\n");

    console.log("Headers:\n", headerLines);

    return {
      statusCode: 200,
      headers: {
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": "*",
      },
      body: `API-name: foo\n${headerLines}`,
    };
  } catch (error) {
    console.error("Error:", error);

    return {
      statusCode: 500,
      headers: {
        "Content-Type": "text/plain",
      },
      body: `Error: ${error.message}`,
    };
  }
};

/**
 * すべてのリクエストヘッダーを取得する
 * PHPのgetallheaders()と同等の処理
 * @param {Object} event - API Gatewayのイベントオブジェクト
 * @returns {Object} ヘッダーのキーと値のオブジェクト
 */
const getAllHeaders = (event) => {
  const headers = {};

  // API Gateway v1 / v2 どちらも event.headers にヘッダーが格納される
  if (event.headers) {
    Object.entries(event.headers).forEach(([name, value]) => {
      headers[name] = value;
    });
  }

  // API Gateway v2 では multiValueHeaders の代わりに
  // event.cookies にCookieが別途格納されるため追加する
  if (event.cookies && event.cookies.length > 0) {
    headers["Cookie"] = event.cookies.join("; ");
  }

  return headers;
};

vh02

Deployが完了したら、関数の概要欄の(+ トリガーを追加)ボタンを押下します。

vh03

トリガーを追加画面に遷移したら、、トリガーの設定でソースとしてAPI Gatewayを選択します。インテントで 新規のAPIを作成 、APIタイプは REST APIで、セキュリティは オープンと設定して (追加)ボタンを押下します。

vh04

トリガーのAPI Gatewayが作成できました。

vh05

APIエンドポイントから動作を確認してみます。Webブラウザでそのまま開きました。冒頭のAPI-name: fooという文字列、そしてリクエスト時のヘッダがそれぞれ確認でき、意図した動作をしていることが確認できます。

vh06

同様の手順で2つ目のAPI Gatewayエンドポイントも作成します。違いとしてはLambda関数の関数名(vhtest-barとしました)とAPI名(vhtest-bar-API)、またLambda関数のコードとしては以下のように、21行目のレスポンス冒頭の文字列出力箇所だけAPI-name: bar に変更しています。

vh07

2つ目のAPI作成後、同様の手順でAPIエンドポイントにWebブラウザでアクセス、動作確認をしておきましょう。

vh08

以下のように1行目が API-name: bar という出力になっていますね。こちらが2つ目のAPIからのレスポンスとなります。

vh09

API Gatewayの動作がHostヘッダで決まることの確認

動作確認用のAPI Gatewayが作成できました。では実際に動作を確認してみましょう。なお、以下本エントリで検証するホスト名から名前解決してIPアドレスを得て、別のAPIのホスト名をHostヘッダとして渡す、というリクエスト方法は推奨されていないものである認識です。動作検証などの確認に留め、実際の運用環境ではくれぐれも使わないようにしましょう。

まずはHostヘッダを指定しないアクセスです。当然ながら、それぞれのAPIに応じたレスポンスが返ってきます。

 % curl https://wa0oxxxxxx.execute-api.ap-northeast-1.amazonaws.com/default/vhtest-foo
API-name: foo
accept: */*
Host: wa0oxxxxxx.execute-api.ap-northeast-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-09xxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https
 % curl https://olffxxxxxx.execute-api.ap-northeast-1.amazonaws.com/default/vhtest-bar

API-name: bar
accept: */*
Host: olffxxxxxx.execute-api.ap-northeast-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-13xxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https

続いて、1つ目のAPI vhtest-fooのホスト名(FQDN)を名前解決し得られたIPアドレスに対し、2つ目のAPI vhtest-barのホスト名をHostヘッダとして指定してリクエストを送信しています。

まずは名前解決を行います。

 % dig wa0oxxxxxx.execute-api.ap-northeast-1.amazonaws.com +short
52.198.76.135
54.178.66.151
18.182.190.194

IPアドレス 52.198.76.135 に対して、-H "Host: olffxxxxxx.execute-api.ap-northeast-1.amazonaws.com" と2つ目のAPI vhtest-barのホスト名をHostヘッダとして指定してリクエストを送信、リクエスト先のパスもdefault/vhtest-barを指定しています。また-kオプションでSSLまわりのエラーは無視するよう指定しました。 レスポンスとしては2つ目のAPI vhtest-bar のものが返ってくる結果となりました。

 % curl -k -H "Host: olffxxxxxx.execute-api.ap-northeast-1.amazonaws.com" https://52.198.76.135/default/vhtest-bar
API-name: bar
accept: */*
Host: olffxxxxxx.execute-api.ap-northeast-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-08xxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https

逆のパターン、2つ目のAPI vhtest-barのホスト名(FQDN)を名前解決し得られたIPアドレスに対して、1つ目のAPI vhtest-fooのホスト名をHostヘッダとして指定してリクエストを送信するケースも確認してみます。こちらも、 Hostヘッダで指定した1つ目のAPI vhtest-fooのレスポンスが返る結果 となりました。

 % dig olffxxxxxx.execute-api.ap-northeast-1.amazonaws.com +short
16.76.39.158
3.113.184.175
18.177.173.110

 % curl -k -H "Host: wa0oxxxxxx.execute-api.ap-northeast-1.amazonaws.com" https:
//16.76.39.158/default/vhtest-foo
API-name: foo
accept: */*
Host: wa0oxxxxxx.execute-api.ap-northeast-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-2fxxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https

API GatewayはHostヘッダの値でレスポンスを返すAPIが決まる、という挙動が確認できました。AWSアカウントをまたぐケースについても確認してみます。別AWSアカウントで3つ目のAPIを作成しました。詳細な手順は省略しますが、作成手順は先ほどと同様でAPI-name: bazという名称で作成してます。

まずは通常のHostヘッダを指定しないリクエストです。

 % curl https://99lqxxxxxx.execute-api.ap-northeast-1.amazonaws.com/default/vhtest-baz
API-name: baz
accept: */*
Host: 99lqxxxxxx.execute-api.ap-northeast-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-6dxxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https

先ほど1つ目のAPI vhtest-fooのホスト名(FQDN)を名前解決し得られたIPアドレス52.198.76.135 に対して、3つ目の別AWSアカウントのAPIのホスト名をHostヘッダに指定してリクエストを送信してみます。パスも忘れずに変更してcurlコマンドを実行すると、こちらもHostヘッダで指定したAPIのレスポンスが返ってきました。

 % curl -k -H "Host: 99lqxxxxxx.execute-api.ap-northeast-1.amazonaws.com" https://52.198.76.135/default/vhtest-baz
API-name: baz
accept: */*
Host: 99lqxxxxxx.execute-api.ap-northeast-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-28xxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https

もう1つのパターンとして、別リージョンものAPIではどうなるかも確認してみました。1つ目、2つ目と同じAWSアカウントの別リージョンに、同様の手順で API-name: qux を作成します。以下は通常のHostヘッダを指定しないリクエストです。

 % curl https://cyncxxxxxx.execute-api.us-east-1.amazonaws.com/default/vhtest-qux
API-name: qux
accept: */*
Host: cyncxxxxxx.execute-api.us-east-1.amazonaws.com
User-Agent: curl/8.7.1
X-Amzn-Trace-Id: Root=1-6axxxxxx-2axxxxxxxxxxxxxxxxxxxxxx
X-Forwarded-For: 1XX.XXX.XXX.XXX
X-Forwarded-Port: 443
X-Forwarded-Proto: https

先ほど1つ目のAPI vhtest-fooのホスト名(FQDN)を名前解決し得られたIPアドレス52.198.76.135 に対して、別リージョンのAPIのホスト名をHostヘッダに指定してリクエストを送信してみます。こちらは正常なレスポンスが返らず、{"message":"Forbidden"}となりました。HTTPステータスコードは403が返ってきています。リージョンをまたいだ場合はこのName-Based Virtual Hostingの動作はしない、という結果になりました。

 % curl -k -H "Host: cyncxxxxxx.execute-api.us-east-1.amazonaws.com" https://52.198.76.135/default/vhtest-qux
{"message":"Forbidden"}
 % curl -i -k -H "Host: cyncxxxxxx.execute-api.us-east-1.amazonaws.com" https://52.198.76.135/default/vhtest-qux
HTTP/2 403
date: Fri, 31 Jul 2026 11:28:10 GMT
content-type: application/json
content-length: 23
x-amzn-requestid: c3xxxxxx-fxxx-4xxx-9xxx-16xxxxxxxxxx
x-amzn-errortype: ForbiddenException
x-amz-apigw-id: BXxxxxxxxxxxxxA=

{"message":"Forbidden"}

なおAPI Gatewayリソース作成時に触れていませんでしたが、本ブログエントリの手順でLambdaのマネジメントコンソールから作成したAPI GatewayはAPIエンドポイントタイプが リージョン です。エッジ最適化 ではまた違った挙動になるかもしれません。

API GatewayのName-based Virtual Hostingな動作をどう考えるか

筆者がこの動作に気がつくきっかけとなったのは、API GatewayにAWS WAFをアタッチする検証でした。WAFをCountモードで動作させログを確認してみたのですが、Blockされるような攻撃的なアクセスがまったくありませんでした。個人的には何もしていなくても攻撃的なアクセスがあり、BlockないしCountされるものだと思っていました。例えば、ALBにWAFをアタッチした場合はこのような挙動になりますよね。

例えば以下ブログにも、ALBを3時間程度起動していたら攻撃がきた、という記載があります。

しかしAPI GatewayにWAFをアタッチした場合は、待てど暮らせど攻撃的なアクセスはやってきません。それはそれでよいのですが、なぜだろうと考えたとき、今回のブログエントリで例示してきたような [ランダムな文字列].execute-api.ap-northeast-1.amazonaws.com というホスト名(FQDN)と、このホスト名がCloudFrontのようなName-based Virtual Hostingな動作をしているのでは、と推測し確認にいたりました。

さて、参照しているCloudFrontがName-based Virtual Hostingな動作をすることを検証したブログエントリと順序は前後してしまいますが、このAPI GatewayがName-based Virtual Hostingな動作をすることから考慮できる留意事項などを確認しておきましょう。

例えばName-Based Virtual Hostingのような動作をしないALBなどのAWSリソースでは、独自ドメインを利用する際にDNS側でレコード登録をすれば完了です。対して、CloudFrontで独自ドメインを利用する際にはDNS側のレコード登録のほか、CloudFrontディストリビューションへのCNAME設定が必要でした。(詳細は参照ブログエントリを確認ください。)

API Gatewayでの独自ドメイン利用についても確認してみると、CloudFrontと同様にDNS側のレコード登録のみでは完了せず、API Gateway側でカスタムドメイン名の設定が必要です。

さらにはマッピングという操作も必要とのこと、個人的にAPI Gatewayについては明るくなく設定項目もあまり見たことがなかったのですが、エンドポイントアクセスモードまわりについてはエッジ最適化エンドポイントの場合、CloudFrontとも関連してそうですね。機会があればこちらも確認してみたいと思いました。

vh10

vh11

またCloudFrontでは独自ドメイン(カスタムドメイン)をディストリビューション間で変更(付け替える)場合、CNAME重複が許されておらずちょっと大変(DNSレコード変更のようにはいかない)という留意事項がありました。API Gatewayでも同様に、ダウンタイム無しでカスタムドメインを別API Gatewayリソースに移行するのは少し大変内容です、以下のブログエントリを見つけました。

エンドポイントタイプがエッジ最適化の場合は全アカウント全リージョン、リージョナルの場合はそのリージョン内膳アカウントで、カスタムドメインの重複は許されないとのことです。

API GatewayがName-Based Virtual Hostingな動作をすることから、IPアドレス総当りのような攻撃が発生しづらいこと、またカスタムドメイン利用の際にはDNSへのレコード登録以外の作業が必要なことをなどが特徴となることが確認、把握できますね。

まとめ

Amazon API GatewayがHostヘッダの値で動作するAPIが決まるName-based Virtual Hosting(名前ベースのバーチャルホスティング)の挙動をすることを確認してみました。API GatewayのリソースはREST API、エンドポイントタイプはリージョン、カスタムドメインを用いないでデフォルトのエンドポイントで確認しました。HTTP APIなどほかAPIタイプや、エッジ最適化のエンドポイントタイプ、カスタムドメインを使用した際などの挙動も気になるところです。またCloudFrontやAPI Gatewayのほかにも、このようなName-Based Virtual Hostingの挙動をしそうなAWSサービスはあるのかもしれません。機会があれば確認してみたいと思いました。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事