Nginxで静的ファイルを配信する:最適化のヒント

適切なキャッシュヘッダー、Gzip圧縮、安全なデフォルト設定、隠しファイルの保護に関するヒントを用いてNginxの静的ファイル配信を最適化し、古いアセットによる問題を減らしてサイトを高速化します。

Nginxで静的ファイルを配信する:最適化のヒント

Nginxで静的ファイルを配信することは、画像、CSS、JavaScript、ダウンロード、ビルド済みフロントエンドアセットを配信する最も一般的で効率的な方法の1つです。Nginxはこの仕事が非常に得意ですが、いくつかの設定の選択によって、高速で予測可能なサイトと、帯域幅を無駄にしたり古いコンテンツを配信したりするサイトの違いが生まれます。

静的ファイルの最適化は、主に明確なパス、正しいキャッシュヘッダー、圧縮、安全なデフォルト設定に関するものです。強力な結果を得るために複雑な設定は必要ありませんが、キャッシュルールがファイルの命名方法とデプロイ方法に一致している必要があります。

明確な静的ファイルの場所から始める

最もシンプルな静的ファイルの設定は、roottry_filesを使用します。

server {
    listen 80;
    server_name example.com;

    root /var/www/example.com/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

この設定では、/css/app.cssへのリクエストは/var/www/example.com/public/css/app.cssにマッピングされます。ファイルが存在しない場合、Nginxは404を返します。

この直接マッピングはデバッグ時に便利です。ブラウザからURLを取得し、ファイルシステムパスに変換して、ファイルが存在するかどうかを確認できます。

ls -l /var/www/example.com/public/css/app.css

ファイルが存在するのにNginxが404を返す場合は、別のroot、より具体的なlocationブロック、またはパスを上書きするインクルードファイルを探してください。

シングルページアプリケーションの場合、不明なルートをindex.htmlにフォールバックさせたい場合があります。

location / {
    try_files $uri $uri/ /index.html;
}

これはフロントエンドルーターに便利ですが、すべてのサイトで盲目的に使用しないでください。欠落しているアセットもindex.htmlを返す場合、壊れたJavaScriptや画像パスのデバッグが混乱する可能性があります。多くのチームはアセット用に別の場所を使用して、欠落しているファイルが実際の404を返すようにします。

URLパスを別のファイルシステムパスにマッピングする必要がある場合は、aliasを使用することもできます。

location /assets/ {
    alias /srv/shared-assets/;
}

末尾のスラッシュに注意してください。aliasを使用する場合、ロケーションパスとファイルシステムパスは通常、両方とも/で終わる必要があります。不一致は予期しないファイルパスを生成する可能性があります。

安全なパターンの1つは次のとおりです。

location /downloads/ {
    alias /srv/downloads/;
    try_files $uri =404;
}

ここでは、/downloads/manual.pdf/srv/downloads/manual.pdfにマッピングされます。末尾のスラッシュの規則がないと、存在しないパスを誤って構築したり、公開するつもりのなかったディレクトリを公開したりするのは簡単です。

マッチング動作の詳細については、Nginx locationブロックを参照してください。

ブラウザキャッシュヘッダーを追加する

静的ファイルはブラウザキャッシュの優れた候補です。ユーザーがapp.cssを一度ダウンロードした場合、変更がない限り、ブラウザはページを表示するたびに再度フェッチするべきではありません。

バージョン管理されたアセットには、長いキャッシュ有効期間を使用します。

location /assets/ {
    root /var/www/example.com/public;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

これは、デプロイ中にファイル名が変更される場合に最適に機能します(例:app.8f3a91.cssbundle.20260523.js)。コンテンツが変更されたときにファイル名が変更される場合、ブラウザは古いファイルを長期間安全にキャッシュできます。

同じ名前を保持するファイルには、より短いキャッシュを使用します。

location = /index.html {
    root /var/www/example.com/public;
    expires -1;
    add_header Cache-Control "no-cache";
}

このパターンはフロントエンドアプリで一般的です。HTMLファイルは新鮮な状態を保ち、ハッシュ化されたCSSとJavaScriptファイルは積極的にキャッシュされます。

実用的な例:ReactまたはVueのビルドは、ハッシュ化されたアセットを/assets/に、プレーンなindex.htmlを出力します。/assets/を1年間キャッシュしますが、index.htmlは再検証させます。ユーザーは高速な再訪問を得られ、新しいデプロイでも最新のアセット参照が読み込まれます。

キャッシュルールを変更した後は、推測ではなくヘッダーをテストします。

curl -I https://example.com/assets/app.8f3a91.css
curl -I https://example.com/

ハッシュ化されたアセットに長期間有効なCache-Control値が表示されることを確認します。通常、HTMLページは再検証するか、短い有効期間を使用する必要があります。両方が1年間キャッシュされると、デプロイによってユーザーが古いJavaScriptを指す古いHTMLファイルに固定される可能性があります。

テキストアセットに圧縮を使用する

CSS、JavaScript、SVG、JSONなどのテキストアセットは圧縮効果が高いです。httpブロックでGzipを有効にできます。

gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_types
    text/css
    application/javascript
    application/json
    image/svg+xml;

GzipがJPEG、PNG、WebP、MP4、zipファイルにあまり役立つとは期待しないでください。これらの形式はすでに圧縮されています。それらを再度圧縮しようとすると、通常はCPUを浪費します。

トラフィックの多い静的サイトの場合は、事前圧縮ファイルを検討してください。ビルドプロセスで大きなCSSファイルとJavaScriptファイルの.gzバージョンを作成し、ブラウザがGzipをサポートしている場合にNginxがそれらを提供できます。

gzip_static on;

これにより、Nginxがディスクから事前圧縮ファイルを読み取るため、ランタイムの圧縮作業が削減されます。これは、アセットが事前にビルドされ、リクエスト間で変更されない場合に最も役立ちます。

圧縮はアセット配信の一部にすぎません。ファイルサイズは依然として重要です。未使用のJavaScriptを削除し、ビルドプロセス中に画像を最適化し、ユーザーが必要としない大きなファイルを配信しないようにします。

圧縮をテストするときは、Accept-Encodingヘッダーを含めます。

curl -I -H 'Accept-Encoding: gzip' https://example.com/assets/app.js

Content-Encoding: gzipVary: Accept-Encodingを探します。Varyヘッダーは、CDNまたは共有キャッシュがNginxの前にある場合に重要です。圧縮された応答と圧縮されていない応答が混同されてはならないためです。

ファイル配信と安全性を向上させる

Nginxはデフォルト設定で静的ファイルを効率的に提供できますが、本番環境ではいくつかの詳細が役立ちます。

まず、明示的に必要でない限り、ディレクトリリストを無効にします。

autoindex off;

ディレクトリリストは、公開するつもりのなかったファイル名と構造を明らかにする可能性があります。

次に、.env.git、およびその他のドットファイルなどの隠しファイルへのアクセスをブロックします。

location ~ /\.(?!well-known) {
    deny all;
}

.well-knownの例外は、証明書検証と標準ベースのファイルがそのディレクトリを使用する可能性があるため、一般的です。

3番目に、静的ファイルのアクセス許可がNginxにファイルの読み取りを許可するが、Webサーバーに不要な書き込みアクセス権を与えないことを確認します。一般的な設定では、デプロイツールがファイルを書き込み、Nginxワーカーユーザーがそれらを読み取ることができます。

4番目に、MIMEタイプを確認します。Nginxには通常mime.typesファイルが含まれていますが、機能を削減したコンテナやカスタムビルドでは見落とされる可能性があります。CSSがtext/plainとして提供される場合、ブラウザはそれを拒否したり、異なる動作をしたりする可能性があります。

次を使用します。

include /etc/nginx/mime.types;
default_type application/octet-stream;

最後に、アセットに対する繰り返しの404応答がないかログを監視します。これは、デプロイが存在しないファイルを参照している、キャッシュがまだ古いファイル名を指している、またはaliasパスが間違っていることを意味することがよくあります。

静的配信が遅いと感じた場合、見つけられるすべてのチューニングディレクティブをコピーすることから始めないでください。最初に、問題が実際にNginxであるかどうかを確認してください。大きな画像、最適化されていないフロントエンドバンドル、リモートストレージマウント、CDNキャッシュミスは、サーバーブロックのマイクロ最適化の欠如よりも一般的な原因です。

簡単なローカルチェックの場合:

curl -o /dev/null -s -w 'status=%{http_code} size=%{size_download} time=%{time_total}\n' https://example.com/assets/app.js

次に、それをCDNログ、ブラウザの開発者ツール、またはユーザーと同じ地域からのリクエストと比較します。Nginxからの高速な応答とブラウザでの低速な応答は、通常、配信パスの他の場所を指しています。

サポートを求めるタイミング

静的ファイルが共有ストレージ、マウントされたボリューム、オブジェクトストレージゲートウェイ、またはNginxの前にあるCDNから提供されている場合は、DevOpsエンジニアを連れてきてください。最適なキャッシュ戦略は、Nginxサーバーブロックだけでなく、配信パス全体に依存します。

また、デプロイ後にユーザーが古いJavaScriptを報告する場合もサポートを求める必要があります。これは通常、キャッシュルールとファイル名のバージョン管理戦略が一致していないことを意味します。

Nginxで静的ファイルを配信する場合、パスが予測可能で、キャッシュの有効期間がファイル名戦略と一致し、テキストアセットが圧縮されている場合に最適に機能します。欠落しているファイルを可視化し、隠しファイルを保護し、設定変更ごとにヘッダーをテストします。クリーンな静的設定により、可動部品を追加せずにサイトを高速化できます。