unmask

docs

JA4 取得、LB 設定、対応 distro、よくある質問。

LB からクライアントの JA4 を取る

SSL 終端を Load Balancer (GCP / AWS / Cloudflare 等) で行っている場合、backend nginx が見る TLS handshake は LB ↔ nginx 間 のものになり、ここから JA4 を計算しても LB 自身の TLS fingerprint になってしまいます。実際のクライアントの JA4 を取るには、 LB 側でクライアントの TLS handshake から JA4 を抽出し、ヘッダーに詰めて backend へ転送する 設定が必要です。

このページでは各 LB で具体的にどう設定するかを説明します。unmask の native mode / forward-auth mode どちらでも共通です。

1. 全体像

[Client] ──TLS──▶ [LB] ──HTTP + X-Client-JA4──▶ [nginx + unmask]
                  ↑                ↑
       ここで JA4 抽出        header から読む

LB / CDN 側の役割:

設定する箇所は 2 つ:

2. CDN / LB 側の設定

使っている LB / CDN を選んで、クライアントの JA4 を X-Client-JA4 ヘッダーに載せる設定をする。このセクションは native / forward-auth どちらの mode にも当てはまる。

GCP HTTPS Load Balancer

GCP の HTTPS LB は custom request header 機能で {client_ja4_fingerprint} という template variable をヘッダーの値に埋め込めば、 LB が JA4 を計算してヘッダーに展開します。GCP の custom header 機能はクライアントの送信値を 必ず上書きするので、偽装の心配はありません。

手順 1: backend service を編集 (Console)

Google Cloud Console から:

  1. 左メニュー → Network ServicesLoad balancing
  2. 該当の LB をクリック → Edit
  3. Backend configuration → 該当の backend service → ✏️ 鉛筆アイコンで編集
  4. Advanced configurations 展開 → Custom request headers+ ADD HEADER
  5. Header に X-Client-JA4:{client_ja4_fingerprint} を入力
  6. SaveUpdate で完了
[ Screenshot: GCP Console > Load balancing > Edit backend > Custom request headers ]
手順 2: gcloud CLI で同じことをする
# existing backend service の custom header を 1 行追加 (既存 header は維持されない上書きなので注意).
gcloud compute backend-services update YOUR_BACKEND_SERVICE \
    --global \
    --custom-request-header="X-Client-JA4:{client_ja4_fingerprint}"

--custom-request-header を複数回指定すると最後の 1 つだけが残る (既存とまとめたい 場合は全ヘッダーを一度に指定する)。確認は gcloud compute backend-services describe YOUR_BACKEND_SERVICE --global

手順 3: 動作確認

nginx + unmask が動いているホストで:

curl -sI https://your-domain/unmask/healthz \
    | grep -i x-client-ja4    # LB が転送する header. backend で見える
# または unmask admin の stats tab → JA4 verdict 分布が non-empty に
偽装防止: GCP の --custom-request-header はクライアントの送信値を無条件で上書きします。クライアントが X-Client-JA4 を直接 nginx に投げても、LB を経由した時点で消されます。さらに、nginx を LB 経由 (35.191.0.0/16, 130.211.0.0/22, 35.227.0.0/16 等) のみ受け入れるファイアウォール (GCP VPC firewall rule) で多重に防御することを推奨します。

Cloudflare

Cloudflare は既定で cf-ja4 リクエストヘッダー をオリジンに転送します (JA4 fingerprint)。nginx 側で cf-ja4X-Client-JA4 に変換 するだけで unmask が読めます。

手順 1: Managed Transforms で cf-ja4 を有効化
  1. Cloudflare ダッシュボード → 該当の zone を選択
  2. RulesTransform RulesManaged Transforms
  3. Add visitor location headersJA4 fingerprint 系のトグルを ON
  4. cf-ja4 / cf-ja4-signal 等がオリジンへのリクエストに自動で乗る
[ Screenshot: Cloudflare dashboard > Rules > Managed Transforms > Add visitor location headers ]
手順 2: nginx 側で cf-ja4 を X-Client-JA4 に変換

http {} スコープに追加:

# Cloudflare が送ってくる cf-ja4 を unmask が読む X-Client-JA4 に rename.
# cf-ja4 が空なら fallback (直 origin access 等) で空文字 → unmask は
# native module の自計算 $client_ja4 に倒れる.
map $http_cf_ja4 $http_x_client_ja4_remap {
    default $http_cf_ja4;
}

※ unmask の標準 map は $http_x_client_ja4 を読むので、server スコープの set $effective_ja4 $http_cf_ja4; で直接上書きする方が簡単な場合もある。

注意: Cloudflare の Free plan / Pro plan では cf-ja4 が出ない ことがあります。Business / Enterprise plan が必要です (2025 時点)。最新のプラン要件は Cloudflare 公式ドキュメントで確認してください。
偽装防止: nginx を Cloudflare IP (cloudflare.com/ips) からのみ受け入れるファイアウォール (オリジンサーバー / ALB の security group / iptables) を入れる。Cloudflare を経由しない直接アクセスからの cf-ja4 / X-Client-JA4 は、nginx 側の if ($remote_addr ~ ...) で剥ぐ。

