讓 Django 不直接傳送檔案,改由 Nginx 透過 X-Accel-Redirect 提升下載效能
大多數 Django 服務在傳送受保護檔案(僅登入使用者可下載、付款後可閱覽等)時,會使用 FileResponse 等方式,讓 Python 程式讀取檔案並將內容送給客戶端。若流量不大或僅是內部伺服器間通訊,這樣的做法足夠。
然而當檔案請求量激增時,應用程式伺服器(Python)會被檔案傳輸工作佔用,導致原本應執行的權限檢查、商業邏輯、API 處理等工作變得困難。此時「由 Django 處理權限檢查,將檔案傳輸交給 Nginx」的典型技術就是 X-Accel-Redirect。
為什麼直接由 Python 傳送會造成瓶頸?
Django 直接傳送檔案的流程大致如下:
- 接收請求
- 執行權限檢查
- 從磁碟/儲存空間讀取檔案
- 由應用程式進程將資料流式傳送到網路
問題在於 第 3、4 步是「重負」:
- 檔案越大,傳送時間越長
- 同時下載數量增加,工作者/執行緒/進程會被佔用
- 最終導致 API 響應延遲、逾時、甚至需要擴充伺服器
相對地,Nginx 已經為靜態檔案傳輸做了最佳化,包括 kernel 層的 sendfile、高效事件迴圈、緩衝/區間請求處理等,專門針對「檔案交付」而設計。
X-Accel-Redirect 的核心概念
Django 只做「檢查」,Nginx 只做「傳送」。
工作原理
- 客戶端請求
/download/123 - Django 只執行資料庫查詢與權限檢查
- Django 回傳空內容,並在回應標頭寫入:
X-Accel-Redirect: /_protected/real/path/to/file.webp - Nginx 看到此標頭後,內部尋找對應檔案並直接送給客戶端
這樣,Django 完全不需要讀取檔案內容,僅負責判斷「此使用者是否有權下載」。
何時特別適合使用此方式?
以下情境會看到顯著效益:
1) 下載/圖片請求量大且併發高的服務
- 社群/訊息平台的圖片、附件、報告 PDF 下載
- 請求數多、邏輯簡單的模式,X-Accel-Redirect 能發揮最大效能
2) 檔案較大或區間請求(Range)重要的服務
- 視訊/音訊/大容量壓縮檔
- 瀏覽器/播放器使用
Range進行播放/續傳 → Nginx 能更穩定地處理此類傳輸
3) 想降低應用伺服器成本
- Python 工作者成本高(記憶體/CPU),若被檔案傳輸佔用會「流失」效能
- 把傳輸交給代理層,讓應用伺服器專注於商業邏輯
何時可以不使用?
- 內部伺服器間通訊且流量低
- 檔案請求少,且 API/資料庫邏輯是瓶頸
- 檔案存放於 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 只確認權限,並回傳 X-Accel-Redirect 標頭。
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) 取得物件並檢查權限
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 - 每個請求的處理時間極短,工作者不會被檔案傳輸佔用
安全檢查清單
1) 內部路徑必須由伺服器決定
- 防止客戶端輸入
/_protected/../../etc/passwd等路徑穿越 - 只使用資料庫中「安全」的相對路徑,或採用白名單映射
2) Nginx location 必須設為 internal
- 若未設
internal,使用者可直接存取/_protected/...,造成安全漏洞
3) 權限檢查僅由 Django 執行
- Nginx 僅為傳輸引擎,所有存取控制由 Django 負責
其他第三方服務替代方案
若成本不是問題,也可以考慮直接由第三方儲存服務提供檔案:
- CDN 快取:公開檔案可先放在 CDN,效能提升更顯著
- 預簽名 URL(S3 等):若使用物件儲存,X-Accel-Redirect 可能不必要,直接使用預簽名 URL 更簡單
結語
總結來說,將檔案傳輸交給 Nginx 的代理層,能顯著提升效能,因為 Nginx 已經為靜態檔案傳輸做了最佳化。這樣,應用伺服器就能專注於權限檢查與商業邏輯,即使下載流量激增,整體系統仍能保持穩定。
若流量不大,FileResponse 仍是乾淨且足夠的選擇;但當「檔案請求暴增」時,X-Accel-Redirect 是最快、最有效的解決方案。
只要記住一句話:「權限由 Django 處理,傳輸由 Nginx 負責」。
目前沒有評論。