Djangoでファイルを「直接」配信せず、Nginxに「代わりに」配信させる:X-Accel-Redirectでダウンロード性能を向上させる
多くのDjangoアプリケーションでは、保護されたファイル(ログインユーザーのみ、購入後に閲覧可能など)を配信する際に、FileResponse のように Pythonプロセスがファイルを読み取り クライアントへ送信します。トラフィックが小さく、内部サーバ間の通信であれば、この方法でも十分です。
しかし、ファイルリクエストが急増すると、アプリケーションサーバ(Python)がファイル配信に時間を取られ、権限チェックやビジネスロジック、API処理が滞ります。そこで「権限チェックはDjangoが行い、ファイル転送はNginxに任せる」代表的な手法が X-Accel-Redirect です。
なぜPythonがファイルを直接送るとボトルネックになるのか
Djangoがファイルを直接配信する流れは次のようになります。
- リクエスト受信
- 権限チェック
- ディスク/ストレージからファイル読み込み
- アプリケーションプロセスがネットワークへ送信(ストリーミング)
問題は 3〜4が「重い作業」 である点です。
- ファイルが大きいほど送信時間が長くなる
- 同時ダウンロードが増えるとワーカー/スレッド/プロセスが次第にロックされる
- 結局API応答遅延、タイムアウト、サーバ増設の圧迫につながる
対照的に Nginxは静的ファイル配信に最適化 されていて、カーネルレベル最適化(sendfile)、効率的なイベントループ、バッファリング/レンジリクエスト処理など「ファイル配信」に特化した機能を活用します。
X-Accel-Redirectの核心アイデア
Djangoは「検査のみ」し、Nginxは「転送のみ」する。
動作原理
- クライアントが
/download/123のようなURLをリクエスト - DjangoがDB照会/権限チェックのみ実行
- Djangoがレスポンスヘッダーに以下のように記載し、空ボディで返却
X-Accel-Redirect: /_protected/real/path/to/file.webp
4. Nginxがこのヘッダーを見て 内部的にファイルを探し、クライアントへ直接送信
- Djangoはファイル内容を直接読み取らない
つまり、Djangoは「このユーザーがこのファイルを取得してもよいか?」だけを担い、実際のファイル転送はNginxに委ねます。
この手法が特に有効なケース
以下の状況で効果が大きくなります。
1) ダウンロード/画像リクエストが多く、同時性が高いサービス
- コミュニティ/メッセンジャー画像、添付ファイル、レポートPDFダウンロード
- 「リクエスト数は多いがロジックは単純」なパターンではX-Accel-Redirectが光ります。
2) ファイルサイズが大きい、またはRangeリクエストが重要なサービス
- 動画/音声/大容量圧縮ファイル
- ブラウザ/プレイヤーが
Range(区間リクエスト)で再生/再取得する場合、Nginxがこれをより安定して処理します。
3) アプリサーバコストを抑えたいとき
- Pythonワーカーは高価(メモリ/CPU)で、ファイル転送に絡むと「お金が漏れる」構造になります。
- ファイル転送をプロキシ層に任せることで、アプリサーバはロジック処理に集中できます。
逆に、使わなくてもよいケース
- 内部サーバ間の通信でトラフィックが低い
- ファイルリクエストが少なく、ほとんどがAPI/DBロジックがボトルネック
- ファイルがローカルディスクではなくS3など外部オブジェクトストレージで、既にCDN/プリサインURLで解決できる構造
このような場合は FileResponse でも運用上十分です。必要になったときに導入しても遅くはありません。
実装例:Django + Nginx

Nginx設定例
ポイントは internal です。
internal に設定された location は クライアントが直接アクセスできず、X-Accel-Redirect などの内部リダイレクトからのみ アクセス可能です。
# 実際に保護ファイルを配信する内部エンドポイント
location /_protected/ {
internal;
# 実際のファイルがあるディレクトリ
alias /var/app/protected_media/;
# 性能オプション(環境に合わせて)
sendfile on;
tcp_nopush on;
# 必要に応じてキャッシュ/ヘッダー制御可能
# add_header Cache-Control "private, max-age=0";
}
/var/app/protected_media/以下に実際のファイルがあると仮定- 外部に公開されるURLは
/download/...のような Django ルート - 内部転送パスは
/_protected/...に統一
Djangoビュー例
Djangoは権限だけ確認し、ファイル内容を読み取らずヘッダーだけを返します。
from django.http import HttpResponse, Http404
from django.contrib.auth.decorators import login_required
from django.utils.encoding import iri_to_uri
@login_required
def download(request, file_id):
# 1) DB照会 + 権限チェック
obj = get_file_object_or_404(file_id) # 例
if not obj.can_download(request.user):
raise Http404
# 2) 内部パス構築(Nginx の /_protected/ 以下にマッピング)
internal_path = f"/_protected/{obj.storage_relpath}"
# 3) X-Accel-Redirect ヘッダーのみ設定し、ボディは空
response = HttpResponse()
response["X-Accel-Redirect"] = iri_to_uri(internal_path)
# (任意)ダウンロードファイル名/コンテンツタイプ指定
response["Content-Type"] = obj.content_type or "application/octet-stream"
response["Content-Disposition"] = f'attachment; filename="{obj.download_name}"'
return response
ポイント:
- FileResponse(open(...)) のようなファイルI/Oはありません。
- Djangoはリクエストごとの処理時間が非常に短くなり、ワーカーがファイル転送でロックされません。
セキュリティチェックリスト
1) 内部パスは必ず「サーバが決定」
- クライアント入力で
/_protected/../../etc/passwdのようなパスが生成されないように - DBに保存された「安全な相対パス」だけを使用するか、ホワイトリストベースでマッピングしてください。
2) Nginx location は必ず internal
internalが無ければ、ユーザーが/_protected/...を直接叩いて回避ダウンロードできてしまいます。
3) 権限チェックロジックは Django のみで信頼
- Nginx は「転送エンジン」役で、アクセス制御は Django が担う構造が安全です。
第三者サービスを利用する代替案
コストに余裕があれば、最初からファイルを第三者ストレージで提供する設計も可能です。コストはかかりますが、安定性が高く、サーバリソースを節約できます。プロジェクトチームの状況に合わせて選択してください。
- CDNキャッシュ:公開ファイルなら Nginx 前に CDN キャッシュがより効果的
- プリサインURL(S3 等):オブジェクトストレージベースなら X-Accel-Redirect の代わりにプリサインURLがよりシンプルな場合もあります
まとめ
一般に、ファイル配信はウェブアプリケーション側で行うより、Nginx にオフロードしたほうが性能面で有利です。静的配信に最適化された Nginx がカーネル最適化まで活用して処理するためです。 その結果、アプリサーバは「権限チェック + ビジネスロジック」に集中でき、ダウンロードトラフィックが増えても全体システムがはるかに安定します。
トラフィックが大きくない場合は FileResponse でも十分です。ただし「ファイルリクエストが急増したときにアプリサーバが崩壊するパターン」は非常に一般的で、その際に最も速く効果を得られるカードが X-Accel-Redirect です。
キーワードを一つ覚えておくと良いでしょう: 「権限は Django、転送は Nginx」。
連関ポスト