AWS ALB / CloudFront

残念ながら 2025 時点で AWS ALB / CloudFront 共に native の JA4 抽出機能はありません。 custom header に template variable を入れる仕組み (GCP の {client_ja4_fingerprint} 相当) もありません。迂回策が必要です。

迂回策 A: Cloudflare を前段に置く

AWS ALB の前段に Cloudflare を入れて、JA4 の抽出を Cloudflare に任せる。最もシンプルで、 本番でもそのまま使える。詳細は Cloudflare タブを参照。

迂回策 B: Lambda@Edge で計算

CloudFront に Lambda@Edge function を割り当て、viewer request の段階で TLS handshake を 解析し、JA4 を計算してヘッダーへ注入する。実装は JA4 の仕様に準拠する。解析は aws-sdk 系のライブラリと自前実装の組み合わせになる。

// Lambda@Edge (Node.js) sketch — viewer request event
exports.handler = async (event) => {
    const req = event.Records[0].cf.request;
    // CloudFront は req.clientIp と一部 TLS info を提供するが,
    // 完全な ClientHello bytes は来ないため, JA4 完全実装は厳しい.
    // 代わりに ja3 hash や clientTLSVersion 等を組み合わせて proxy
    // signature を作る pragmatic 実装が現実解.
    req.headers['x-client-ja4'] = [{ key: 'X-Client-JA4', value: 'placeholder' }];
    return req;
};
現実的な指針: AWS の LB は現状クライアントの JA4 を出さないため、AWS 環境では forward-auth mode + UA フィルタ + rate limit + honeypot による JA4 抜き運用 が現実的です。unmask は JA4 が無くても動作し、ステルス系の bot の検出率は落ちますが、他のシグナルで大半の bot は捕まえられます。
迂回策 C: ALB を経由せず EC2 で直接 TLS 終端

EC2 インスタンスに Elastic IP + ACM 証明書を直接割り当てて nginx で TLS を終端する。nginx は unmask native module でクライアントの TLS handshake を直接見られるので、完全な JA4 を取得できる。ただし、 ALB の機能 (sticky sessions, WAF integration) は使えなくなる。

その他の LB / Reverse proxy

TLS を終端する任意の前段レイヤー

レシピは上記の LB と同じで、前段レイヤーで TLS を終端し、そこでクライアントの JA4 を 計算して X-Client-JA4 ヘッダーに詰めて backend へ転送する (クライアントの送信値は上書きする)。unmask がそのヘッダーを信頼するよう、 管理画面 設定 → ネットワークcustom LB セクションで 前段レイヤーの接続元 CIDR を追加する。クライアントの TLS handshake から JA4 を出せる reverse proxy なら、同じ方式で unmask を動かせる。

nginx + freenginx / FoxIO ja4 module

unmask の native mode に切り替えるのが最も楽 (公式 module を提供)。インストール手順は install guide の native mode を参照。

3. unmask 側の設定

§2 で前段 (LB / CDN) が X-Client-JA4 を送るようになったら、unmask 側は「どの送信元を信頼するか」を 1 箇所決めるだけ。native / forward-auth / Apache のどれでも操作は同じ (管理画面の 設定 → ネットワーク タブ)。

  1. 「信頼する LB / CDN」で、使っている LB / CDN (GCP HTTPS LB / Cloudflare / AWS CloudFront 等) にチェックを入れる
  2. 一覧に無いもの (自社内部 LB / ALB 等) は custom LB で接続元 CIDR と JA4 ヘッダーを追加する

保存すると、その送信元から来た X-Client-JA4 だけを採用する gate が生成され、直アクセスの詐称ヘッダーは破棄される。あとは sudo nginx -s reload (Apache は reload) するだけで、server block / vhost への JA4 用追記は不要。これで完了

仕組み (運用者は意識不要): native は plugin が ClientHello から JA4 を算出 (前段が無くても動く)、forward-auth (nginx)forward-auth-lbtrust.conf + 同梱 server.incApache (mod_lua) は接続元 peer を daemon に渡して判定。専用トグルは無い。

補足: ① 別ホストから forward-auth する構成なら、その nginx / Apache の IP も「信頼する LB / CDN」に追加する。② Apache は接続元 peer を渡す mod_rewrite 1 行を vhost に足す (同梱 apache-forward-auth.conf 参照)。

4. 動作確認 (admin ダッシュボード)

  1. nginx reload 後、普通のブラウザで対象のサイトにアクセス
  2. unmask admin の stats tab → 「JA4 verdict 分布」カード
  3. verdict が (none) ばかりなら、ヘッダーが来ていないか、信頼 IP フィルタで剥がされている
  4. verdict に chrome_fake_h1 等の bot 名が出れば、JA4 が取れて分類されている ✓
curl -sk https://your-domain/unmask/healthz -I 2>&1 | head
# nginx 側 access_log に ja4=t13d... が記録されるか確認:
sudo tail -1 /var/log/nginx/access.log | grep ja4