
サーバーサイドGTMをコスト効率よく導入 — GA4測定IDのなりすまし対策つき
サーバーサイドGTMを自分のブログに導入し、実際に動かして得られた効果とつまずきポイントをまとめました。何が変わるのか、どんな効果が本当にあるのか、Cloud Run・AWS Lambda・自作フォワーダーのどれを選ぶべきか、そしてGTM管理画面での具体的な設定手順まで、これから導入する人向けに実例つきで解説します。
目次
サーバーサイドGTMを自分のブログに導入し、実際に動かして得られた効果とつまずきポイントをまとめました。何が変わるのか、どんな効果が本当にあるのか、Cloud Run・AWS Lambda・自作フォワーダーのどれを選ぶべきか、そしてGTM管理画面での具体的な設定手順まで、これから導入する人向けに実例つきで解説します。
サーバーサイドGTMとは
サーバーサイドGTM(server-side Google Tag Manager)は、GA4などの計測タグを「ブラウザの中」ではなく「自分が管理するサーバー」の中で実行する仕組みです。
普段私たちが「GTMを入れる」というとき、実際に動いているのはブラウザの中です。ブラウザが直接Googleのサーバー(www.googletagmanager.comやwww.google-analytics.com)へ計測データを送っています。
サーバーサイドGTMでは、この間に「自分の会社・自分のドメインで動くサーバー」を1つ挟みます。ブラウザは自社ドメインにデータを送り、そのサーバーが改めてGoogleなど各サービスへデータを転送する、という2段構成になります。
[今までの構成]
ブラウザ ── 直接送信 ──> Google(googletagmanager.com / google-analytics.com)
[サーバーサイドGTMの構成]
ブラウザ ── 送信 ──> 自社のサーバー(自社ドメイン) ── 転送 ──> Google / その他の送信先「中継サーバーを挟むだけ」と思われがちですが、これは正確ではありません。実際にはこのサーバーはGoogleが提供する専用のプログラム(コンテナ)で、単なる転送役ではなく「タグ・トリガー・変数を実際に実行するエンジン」そのものです。この認識のズレは後述する導入方式の検討にも関わってきます。
クライアント(通常)GTMとは — こうなっていたら「普通」です
まず前提として、多くのサイトが使っている「普通のGTM」がどんな見た目になっているかを確認しておきます。以下のようなコードが<head>タグ内にあれば、それは通常のクライアントサイドGTMです。
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>ポイントはhttps://www.googletagmanager.com/gtm.js?id=...という部分です。ブラウザの開発者ツール(Networkタブ)を開いて、このgoogletagmanager.com宛てのリクエストが直接見えるなら、それは今回説明する「サーバーサイド化」がまだされていない状態です。
なぜサーバーサイドがいいの?
導入の動機になりうる効果を、実際に検証した内容も交えて整理します。「なんとなく良さそう」で終わらせず、効果があるものとないものを分けて書きます。
効果がある: 外部に渡す前に、個人情報を自社サーバーでマスキング・制御できる
これはサーバーサイドGTMの最も標準的で重要な利点です。サーバーコンテナは単なる中継役ではなく「タグ実行エンジン」なので、ブラウザから受け取った計測データを、外部サービスへ転送する直前に自社サーバーの中で加工できます。
クライアントサイドだけの構成では、ブラウザから外部サービスへデータが直接飛ぶため、いったん送信される情報を途中で止める場所がありません。サーバーサイド化すると外部へ出る手前に必ず自社の「関所」を通るようになり、ここがどの情報を第三者に渡すかを検閲・加工できる唯一のポイントになります。
具体的には、たとえば次のような制御ができます。
- ・メールアドレス・氏名・ユーザーIDや、URLのクエリ文字列に紛れ込んだ個人情報を、外部に渡す前に削除・マスキングする
- ・IPアドレスを匿名化・除去する
- ・送信先(GA4・広告・その他)ごとに「渡してよいフィールド」を選び分ける
単なる計測の改善ではなく、どの個人情報を第三者に渡すかを自社の管理下に置くという、プライバシー保護・データガバナンスの観点での利点です。
効果がある: 企業ネットワークのドメイン単位遮断の回避
法人のPCで運用されているセキュリティ製品や社内プロキシは、多くの場合「ドメイン名」を見てgoogletagmanager.comやgoogle-analytics.com宛ての通信をまとめてブロックします。読者に法人ユーザーが多いサイトでは、この遮断によって計測データがかなりの割合で欠けている可能性があります。
サーバーサイドGTMでは通信先が自社ドメインになるため、この種の遮断は素直に回避できます。
効果は限定的: 個人の広告ブロッカー回避
「ファーストパーティ化すれば広告ブロッカーも回避できる」という説明をよく見かけますが、これは半分だけ正しいです。
実際に主要な広告ブロッカーのフィルタールール(EasyPrivacy)を確認すると、.js?id=GTM-のようにコンテナIDのパターン自体をドメインに関係なく検出するルールが存在します。つまり、ホスト名だけ自社ドメインに変えても、URLの中にGTM-から始まるIDがそのまま見えていれば、結局ブロックされる可能性が残ります。
さらに踏み込むと、ファーストパーティ化による回避策そのものを想定した書き方のルール(第三者かどうかを問わず適用されるもの)も見つかりました。ブロッカー側もこの手の回避には既に対策済み、ということです。
後述しますが、Googleの公式コンテナには「配信パスをランダムな文字列にする」機能があり、これを使うとこの種の検出をある程度回避できます。ただし「回避できる」のであって「絶対に検出されない」わけではありません。
効果がある: Cookieの持続性向上
Safari(ITP)やその他のブラウザは、サードパーティCookieの保持期間を大きく制限しています。計測用のCookieを自社ドメインで発行できるようになるため、この制限を受けにくくなり、リピーター判定などの精度が上がります。
効果がある: Bot・不正トラフィックの管理を自分の手に取り戻せる
計測エンドポイントの通信先がgoogle-analytics.comのようなGoogle管理下のドメインから自社ドメインに変わるということは、その通信が自社のCDN(CloudFrontなど)を経由するようになるということでもあります。これにより、WAFのBot対策ルールを計測トラフィックにも適用できるようになります。
これまではGA4への送信がGoogle側のインフラで完結していたため、そこにスクレイパーなどの不正なアクセスが混ざっていても、サイト運営者側では手出しができませんでした。サーバーサイドGTM化すると、この通信も自社の管理下に入るため、計測データに混ざるノイズを自分の意思でコントロールできるようになります。
効果がある: 広告運用のコンバージョン計測がより正確・安定する
広告(Google広告や各種SNS広告など)を運用している場合、コンバージョン(申込・購入などの成果)の計測はサーバーサイド経由が推奨されます。理由はこれまで挙げた利点が重なって効いてきます。
- ・広告ブロッカーやブラウザのトラッキング防止(ITP等)でコンバージョンのタグが弾かれても、サーバー側から送れば取りこぼしを減らせる(=ドメイン遮断回避・Cookie持続の効果が、そのままコンバージョン計測の精度に直結する)
- ・拡張コンバージョンのように「成果と一緒にハッシュ化した個人情報を送って計測精度を上げる」仕組みを、ブラウザに生データを晒さず自社サーバー側で扱える(前述のマスキング・ガバナンスと同じ考え方)
- ・ブラウザ経由の一時的な失敗(ネットワーク断など)に比べ、サーバー間の送信は安定しやすい
コンバージョンの取りこぼしが減るほど広告の費用対効果を正しく測れ、最適化も効きやすくなるため、広告運用者にとっては直接的なメリットになります。
効果がある: 計測データを一度サーバーで受け、複数の送信先へまとめて分配できる
送信先がGA4だけでなく広告・SNS・CRMなど複数ある場合に効く利点です。クライアントサイドだけの構成では、送信先を1つ増やすたびに、ブラウザで動く第三者のタグ(スクリプト)を1つ増やすことになります。サーバーサイドGTMでは、ブラウザからは1回だけ自社サーバーへ計測データを送り、そこから各送信先へサーバー側で振り分けられます。
- ・送信先が増えても、ブラウザ側のタグ(=第三者スクリプト)を増やさずに済む
- ・ブラウザで動く第三者スクリプトが減るぶん、各ベンダーがページやCookieに直接触れる範囲が狭まり、セキュリティ・プライバシー面でも有利
- ・送信先ごとの加工(前述のマスキングなど)も、この分配の段でまとめて行える
ただしこれは「ページが速くなる」という話とは別物です(次項)。ブラウザから第三者スクリプトを減らせるのは事実ですが、GTM自体の読み込みは残るため速度改善を主目的にはできません。また、送信先が実質GA4だけの構成では、この分配の恩恵は小さくなります。
誤解しやすいポイント: ページ表示速度
「クライアント側のJSが減るのでページが速くなる」という説明も見かけますが、これは正確ではありません。ブラウザが読み込むスクリプト自体は変わらず、読み込み先が変わるだけです。速度改善を主目的にするのはおすすめしません。
導入方法
サーバーサイドGTMは「サーバーでコンテナを1つ動かす」だけなので、ホスティング先はいくつか選択肢があります。
前提として、このサーバーコンテナはGoogleが配布するステートレスなDockerイメージ(設定は毎回Google側から取得する)で、要は「HTTPを喋るコンテナが1つ動けばよい」だけです。そのためコンテナが動く環境ならどれでも動きます — Cloud Run、AWS Lambda(コンテナイメージ)、ECS/Fargate、EC2、GKEなど、事実上どのコンテナ実行基盤でも構いません。GTM管理画面での設定手順(後述)も、どれを選んでも同じです。
「どれでも動く」ので、選ぶ基準はコンテナが動くかどうかではなく、次の3点になります。
- ・コスト: 使った分だけで低トラフィックならほぼ無料に収まるのはLambda。Cloud Runもリクエスト課金だが、sGTMではコールドスタートや障害対策でインスタンスを常駐させるのが基本のため、実際には月$40前後(1インスタンス)〜$120前後(Google推奨の3インスタンス)の下限がある。Fargate・EC2も常時起動ぶんの費用が乗る
- ・コールドスタート: ゼロまでスケールする方式は、最初の計測リクエストに起動待ちが乗ることがある。常駐させれば避けられる(計測用途なら許容できることが多い)
- ・コンテナとの相性・手間: このイメージは「常駐HTTPサーバー」として作られているので、Cloud RunやFargate/EC2には素直に載る。Lambdaだけは後述のWeb Adapterを噛ませる必要があり、そのぶん独自のつまずきが増える
大まかには、低トラフィックでコスト最小化を最優先するならLambda(Cloud Runは後述のとおり月$40前後の下限がある)、安定した中〜高トラフィックやコールドスタート回避を優先するなら常駐のFargateやCloud Run(複数インスタンス)が素直です。EC2は自分でインスタンスを握りたい特別な理由がなければ、この用途でわざわざ選ぶ必要は薄めです。以下では、標準であるCloud Runと、筆者が実際に選んだLambda構成の順に説明します。
標準: Google Cloud Run
Googleが公式にサポートしているのはCloud Run(GCP)です。GTMの管理画面から「自動でプロビジョニングする」を選ぶと、Cloud Run上にコンテナが自動構築されます。GCPを既に使っている、あるいはインフラをどこに置くか特にこだわりがないなら、これが一番手間のかからない選択肢です。
コスト面の注意: Cloud Runには月$40前後の下限がある
Cloud Runはリクエスト課金なので一見「使わなければ無料」に見えますが、サーバーサイドGTMではコールドスタートや障害時のデータ欠損を避けるため、インスタンスを常駐させるのが前提です。Googleは本番環境で最低3インスタンスを推奨しており、1インスタンスあたり月$40前後 — つまり推奨構成だと月$120前後が下限になります(ログ出力を切り忘れるとさらに上乗せされます)。個人ブログのような低トラフィックでは、この固定費が相対的に重く感じられます。筆者がCloud RunではなくコンテナをLambda化する構成を選んだ最大の理由がこれで、Lambdaならリクエスト単位課金+無料枠で、低トラフィックなら実費をほぼゼロに抑えられます。
筆者が選んだ、ややトリッキーな構成: AWS Lambda
既存のインフラがAWSに寄っている場合、「わざわざこのためだけにGCPアカウントを増やす」のは避けたくなります。この場合、AWS Lambda(コンテナイメージとして起動)+ Lambda Web Adapter(コンテナをLambda化するための薄いアダプタ)という組み合わせで、AWS上に同じコンテナを動かすことができます。
これはGoogleの公式手順には載っていない、いわば「非公式だが動く」構成です。メリット・デメリットの両方を書きます。
- ・メリット: Lambdaはリクエストが来た時だけ課金される仕組みなので、個人ブログ〜中規模サイト程度のアクセス数であれば、無料枠の範囲に収まり実費がほぼゼロになることがあります。Cloud RunをGCPで新規に立てるよりも、既存のAWS環境に馴染ませやすいのも利点です。
- ・デメリット: 公式サポート対象外なので、細かい設定で何度もつまずきます(下記の「導入時の注意」を参照)。素直にCloud Runを使う場合と比べて、トラブルシュートを自分でやる覚悟が要ります。
導入時の注意①: CDN(CloudFront等)を前段に置く場合のHostヘッダー
AWSでこの構成を組む場合、Lambda関数を直接インターネットに公開するのではなく、CDN(CloudFrontなど)を前段に置いてカスタムドメインを割り当てるのが一般的です。この際、CDNの「オリジンへのリクエスト転送設定」で閲覧者のHostヘッダーをそのまま転送する設定にしていると、Lambda側のホスト名検証に引っかかって403エラーになります。「Hostヘッダーだけは転送しない」設定を選ぶ必要があります。
導入時の注意②: コンテナイメージのビルド設定
Dockerイメージをビルドする際、最近のビルドツールはデフォルトで「provenance」「SBOM」と呼ばれる付加情報をイメージに埋め込みます。AWS Lambdaはこの形式のイメージを受け付けず、作成時にエラーになります。イメージビルド時にこれらの付加情報を明示的に無効化する必要があります。
導入時の注意③: CORSの二重設定
Lambda関数を外部公開する仕組み(Function URL等)自体にCORS設定(許可するオリジン等)を追加できる場合がありますが、コンテナ自身が既に正しくCORSを処理しているため、ここで二重に設定すると、レスポンスヘッダーに許可オリジンの値が2つ入ってしまい、ブラウザ側でCORSエラーとして弾かれます(「Access-Control-Allow-Originに複数の値が入っている」というエラーが出たら、まずここを疑ってください)。この手のプラットフォーム側の設定は追加しない方が安全です。
検討したが採用しなかった案: 自作の中継フォワーダー
「単に中継するだけなら自分でサーバーを書いた方が軽くて速いのでは」という考え方もあります。実際、筆者も最初はこの発想で検討しました。
しかし前述の通り、サーバーサイドGTMは中継役ではなく「タグ実行エンジン」です。自作すると以下を自分で背負うことになります。
- ・GTMの管理画面(ノーコード)でタグを追加・変更する運用ができなくなる
- ・GA4やGoogle広告側の仕様変更に、自分で追従し続ける必要がある
- ・公式のデバッグ・プレビュー機能が使えない
計測先がGA4だけのようなシンプルな構成であればコード量自体は小さく済みますが、「公式のタグ管理基盤を捨てる」という判断であることは意識しておく必要があります。今回は公式コンテナを使う方針にしました。
追記(2026年7月): サーバーコンテナ自体を持たない選択肢 — Google タグ ゲートウェイ
この記事を書いた後、GTMの管理画面で「Google タグ ゲートウェイ」という選択肢に気づきました。この機能自体がいつ追加されたのかは未確認です(サーバーコンテナを作ったことで案内が表示されるようになった可能性もあります)が、少なくとも筆者にとっては初めて見る選択肢でした。ここまで見てきたCloud Run・Lambda・自作フォワーダーはどれも「自分でサーバーコンテナを用意する」前提でしたが、条件次第ではその前提自体が要らなくなるかもしれません。
GTMの管理画面から数クリックで設定でき、DNSにCNAMEレコードを1本追加するだけで使え、無料です。実際に導入するとアーキテクチャがどう変わるか、メリット・デメリットは何かは、後述の「Google タグ ゲートウェイ(CDNのみ)の場合」でまとめて説明します。
具体的な設定手順(GTM管理画面)
ホスティング先(Cloud Run/Lambdaなど)がどれであっても、GTM管理画面での設定手順自体は共通です。ここでは実際の画面を見ながら進めます。
手順1: サーバーコンテナを作成する
GTMの管理画面で、通常のWebコンテナとは別に「サーバー」タイプのコンテナを新規作成します。作成した直後は、こういった案内バナーが出ています。

