
サーバーサイドGTMの導入価値と方法を、現役フリーランスエンジニアが解説
サーバーサイド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宛てのリクエストが直接見えるなら、それは今回説明する「サーバーサイド化」がまだされていない状態です。
なぜサーバーサイドがいいの?
導入の動機になりうる効果を、実際に検証した内容も交えて整理します。「なんとなく良さそう」で終わらせず、効果があるものとないものを分けて書きます。
効果がある: 企業ネットワークのドメイン単位遮断の回避
法人の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化すると、この通信も自社の管理下に入るため、計測データに混ざるノイズを自分の意思でコントロールできるようになります。
誤解しやすいポイント: ページ表示速度
「クライアント側のJSが減るのでページが速くなる」という説明も見かけますが、これは正確ではありません。ブラウザが読み込むスクリプト自体は変わらず、読み込み先が変わるだけです。速度改善を主目的にするのはおすすめしません。
導入方法
サーバーサイドGTMは「サーバーでコンテナを1つ動かす」だけなので、ホスティング先はいくつか選択肢があります。
標準: Google Cloud Run
Googleが公式にサポートしているのはCloud Run(GCP)です。GTMの管理画面から「自動でプロビジョニングする」を選ぶと、Cloud Run上にコンテナが自動構築されます。GCPを既に使っている、あるいはインフラをどこに置くか特にこだわりがないなら、これが一番手間のかからない選択肢です。
筆者が選んだ、ややトリッキーな構成: 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だけのようなシンプルな構成であればコード量自体は小さく済みますが、「公式のタグ管理基盤を捨てる」という判断であることは意識しておく必要があります。今回は公式コンテナを使う方針にしました。
具体的な設定手順(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: 公開する
サーバーコンテナ・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コンテナの役割自体は変わっていないことも確認できます。
確定した設計判断
最終的に採用した構成のポイントをまとめます。
- ・ホスティング: AWS Lambda(コンテナイメージ) + Lambda Web Adapter。既存インフラがAWSに寄っていたための選択で、GCPが前提ならCloud Runが素直な選択肢
- ・CDNとLambdaの間はOAC(署名付きアクセス制御)を使わない: ブラウザからの生のPOSTリクエストは署名を付けられないため、この手の署名前提のアクセス制御とは相性が悪い。Lambda側は認証なしで公開し、CDN側は普通のオリジンとして前段に置くだけにする
- ・VPCには繋がない: このサーバーは社内DBなど他の内部リソースにアクセスする必要が無いため、VPCに接続しない。VPC接続は起動が遅くなる要因になるため、必要が無いなら繋がない方が良い
- ・サブドメインの命名: "gtm"や"sgtm"という文字列を含めない。前述の通り、広告ブロッカーの一部ルールがこうした命名パターンそのものを狙っているため
- ・プレビュー機能は今回は導入しない: GTMの「公開前にタグの動作を確認する」プレビュー機能は、そのための専用インスタンスをもう1つ用意する必要がある。コスト最小化を優先し、今回は見送った(必要になれば後から追加できる構成にしてある)

堀川 慎
Backend Engineer / AWS / Django / Go