自前の CDN サーバーを構築する:1 回取得して、何千回でも配信
CDN サーバーは各ファイルをオリジンから 1 回だけ取得し、その後は何千回でも自分で配信します。オリジンサーバーに届くリクエストはごく一部になり、訪問者は短い読み込み時間を得られます。その構築手順を解説します。
訪問者がサイトから読み込むファイルは、1 つずつオリジンサーバーがファイルシステムから取り出し、応答に詰めて送り出しています。1 日の訪問者が 10 人なら、誰も気づきません。同時に 1 万人が訪れると、アプリケーション自体は何もしていないのにサイトが遅くなる原因が、まさにこれです。
CDN サーバーはその前に入ります。各ファイルをオリジンから1 回だけ取得し、その後は必要な回数だけ自分で配信します。これによりオリジンに届くリクエストはごく一部になり、訪問者は配信だけを仕事にしているサーバーからファイルを受け取ります。本記事では、これが実際にどれだけの効果を生むのか、nginx でこうしたサーバーをどう構築するのか、そしてトラフィックと拠点で何が重要になるのかを解説します。
CDN サーバーが実際に行うこと
流れは 3 文で説明できます。
- 訪問者が CDN サーバーにファイルを要求します。
- そこにすでにあれば、すぐに送り出されます。これがヒットです。
- そこにない場合は、CDN サーバーがオリジンから取得し、保存して配信します。これがミスで、有効期間ごとにファイルあたり 1 回だけ起きます。
原理はストリーミングサーバーと同じで、1 回取得して何度でも配信します。違いは、ストリーミングサーバーが流れ続けるデータを配り、CDN サーバーが完成したファイルを配るという点だけです。
計算:キャッシュはどれだけ負荷を下げるか
決め手はヒット率、つまりキャッシュが自分で応答したリクエストの割合です。1 日 100 万リクエストの場合、負荷の下がり方は次のようになります。
| ヒット率 | オリジンへのリクエスト | 負荷の軽減 |
|---|---|---|
| 50% | 500,000 | 2 分の 1 |
| 80% | 200,000 | 5 分の 1 |
| 95% | 50,000 | 20 分の 1 |
| 99% | 10,000 | 100 分の 1 |
静的ファイルでは 95% 以上が通常です。画像、スクリプト、フォントはほとんど変わらないからです。95% から 99% への引き上げは、数字から想像するよりも価値があります。残っている負荷をさらに 5 分の 1 にするからです。
同時にトラフィックの位置が移ります。オリジンはほとんど何も転送しなくなり、代わりに CDN サーバーがその分を転送します。1 日 50 万回の画像呼び出しで 1 枚 400 KB なら、1 日あたり 200 GB、つまり月に約 6 TB で、これは画像だけの数字です。したがって CDN ノードで重要な数字は CPU ではなく、回線と転送量です。
自前の CDN サーバーが見合う場面
商用の CDN 事業者は転送した 1 GB ごとに課金します。これは手軽で、転送量が少なければ安く済みます。ある量を超えると、この関係が逆転します。
| 状況 | 自前の CDN サーバー | 1 GB あたりの課金 |
|---|---|---|
| 月に数 GB | ほとんど見合わない | 安い |
| 月に数 TB | 固定の月額 | 量に比例して増える |
| 大きなダウンロード、動画アーカイブ | はっきり有利 | すぐ高額になる |
| ルールとログの完全な制御 | 完全にできる | 制限がある |
さらに、数字にしにくい点があります。自前のノードなら、何をキャッシュするか、どれだけ保持するか、いつ消すかを自分で決められます。他社のルールに縛られず、他社の枠組みに収まらないものも配信できます。
nginx をキャッシュとして構築する
インストールする
apt update
apt install -y nginx
必要なのはこれだけで、キャッシュ機能は nginx に組み込まれています。ファイルを置くディレクトリを作成します。
mkdir -p /var/cache/cdn
chown www-data:www-data /var/cache/cdn
設定する
キャッシュは http ブロックで一度定義し、その後それぞれの location で使います。
proxy_cache_path /var/cache/cdn
levels=1:2
keys_zone=cdn:100m
max_size=200g
inactive=30d
use_temp_path=off;
proxy_cache_key "$scheme$request_method$host$request_uri";
各値の意味は次のとおりです。keys_zone=cdn:100m は約 800,000 エントリー分に相当し、max_size はディスク上の容量を制限し、inactive=30d は 30 日間誰も要求しなかったファイルを破棄し、use_temp_path=off はディレクトリ間の不要な移動を避けます。
その下が実際のサーバーです。
server {
listen 443 ssl;
http2 on;
server_name cdn.example.com;
ssl_certificate /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
location / {
proxy_pass https://origin.example.com;
proxy_set_header Host origin.example.com;
proxy_http_version 1.1;
proxy_cache cdn;
proxy_cache_valid 200 301 302 24h;
proxy_cache_valid 404 1m;
# 同時に発生した複数のミスを 1 つのリクエストにまとめる
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
# オリジンに問題があるときも配信を続ける
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
# セッション Cookie にキャッシュを壊させない
proxy_ignore_headers Set-Cookie;
proxy_hide_header Set-Cookie;
add_header X-Cache-Status $upstream_cache_status always;
}
}
このなかで特に注目すべき行が 3 つあります。proxy_cache_lock は、新しいファイルに対して 100 人の訪問者が同時にオリジンへ問い合わせるのを防ぎ、問い合わせるのは 1 人だけ、残りは待つようにします。proxy_cache_use_stale は、オリジンが再起動中であったり応答が遅いときでも配信を維持します。そして Set-Cookie を隠すことで、セッション Cookie を含む応答がひとつあるだけでキャッシュ全体が使い物にならなくなるのを防ぎます。
効いているかを確認する
nginx -t && systemctl reload nginx
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
curl -sI https://cdn.example.com/bild.jpg | grep -i x-cache-status
1 回目の呼び出しでは MISS、2 回目では HIT と表示されます。2 回目も MISS のままなら、原因はほぼ必ず 3 つの定番のどれかです。応答に含まれる Set-Cookie、オリジンからの Cache-Control: no-store、あるいは長さの指定が欠けていることです。
何をキャッシュし、何をしないか
きれいな解き方は、オリジン自身にそれを言わせることです。内容を知っているのはオリジンで、キャッシュではありません。そのために、オリジンが適切なヘッダーを設定します。
| 内容 | オリジンでのヘッダー |
|---|---|
| 名前にチェックサムが入ったファイル | Cache-Control: public, max-age=31536000, immutable |
| バージョン管理のない画像やダウンロード | Cache-Control: public, max-age=86400 |
| 公開ページの HTML | Cache-Control: public, max-age=300 |
| ログイン後の領域、カート | Cache-Control: private, no-store |
キャッシュ側で判断したい場合は、ファイル種別ごとに location を分ければできます。
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico|css|js|woff2|mp4|zip)$ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 7d;
add_header Cache-Control "public, max-age=604800";
add_header X-Cache-Status $upstream_cache_status always;
}
内容を狙って消す
最も簡単なのは、そもそも消さずに済ませる方法です。変更した画像ごとに新しいファイル名、たとえば logo.a1b2c3.png を付けていれば、この問題は起きません。古いファイルはいずれ自然にキャッシュから外れます。
それができない場合、nginx では無償のモジュール ngx_cache_purge が役に立ちます。追加モジュールがなくても、nginx はキャッシュキーの MD5 値をファイル名に使うため、パスをキーから計算できます。
KEY="httpsGETcdn.example.com/bild.jpg"
H=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
rm -f "/var/cache/cdn/${H: -1}/${H: -3:2}/$H"
日常の運用なら、この数行をパスの一覧に対して実行する小さなスクリプトで足りることが多いです。
大きなファイルと動画
数百 MB を超えるファイルにはブロックへの分割が必要です。これがないと、動画内で再生位置を動かすたびにキャッシュがファイル全体を取り直します。
location /video/ {
proxy_pass https://origin.example.com;
proxy_cache cdn;
proxy_cache_valid 200 206 7d;
slice 1m;
proxy_set_header Range $slice_range;
proxy_cache_key "$uri$slice_range";
}
これにより nginx は、実際に要求されたブロックだけを取得します。視聴者が映画の中ほどへ飛んでも、回線を流れるのは数 GB ではなく数 MB です。
TLS、HTTP/2、HTTP/3
CDN ノードは多数の接続に多数の小さなファイルを配信します。新しいプロトコルが効くのはまさにこの場面です。HTTP/2 は http2 on; だけで済みます。HTTP/3 が特に効くのはモバイル回線で、網が切り替わっても接続が途切れないからです。
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
このときファイアウォールで UDP 443 を開けるのを忘れないでください。HTTP/3 を設定したのに誰も使っていない原因として、これが最も多いものです。
複数の拠点
ノードが 1 つあるだけで、利益の大部分、つまりオリジンの負荷軽減は得られます。2 つ目の利益、訪問者までの距離の短さは、複数の拠点を持ってから出てきます。
振り分けは DNS で行います。同じ名前が、訪問者の所在地に応じて別のノードを指します。そのためには地理的な振り分けに対応した DNS 事業者が必要です。すべてのノードは同じ設定と同じオリジンを持ち、互いのことを何も知りません。この構成が頑丈なのはまさにそのためで、1 つのノードが落ちたら DNS から外せば、残りが引き受けます。
KernelHost はドイツ、米国西海岸、米国東海岸、シンガポール、韓国に拠点を用意しています。遅延を含む一覧はサーバー拠点でご確認いただけます。目安としては、訪問者の相当な割合がいる大陸ごとにノードを 1 つです。
推測せずに測る
キャッシュの状態をログに書けば、ヒット率をいつでも数えられます。
log_format cdn '$remote_addr $status $body_bytes_sent '
'$upstream_cache_status "$request"';
access_log /var/log/nginx/cdn.log cdn;
集計はワンライナーでできます。
awk '{print $4}' /var/log/nginx/cdn.log | sort | uniq -c | sort -rn
静的ファイルで HIT の割合が 90% を下回る場合、原因を探す価値はほぼ必ずあります。多いのは、応答に含まれる Cookie、短すぎる有効期間、あるいはアドレスの末尾のパラメーターが毎回変わり、キャッシュにはそのたびに別のファイルに見えている場合です。
どのサーバーが向いているか
CDN ノードは配信のための機械です。そのため重要な性能の順序は、アプリケーションサーバーとは違ってきます。
- トラフィックと回線。負荷はすべてここに生まれます。1 つのノードは運用中、月に数 TB を定常的に転送します。
- ディスク。キャッシュは小さなランダムアクセスの集まりなので、NVMe が明確に有利です。容量は、持っているデータ全体の大きさではなく、そのうちどれだけが頻繁に要求されるかで決めます。
- RAM。OS は空きメモリーを、ディスクの手前の二次キャッシュとして使います。ここでは RAM が多いほど、そのまま速さにつながります。
- CPU。最も影響が小さい部分です。使われるのは TLS だけで、最近のプロセッサーは片手間に処理します。
まさにこの用途のために作ったのが、当社の無制限トラフィックのルートサーバーです。1 台ごとに絞りのない実測 2 × 1 Gbit/s、固定の月額で無制限の転送量、RAID 構成のデータセンター向け NVMe、そして 3.2 Tbps のフラッド対策を標準で備えています。用意にかかる時間は 30 秒未満で、完全な root 権限、IPv4、/64 の IPv6 ネットワークが付きます。料金はプリペイド方式で月単位の解約ができるので、ノード 1 つから始めて、訪問者が各地に広がったらいつでも 2 つ目の拠点を追加できます。
よくあるご質問
CDN サーバーは具体的に何をしますか?
実際にどれくらい負荷が下がりますか?
そのために商用の事業者を使う必要がありますか?
どのファイルをキャッシュし、どれをキャッシュすべきでないですか?
キャッシュはファイルをどれくらい保持すべきですか?
キャッシュから特定のファイルを削除する方法は?
オリジンサーバーが停止したらどうなりますか?
大きな動画ファイルでも機能しますか?
拠点はいくつ必要ですか?
キャッシュが効いているかはどう分かりますか?
2026 KernelHost GmbH。無断複写・転載を禁じます。本ガイドは著作権により保護されております。他のウェブサイトへの掲載は、一部のみの場合や編集を加えた場合であっても、当社の書面による同意なしには認められません。出典の明記とリンクを添えた引用は歓迎いたします。