「設定を開始」から進むと、サーバーの立て方を選ぶ画面になります。「自動でプロビジョニングする」を選べばCloud Runに自動構築されます。AWSなど別の環境に自分で構築したい場合は「手動でプロビジョニングする」を選びます。

注意: この画面でしか手に入らない値がある
表示されるCONTAINER_CONFIGという文字列は、後で自分のサーバー(Lambdaなら環境変数)に設定する必須の値です。この値はAPI経由では取得できず、この画面からコピーする以外の入手方法がありません。自動化したい場合でも、ここだけは手動作業が必要だと割り切ってください。この値は認証情報そのものなので、他人に見せたり、スクリーンショットをそのまま公開したりしないよう扱いに注意してください。
手順2: 「Webコンテナクライアント」を追加し、gtm.jsの配信を許可する
サーバーコンテナ側に、既存のWebコンテナ(普段運用しているGTMコンテナ)からのスクリプト配信を許可するクライアントを追加します。クライアントの種類は「Google タグ マネージャー: ウェブコンテナ」を選びます。

「許可されているコンテナID」に、普段使っているWebコンテナのID(GTM-XXXXXXX)を入力します。入力すると「タグ配信パス」が自動生成されます。

このタグ配信パスがランダムな理由
自動生成される/xxxxxxxxxxxのような文字列は、前述の「GTM-という文字列自体を狙うブロッカー対策」のための仕組みです。ここは/gtm.jsのような分かりやすい値に手動で変更することもできますが、そうすると本来の対策効果が薄れます。特にこだわりがなければ自動生成された値をそのまま使うことをおすすめします。
このまま保存しようとすると、以下のようなエラーになることがあります。

「地域ごとの設定を有効にする」のチェックが入ったまま地域を選んでいないのが原因です。今回は地域別の設定は不要なので、このチェックを外せば解消します。

手順3: Webコンテナ側のGA4タグに、サーバーの向き先を教える
ここまではサーバー側の設定でした。次は普段運用しているWebコンテナ側の設定です。GA4のタグ(多くの場合「Google タグ」という種類で登録されています)を開きます。

「構成パラメータ」に、パラメータ名server_container_url、値に自分のサーバーのドメイン(https://your-domain.comのような形)を追加します。

ここまでで、アカウント配下にはWebコンテナとサーバーコンテナの2つが並んだ状態になっているはずです。

手順4: GA4への転送タグを追加する(忘れがちな最重要ステップ)
手順1〜3は多くの解説記事に載っていますが、この手順を省略している解説が多く、それが原因で「設定は終わったはずなのにGA4にデータが1件も届かない」という状態にほぼ必ず一度は遭遇します。
サーバーコンテナの「タグ」メニューから新規タグを作成します。

タグの種類は「Google アナリティクス: GA4」を選びます。

これも忘れがちな設定: 測定IDの明示入力
このタグは「クライアントから測定IDを自動的に引き継ぐ」と説明されることがありますが、実際には測定ID(G-XXXXXXX)をタグの設定画面で明示的に入力しないと動作しません。空欄のままだと、これもエラーは出ずに単にデータが届かないだけなので気づきにくいです。

トリガーは新規に「カスタム」トリガーを作り、条件を「Client Name 等しい GA4」に設定します(GTM画面上では{{Client Name}}という二重波かっこの変数名で表示されます)。

手順5の前に: サイト側の読み込みスニペットを自社ドメインに向け直す
ここまではGTM管理画面の設定でした。手順2で「自社サーバーからgtm.jsを配信する」設定を入れたので、最後にサイトのHTML(<head>内)に埋め込んでいる読み込みスニペットも、配信元を自社ドメインに向け直します。冒頭で見た「普通のGTM」のスニペットと、次の2点が変わります。
<script>(function(w,d,s,l,i,u){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s);j.async=true;j.src=u;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX','https://your-domain.com/タグ配信パス/');</script>変わったのは次の2点だけです。
- ・配信URLをコードで組み立てるのをやめ、URLをまるごと1つの引数(
u)として渡し、j.src=uにする。普通のスニペットがwww.googletagmanager.com/gtm.js?id=…を毎回組み立てていた部分の置き換えです。 - ・その
uに入れるのが自社ドメイン + 手順2で自動生成されたタグ配信パス(https://your-domain.com/タグ配信パス/)。?id=GTM-…は付けません。どのコンテナを配信するかはこのパス自体がサーバー側で対応づけられているためです。
パスをランダム化しない場合
タグ配信パスをカスタムせず標準の/gtm.jsのまま使う場合は、uにはサーバーのベースURL(https://your-domain.com)だけを渡し、j.src=u + '/gtm.js?id='+iのように普通のスニペットと同じ組み立てを残す形にもできます。ただしその場合、前述の「GTM-という文字列を狙うブロッカー対策」の効果は弱まります。
これでgtm.js自体も自社ドメインから配信されるようになり、前述の「ドメイン単位の遮断」への耐性が、計測データの送信だけでなくスクリプトの読み込み段階から効くようになります。なお、この配信用サブドメインにgtm・sgtmという文字列を入れない方がよい理由は、記事末尾の「確定した設計判断」で触れています。
手順5: 公開する
サーバーコンテナ・Webコンテナともに、ここまでの設定を保存しただけでは反映されません。それぞれのバージョン管理から「公開」する必要があります(普段のGTM運用と同じ操作です)。

Webコンテナ側は、GA4タグにserver_container_urlが設定された状態になっていることを確認してから公開します。

手順6: 動作確認
公開したら、ブラウザの開発者ツール(Networkタブ)を開いてページを再読み込みし、自分のドメイン宛てのリクエストが正しく処理されているか確認します。

この時点でこういうCORSエラーに遭遇することがあります。

原因と対処は前述の「導入時の注意③」を参照してください。修正すると以下のように正常な状態になります。

最後にGA4の管理画面(リアルタイムレポート)を開き、実際にイベントが記録されているかも必ず確認してください。「開発者ツール上は200 OKなのにGA4には届いていない」というケースが本当にあるので、ここを確認して初めて「導入完了」と言えます(原因は手順4を参照)。

アーキテクチャ変更前後
ここまでで「Webコンテナ」「サーバーコンテナ」という2つの言葉が出てきました。どちらも名前に「コンテナ」と付くので混同しやすいのですが、決定的に違うのは実行される場所です。この違いを整理するために、登場人物を6つに分解して説明します。
登場人物6つと、それぞれの役割

- エンドユーザー: サイトを閲覧する人。ブラウザを操作している側。
- 自社サービス本体: 自分たちが運営しているWebサイト/アプリそのもの。エンドユーザーにHTMLを返す役割で、そのHTMLの中に前述の「GTM読み込みスニペット」が埋め込まれている。GTMの計測ロジック自体には関与しない、ただの「置き場所」。
- Webコンテナ: GTMの管理画面で普段からタグ・トリガー・変数を設定している、おなじみのコンテナ。設定の実体はGoogle側にあるが、実際にそのロジックが動く(実行される)場所はエンドユーザーのブラウザの中。gtm.jsを読み込んだ瞬間、ブラウザがこの設定内容をダウンロードしてその場で実行する。
- サーバーサイドGTMコンテナ稼働インフラ: 自分たちで用意する、Googleの公式プログラムを動かすための「箱」(Cloud RunやLambdaなど)。ここにはGTM固有のロジックは入っておらず、後述の「サーバーコンテナ」の設定をGoogle側から取得してきて、その通りに動くだけの汎用的な実行環境。
- サーバーコンテナ: Webコンテナと同じGTM管理画面上で設定する、もう1つ別のコンテナ。中身はクライアント(受信したリクエストを解析する担当)とタグ(解析結果を実際の送信先に転送する担当)で構成されている。設定の実体はGoogle側にあるが、実際に実行される場所は4の自社インフラの中。
- GA4など: 最終的にデータが記録される、本来の送信先。
つまり同じ「GTMコンテナ」という言葉でも、Webコンテナはブラウザの中で動き、サーバーコンテナは自社インフラの中で動くという違いがあります。どちらも設定はtagmanager.google.comの同じ画面で行いますが、動く場所は全く別です。ここを混同すると「クライアントを設定したのにサーバーコンテナ側で何も動いていない」ように見えて混乱するので、分けて考える必要があります。
なぜサーバーサイドGTMでもWebコンテナが必要なの?
ここまで読むと「サーバー側で全部処理するなら、Webコンテナ自体もう要らないのでは?」と思うかもしれません。実際、筆者もここで一度混乱しました。
答えは、「何が起きたか」を観測できるのはブラウザだけだからです。
「このユーザーがページを見た」「50%までスクロールした」「このボタンをクリックした」といった出来事は、ブラウザの中でJavaScriptが動いていない限り検知できません。サーバー側は、ブラウザから送られてくる情報を待つことしかできず、能動的にユーザーの行動を観測しには行けないのです。
つまり役割はこう分かれています。
- ・Webコンテナ: 「何が起きたか」を検知して、計測イベント(hit)を作る担当。ブラウザでしか実行できない
- ・サーバーコンテナ: Webコンテナが作ったhitを「どこに・どう加工して送るか」を決める担当。自社サーバーに置くからこそ、ファーストパーティ化やデータ加工ができる
サーバーサイドGTMは「Webコンテナを不要にする仕組み」ではなく、「Webコンテナが作ったhitの送信先を、Googleへの直接送信から自社ドメイン経由に向け直す仕組み」です。何を計測するか決めるWebコンテナの役目は、この構成に変えてもまるごと残ります。
(余談: 注文確定のような、そもそもブラウザを介さずサーバー内だけで完結する出来事であれば、Webコンテナを経由せずサーバーコンテナへ直接データを送ることも可能です。ただしこれはページ閲覧・スクロールのような、ブラウザでしか観測できない大半の行動計測とは別の話です。)
変更前: ブラウザが直接Googleと通信する1段構成。

変更後: ブラウザが話す相手は自社ドメインだけになり、その先の自社インフラ(サーバーコンテナ)が代理でGoogleと通信する2段構成。

ブラウザの開発者ツールを見たときに「通信相手のドメインが変わって見える」のは、この構造の変化がそのまま表れているためです。点線の枠(ブラウザの中で実行される)と実線の枠(自社インフラの中で実行される)を見比べると、増えたのは「自社インフラ+サーバーコンテナ」の部分だけで、Webコンテナの役割自体は変わっていないことも確認できます。
実際のインフラ全体で見ると
ここまでの図は「サーバーサイドGTMの構成要素だけ」を抜き出した単純化した図でした。実際にはこのブログは他にもWebアプリ本体・データベース・CDNなど、もっと多くの要素で構成されたインフラの上で動いています。「サーバーサイドGTMを既存の本番環境にどう追加したか」を、実際のインフラ全体の中で見てみます。

導入前は、CDN・WAF・アプリ実行基盤・データベース・ビルド用のCI基盤など、Webアプリを動かすための一式が本番環境を構成していました。

サーバーサイドGTMを追加した後の構成が上の図です。オレンジの点線で囲んだ部分が今回追加した要素(CDN・Lambda・コンテナレジストリ・専用ログ)で、既存のシステムとは配線が一切繋がっていません。VPCにもデータベースにもアプリ実行基盤にも接続せず、DNSの振り分けだけを共有する、完全に独立した1本の系統として追加しています。これは前述の「VPCには繋がない」という設計判断が、実際の本番構成の中でどう表れているかを示したものです。
既存システムへの影響を気にせずサーバーサイドGTMを試せるのは、この独立性のおかげでもあります。既存側の設定を一切変更せずに追加でき、万が一問題があれば専用のDNSレコードを1つ止めるだけで切り離せます。
なぜ既存のCDNに相乗りさせず、わざわざ専用のディストリビューションを新設したのか、疑問に思う方もいるかもしれません。理由は2つあります。
- ・CDNはドメインではなくパスでルーティング先を決める: 1つのディストリビューションに複数ドメインを紐づけても、振り分けの単位はパスパターンであって、どのドメインでアクセスされたかでは分岐しません。サーバーサイドGTM用に別サブドメインを使う設計にした以上、既存のディストリビューションに相乗りさせるには「アクセス元のホスト名を見て転送先を変える」処理をもう1つ挟む必要があり、これは既存サイトの全リクエストに影響する変更になってしまいます
- ・ディストリビューションを追加してもコストは増えない: CDNはディストリビューションの数ではなく、リクエスト数やデータ転送量に応じて課金される仕組みです。専用ディストリビューションを1つ増やすこと自体に追加コストはほぼかかりません
独立性を犠牲にすれば、既存ドメインの一部のパスにサーバーサイドGTMを載せる(新しいドメインを用意しない)という選択肢もあります。この場合、既存のWAFの保護はそのまま受けられますが、既存システムとの結合が増え、「既存に一切手を入れずに試せる」というメリットは失われます。今回は後者を優先しました。
Google タグ ゲートウェイ(CDNのみ)の場合
前章「導入方法」で触れた、サーバーコンテナを自前で持たない選択肢について、実際のアーキテクチャがどう変わるかをここで見ておきます。
仕組みはシンプルです。GTMの管理画面から数クリックで設定でき、DNSにCNAMEレコードを1本追加するだけで、計測データの通信先が自社ドメイン(ファーストパーティ)に見えるようになります。サーバーコンテナを自分でホスティングする必要がなく、料金もかかりません。

対応CDNの一覧にAmazon CloudFrontが含まれているのが実務上のポイントです。前述のLambda構成もCloudFrontを前段に置いているため、既存のCDNに直結できる可能性が高い、ということになります。
2つのパターンがある
公式ドキュメントを見ると、Google タグ ゲートウェイには2つの使い方があります。すでにサーバーコンテナ(サーバーサイドGTM)を持っているかどうかで分かれます。
- ・①CDNのみ: サーバーコンテナを一切持たない。CDNは「ファーストパーティに見せかける」役目だけを果たし、実際のデータ処理はGoogle管理下のインフラが担う。自分でサーバーを運用する必要がない
- ・②CDN + 既存のサーバーコンテナ(公式が推奨する併用構成): CDNはスクリプト配信(gtm.js)だけを肩代わりし、計測データの処理・加工は今まで通り自分のサーバーコンテナで行う。独自の加工ロジックを維持したい場合の構成
このブログの場合はどちらに近いか
このブログのLambda構成は独自の加工ロジックを持たない素の公式コンテナ(gtm-cloud-image:stable)を、右から左に流すためだけに動かしています。つまり②で維持するはずの「独自の加工ロジック」自体が存在しないため、比較する相手としては①(CDNのみ、サーバーコンテナ無し)の方が近い構成です。以下はこの①を前提に書いています。
①のパターンをこのブログの構成に当てはめると、アーキテクチャは次のように変わります。前節の「変更後」の図と見比べると、「サーバーコンテナ」の箱がまるごと無くなり、CDNの先はGoogleの管理下(中身は見えないブラックボックス)になっていることがわかります。

メリット
- ・サーバーコンテナの構築・運用が一切不要になる(このブログで言えば、Lambda・CloudFront専用ディストリビューション・コンテナイメージのビルドパイプラインがまるごと不要になる)
- ・無料
- ・主要CDN(CloudFront・Cloudflareなど)に対応しており、既存のCDNを流用できる
- ・設定がGTM管理画面から数クリックで完結する
デメリット・できなくなること
サーバーコンテナを手放すということは、3章で説明した「自社インフラを経由するからこそできること」も一緒に手放すということです。
- ・データの変換・加工・マスキングができない: 個人情報の削除やIPアドレスの匿名化など、外部に渡す前の検閲・加工はGoogleタグ以外の中継先には対応しておらず、自社側で手を出せる場所自体が無くなる
- ・非Googleの送信先へ分配できない: GA4以外の複数サービス(広告・CRMなど)へサーバー側でまとめて振り分ける、という使い方はできない
- ・Bot対策はWAFの標準機能の範囲にとどまる: 通信は引き続き自社のCDNを経由するため、WAFによるレート制限や既知の悪性IPブロックといった標準的な防御はこれまで通り効く。ただし、その先にサーバーコンテナが無いぶん、リクエストの中身を見た独自ロジックでの判定・ログ収集・追加の絞り込みはできなくなる
- ・広告ブロッカー回避の効果は限定的: URLの中身(パラメータ)自体は隠されないため、パスパターンで検出する高度なブロッカーには依然としてブロックされうる。独立系の計測では、信号の回復幅は5〜7%程度という報告もある
- ・Cookieの持続性はSafari(ITP)の制限を超えない: ドメインを自社化しても、Cookie自体はブラウザのJavaScriptが発行しているため、ITPの7日制限などはそのまま適用される
- ・CDNが前提条件: CDNを使っていないサイトはそもそも導入できない
- ・測定IDのなりすまし対策も打てない: 測定ID(
tid=G-XXXXXXX)はもともと非公開情報ではなく、クライアントサイドの頃から誰でもブラウザの開発者ツールで見えていた値です。これを知っていれば、自社サイトを一切経由せずGoogleの収集エンドポイントへ直接偽のヒットを送りつけ、計測データを汚染できます(いわゆるゴーストトラフィック)。サーバーコンテナがあれば「自社ページのJSだけが発行できる署名を検証してから転送する」といった対策コードを書けますが、Tag Gatewayにはその実装場所自体がありません
出典: Google: タグ ゲートウェイの概要 / Google: CDN + サーバーサイドGTM併用ガイド / Analytics Mania: Google Tag Gateway解説 / taggrs.io: Google Tag Gateway vs Server-Side GTM
整理すると、Google タグ ゲートウェイは「安いから乗り換える」という話ではなく、サーバーコンテナで得られる制御(データの加工・マスキング、独自ロジックでのBot判定、複数送信先への分配)が要らないなら、そもそも自分でサーバーを持たなくてよいという、選択肢そのものが増えた話です。逆に言えば、3章で挙げた「効果がある」の中でもマスキング・ガバナンス、独自ロジックでのBot判定、複数送信先への分配を実際に使いたい場合は、これまで通りサーバーコンテナが必要になります(WAFの標準防御自体は失われません)。
GTMの実行タイミングを時系列で追う
ここまでで「Webコンテナはブラウザの中で動く」「サーバーコンテナは自社インフラの中で動く」という違いは説明しました。ただ、これだけだと「じゃあ実際にページを開いてから何が起きているのか」が掴みにくいと思います。同じGTM管理画面の中に両方の設定があるのに、実行される場所もタイミングもバラバラだからです。
まずは導入前、ブラウザが直接Googleと通信していた頃の流れです。

赤く塗った箇所が、3章で説明した「壊れやすいポイント」です。企業ネットワークのセキュリティ製品がドメイン単位で通信を遮断していたり、Safari(ITP)などがサードパーティCookieの保持期間を制限していたりする箇所が、そのままこの図に対応しています。
エンドユーザーが初めてページを開いてから、GA4にデータが届くまでを時系列で並べたのが下の図です。

緑で塗った「サーバーコンテナ」の段が、導入前の図には存在しなかった新しいポイントです。ここで初めて、外部に送る前にデータを検閲・加工したり、Botなどの不正トラフィックを管理したりできるようになります。
流れを言葉でも追っておきます。
- ブラウザがページをリクエストし、自社サービス本体がHTMLを返す。head内にはGTMの読み込みスニペットが埋め込まれているが、参照先は自社ドメイン(サーバーサイドGTMのインフラ)になっている
- ブラウザがそのURLへ
gtm.js本体をリクエストする。ここで応答しているのは自社インフラ側のサーバーサイドGTMで、返しているのは「Webコンテナ」の設定一式をJavaScriptにしたもの - ブラウザが受け取った
gtm.jsを実行する。ここで初めて「Webコンテナ」が実際に動く。この時点までWebコンテナはただの設定データであり、動いていたのはサーバー側だけだったという点に注意 - Webコンテナがタグ・トリガーを評価し、計測イベント(hit)を作って、自社インフラ(サーバーサイドGTM)へ送信する
- ここで「サーバーコンテナ」が動く。クライアントがリクエストを解析し、トリガーに一致すればタグが実行され、GA4など外部サービスへ転送する
同じ管理画面上の設定なのに、③はブラウザの中、⑤は自社インフラの中で動いている、という点がこの図で一番伝えたいことです。「Webコンテナの設定を配る役目」と「Webコンテナ本体が動く場所」が別であることも、混乱しやすいポイントとして併せて意識しておくとよいと思います。
Google タグ ゲートウェイ(CDNのみ)の場合
前節で見た「Google タグ ゲートウェイ(CDNのみ)」のアーキテクチャを、同じように時系列で見ておきます。

時系列で見ても、自社が関与するのは「CDNの素通し転送」だけになり、前節の図で緑色に塗って強調していた「サーバーコンテナが動く」工程がまるごと無くなることがわかります。
確定した設計判断
最終的に採用した構成のポイントをまとめます。
- ・ホスティング: AWS Lambda(コンテナイメージ) + Lambda Web Adapter。既存インフラがAWSに寄っていたための選択で、GCPが前提ならCloud Runが素直な選択肢
- ・CDNとLambdaの間はOAC(署名付きアクセス制御)を使わない: ブラウザからの生のPOSTリクエストは署名を付けられないため、この手の署名前提のアクセス制御とは相性が悪い。Lambda側は認証なしで公開し、CDN側は普通のオリジンとして前段に置くだけにする
- ・VPCには繋がない: このサーバーは社内DBなど他の内部リソースにアクセスする必要が無いため、VPCに接続しない。VPC接続は起動が遅くなる要因になるため、必要が無いなら繋がない方が良い
- ・サブドメインの命名: "gtm"や"sgtm"という文字列を含めない。前述の通り、広告ブロッカーの一部ルールがこうした命名パターンそのものを狙っているため
- ・プレビュー機能は今回は導入しない: GTMの「公開前にタグの動作を確認する」プレビュー機能は、そのための専用インスタンスをもう1つ用意する必要がある。コスト最小化を優先し、今回は見送った(必要になれば後から追加できる構成にしてある)
追記: /g/collect(GA4)への直接攻撃・なりすましを防ぐ
ここまでの内容とは別の話ですが、この記事を書いたあとに気づいた、もう1つの穴を塞いだ記録です。サーバーサイドGTMを入れていてもいなくても関係なく効いてくる話なので、追記として残します。

測定IDが漏れると何ができてしまうか
GA4の計測データは、最終的に/g/collectというパスへ、測定ID(tid=G-XXXXXXX)付きで送られます。このパス自体はGA4のプロトコル上固定で、前述の「gtm.js配信パスのランダム化」のような対策は効きません。そして測定IDは、ブラウザの開発者ツールを開けば誰でも見える、もとから非公開ではない値です。
これを知っていれば、自社のサイトを一切経由せず、Googleの本物の収集エンドポイントへ直接偽のヒットを送りつけることができます。いわゆる「ゴーストトラフィック」「ヒットスプーフィング」と呼ばれる、GA4のデータを汚染する既知の攻撃です*1。自社のサイトを経由しないので、CDNやWAFで防ぐこともできません。
やるなら早いほうがいい理由
この対策の本質は「本物の測定IDをブラウザに一切出さない」ことです。裏を返すと、一度でも本物のIDが誰かに見られて保存されてしまったら、後から対策しても手遅れです。IDそのものを変更することはできないので、「後で気が向いたときにやろう」ではなく、気づいた時点でやるべき対策だと考えています。
対策: 測定IDを分離する
やることはシンプルです。ブラウザ側(Webコンテナ)には本物とは無関係なダミーの測定IDを渡し、サーバーコンテナ側で本物のIDに差し替えてからGA4へ転送する、という構成に変えます。
- ・ブラウザの開発者ツールやパケットキャプチャで見えるのは、常にダミーIDだけになる
- ・本物のIDを知っているのはサーバーコンテナの設定だけで、外部から観測できない
- ・この記事の「具体的な設定手順」の手順4で説明した通り、サーバー側の転送タグは元々測定IDを明示入力する仕様だったので、サーバー側は何も変更しなくてよい(既に本物のIDが設定済みのはず)
流れを図にすると、こうなります。

ダミーIDは「実在する、使い捨てのプロパティ」にする
ダミーIDは適当な文字列ではなく、実際にGA4で新規作成した、本物とは完全に別のプロパティのIDを使うことをおすすめします。理由は2つあります。1つはsGTMのGA4クライアントが受信した測定IDをもとに設定を自動取得する挙動があり、実在しないIDだとここでエラーや遅延の原因になりうるため。もう1つは、実在するプロパティなら「誰かがこのIDに直接攻撃してきていないか」をリアルタイムレポートで監視するハニーポットとして使えるためです。
また、本物と同じプロパティの中に新しいストリームを追加するのではなく、完全に別のプロパティとして作ってください。GA4は「主要イベント(旧コンバージョン)」「オーディエンス」「Google広告連携」がストリーム単位ではなくプロパティ単位で共有されるため、同じプロパティ内にダミーストリームを置くと、そこへの攻撃がそのまま本物の集計データを汚染してしまいます。
実際の手順
GA4の管理画面から、プロパティを新規作成します。

プロパティ名は、後から見て何のためのものかわかる名前にしておきます(筆者は「(ダミー)」「(decoy / 計測ID分離用・未使用)」のように明記しました)。

作成すると、本物のプロパティとは別の1本として並びます。無関係な他のプロパティ名はここではマスキングしていますが、要は「本物」と「ダミー」が別々のプロパティとして独立していることが重要です。

作成したダミープロパティのホーム画面に、そのまま使える測定IDが表示されます。

ここで変更するのはWebコンテナ側だけです。サーバーコンテナ側(手順4で作った転送タグ)は、そもそも測定IDを明示入力する仕様だったので、今まで通り本物のIDを入れたまま、一切変更しません。

対してWebコンテナ側のGA4タグは、測定IDをダミーIDに書き換えて保存・公開します。

確認
公開後、本番サイトの開発者ツール(Networkタブ)で/g/collectリクエストを見ると、tidがダミーIDに変わっていることを確認できます。

GA4管理画面のリアルタイムレポートで、本物のプロパティには今まで通りデータが届いており、ダミーのプロパティには何も届いていないことも合わせて確認しました。ブラウザから見える情報だけが変わり、実際の計測は何も変わっていない状態です。
*1 出典: Thyngster: Securing Google Analytics 4 (GA4)
なお、サーバーコンテナのURL(data.kkm-mako.com)自体への迷惑アクセスは、Googleを直接狙う攻撃とは違い必ず自社インフラを経由するので、うざくなったらこちら側で調整が効きます(署名付きトークンでのJS検証など)。今は様子見で十分と判断し、そこまでは実装していません。

堀川 慎
Backend Engineer / AWS / Django / Go