Rails x_sendfileで実現する高速・安全なファイル配信【Rails 7.x/8.x対応・Nginx/Apache設定例付き】
RailsアプリケーションでPDFや画像などのファイルをダウンロード提供する場合、Railsプロセスが直接ファイルを読み込んで返す素朴な実装では本番環境でボトルネックになる。大容量ファイルを配信するとRailsのワーカープロセスがそのファイル転送の間ずっと占有されてしまうからだ。
x_sendfile は、この問題をWebサーバー側にファイル転送を委譲することで解決するしくみだ。Railsがファイルのパスだけを応答ヘッダーに乗せて返し、NginxやApacheが実際のファイル転送を担当する。Railsプロセスは次のリクエストをすぐに処理できる。
本記事では、Rails 7.x/8.x環境でx_sendfileを正しく設定し、本番環境で安全かつ高速なファイル配信を実現する方法を解説する。Nginx向けの X-Accel-Redirect とApache向けの X-Sendfile の両方をカバーする。

x_sendfileとは何か
通常のファイル配信の問題点
Railsで send_file を使った素朴な実装は以下のようになる:
# コントローラー
def download
file_path = Rails.root.join('private', 'files', params[:filename])
send_file file_path, type: 'application/pdf', disposition: 'attachment'
end
この実装では、Railsプロセスがファイルを読み込んでクライアントに転送する間、そのプロセスは他のリクエストを処理できない。100MBのPDFを配信するなら、その転送が完了するまで(回線速度によっては数十秒)そのプロセスがブロックされる。
x_sendfileによる解決
x_sendfile を使うと、処理の流れが変わる:
- クライアントがダウンロードリクエストを送信
- Railsが認証・認可チェックを実施(ここが重要)
- Railsは実際のファイルを返さず、ファイルパスを含む特殊なヘッダーだけを返す
- NginxまたはApacheがそのヘッダーを読んで直接ファイルを配信
- Railsプロセスはすぐに解放され、次のリクエストへ
この方式の利点:
– Railsプロセスの占有時間を最小化できる
– Webサーバーのゼロコピー最適化(sendfileシステムコール)を活用できる
– 認証・認可ロジックはRails側で管理できる(セキュリティ確保)

前提条件
このガイドで扱う環境:
- Rails: 7.x または 8.x(Rails 5.x以降であれば同様に動作)
- Webサーバー: Nginx(推奨)または Apache
- Ruby: 3.1以上
- 前提知識: Rails基本操作、Nginxまたはhttpdの設定ファイル編集
バージョン確認:
rails --version
# Rails 7.2.1 など
ruby --version
# ruby 3.3.0 など
nginx -v
# nginx version: nginx/1.24.0 など
全体の流れ
このガイドは以下の5ステップで構成される。
- Rails側でx_sendfileを有効化する — 5分
- Nginxの設定(X-Accel-Redirect) — 10分
- Apacheの設定(X-Sendfile) — 10分
- Railsコントローラーの実装 — 15分
- セキュリティ考慮事項 — 10分
ステップ1: Rails側でx_sendfileを有効化する
production.rb の設定
config/environments/production.rb に以下を追加する:
# config/environments/production.rb
Rails.application.configure do
# Nginx を使う場合
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"
# Apache を使う場合(どちらか一方のみ有効にする)
# config.action_dispatch.x_sendfile_header = "X-Sendfile"
end
注意:
x_sendfile_headerを設定するだけで、send_file呼び出し時に自動的にそのヘッダーが使われるようになる。既存のコントローラーコードを変更する必要はない。
開発環境では設定しない
開発環境(config/environments/development.rb)にはこの設定を入れないこと。ローカルのWEBrickやPumaには X-Sendfile を処理する機能がないため、ファイルが配信されない。
# development.rb には設定不要
# config.action_dispatch.x_sendfile_header = ... # 入れない
✅ ステップ1完了の確認: config/environments/production.rb に x_sendfile_header の設定が追加されていること。
ステップ2: Nginx の設定(X-Accel-Redirect)
NginxでX-Sendfileに相当する機能は X-Accel-Redirect ヘッダーで実現する。Nginxはこのヘッダーを受け取ると、Railsの応答を破棄し、指定されたURIのファイルを直接配信する。
Nginx設定の基本構造
# /etc/nginx/sites-available/myapp(または nginx.conf の server ブロック)
server {
listen 80;
server_name example.com;
root /var/www/myapp/current/public;
# Rails アプリへのプロキシ設定
location / {
try_files $uri @rails;
}
location @rails {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# x_sendfile 用: 非公開ファイルの配信
# この location は直接アクセス不可(internal ディレクティブ)
location /private/ {
internal;
alias /var/www/myapp/private/;
}
}
internal ディレクティブが重要
internal; ディレクティブにより、この location は外部からの直接アクセスを拒否し、内部リダイレクト(X-Accel-Redirect ヘッダー経由)のみを受け付ける。これにより、認証を通過していないユーザーがURLを直打ちしてファイルを取得しようとしても拒否される。
# 直接アクセスしようとすると 404 が返る
curl https://example.com/private/secret.pdf
# HTTP 404 Not Found
# Rails 経由(認証後)は正常に配信
curl -H "Authorization: Bearer TOKEN" https://example.com/downloads/secret.pdf
# ファイルが正常にダウンロードされる
Unicorn/Pumaをsocketで使う場合
本番環境でUnicornやPumaをUnixソケット経由で使う場合の設定:
upstream rails_app {
server unix:/var/www/myapp/shared/tmp/sockets/puma.sock fail_timeout=0;
}
server {
listen 80;
server_name example.com;
root /var/www/myapp/current/public;
location / {
try_files $uri @rails;
}
location @rails {
proxy_pass http://rails_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# プライベートファイル配信用(internal アクセスのみ)
location /private-files/ {
internal;
alias /var/www/myapp/private_uploads/;
}
}
✅ ステップ2完了の確認: nginx -t でシンタックスエラーがないことを確認し、sudo nginx -s reload で設定を反映する。
ステップ3: Apache の設定(X-Sendfile)
Apache を使う場合は mod_xsendfile モジュールが必要だ。
mod_xsendfile のインストール
# Ubuntu/Debian
sudo apt-get install libapache2-mod-xsendfile
sudo a2enmod xsendfile
sudo systemctl reload apache2
# CentOS/RHEL
sudo yum install mod_xsendfile
sudo systemctl reload httpd
Apache VirtualHost の設定
# /etc/apache2/sites-available/myapp.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/myapp/current/public
# mod_xsendfile の有効化
XSendFile On
# X-Sendfile ヘッダーで使用可能なパス(ホワイトリスト)
XSendFilePath /var/www/myapp/private/
# Rails への ProxyPass
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
<Directory /var/www/myapp/current/public>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
</VirtualHost>
XSendFilePath の重要性
XSendFilePath には、X-Sendfile ヘッダーで指定できるパスをホワイトリスト形式で設定する。これを設定しないと任意のパスのファイルが配信できてしまうため、必ず適切なパスに制限すること。
# 複数のパスを許可する場合
XSendFilePath /var/www/myapp/private/
XSendFilePath /var/www/myapp/uploads/secure/
✅ ステップ3完了の確認: apache2ctl configtest でシンタックスエラーがないことを確認する。
ステップ4: Railsコントローラーの実装
基本的な実装
# app/controllers/downloads_controller.rb
class DownloadsController < ApplicationController
before_action :authenticate_user!
def show
@document = Document.find(params[:id])
# 認可チェック: 現在のユーザーがこのファイルにアクセス可能か確認
unless current_user.can_access?(@document)
return head :forbidden
end
# ファイルの存在確認
file_path = @document.file_path
unless File.exist?(file_path)
return head :not_found
end
# x_sendfile が有効な場合、このヘッダーが自動的に設定される
send_file file_path,
filename: @document.original_filename,
type: @document.content_type,
disposition: 'attachment'
end
end
Nginx 用のパス変換
Nginx の X-Accel-Redirect はファイルシステムのパスではなく、Nginx の location に対応する URI を指定する必要がある。
send_file はデフォルトでファイルの絶対パスをそのまま X-Accel-Redirect ヘッダーに設定するが、Nginx の alias 設定と組み合わせる場合はパスの変換が必要になることがある。
# Nginx 設定
location /private-files/ {
internal;
alias /var/www/myapp/private/;
# /private-files/foo.pdf → /var/www/myapp/private/foo.pdf に変換
}
# Rails コントローラー
def show
file_path = Rails.root.join('..', 'private', params[:filename]).to_s
# send_file を使う(x_sendfile が有効なら自動的にヘッダーを設定)
send_file file_path,
type: 'application/pdf',
disposition: 'attachment'
end
ポイント: Rails の
send_fileはconfig.action_dispatch.x_sendfile_headerが設定されていれば、ファイルを実際に読まずにヘッダーだけをセットして返す。Nginx がX-Accel-Redirect: /private-files/foo.pdfヘッダーを受け取り、aliasに従って/var/www/myapp/private/foo.pdfを配信する。
ダウンロードログを記録する例
# app/controllers/downloads_controller.rb
class DownloadsController < ApplicationController
before_action :authenticate_user!
def show
@document = Document.find(params[:id])
unless current_user.can_access?(@document)
Rails.logger.warn "Unauthorized download attempt: user=#{current_user.id} document=#{@document.id}"
return head :forbidden
end
# ダウンロードログの記録(非同期推奨)
DownloadLog.create!(
user: current_user,
document: @document,
ip_address: request.remote_ip,
user_agent: request.user_agent
)
send_file @document.file_path,
filename: @document.original_filename,
type: @document.content_type,
disposition: 'attachment'
end
end

✅ ステップ4完了の確認: 開発環境で send_file が正常に動作すること(x_sendfileなし)を確認し、Staging/本番環境でX-Accel-RedirectまたはX-Sendfileヘッダーが返ることをcurlで確認する。
# レスポンスヘッダーの確認(本番環境)
curl -I -H "Cookie: _session=..." https://example.com/downloads/1
# X-Accel-Redirect: /private-files/document.pdf が含まれていれば OK
# (実際には Nginx がこのヘッダーを処理してクライアントには見えない)
セキュリティ考慮事項
x_sendfile を使う際にとくに注意すべきセキュリティリスクと対策をまとめる。
1. ディレクトリトラバーサル攻撃の防止
ファイルパスにユーザー入力を使う場合、必ずサニタイズすること:
# 危険: ユーザー入力を直接パスに使う
file_path = Rails.root.join('private', params[:filename])
send_file file_path # /private/../../../etc/passwd のような攻撃が可能
# 安全: basename で実際のファイル名のみを使う
safe_filename = File.basename(params[:filename])
file_path = Rails.root.join('private', 'files', safe_filename)
# さらに安全: DBからファイルパスを取得し、ユーザー入力を直接使わない
document = Document.find(params[:id])
send_file document.file_path # DB に保存された絶対パスを使う
2. 認可チェックの徹底
x_sendfile は「ファイル転送の高速化」の仕組みであり、「アクセス制御」はRails側で必ず実装すること:
def show
document = Document.find(params[:id])
# 認可チェックは必須
authorize! :download, document # CanCanCan の例
# または明示的なチェック
unless document.accessible_by?(current_user)
return head :forbidden
end
send_file document.absolute_path
end
3. private ディレクトリを public の外に置く
配信するファイルは public/ ディレクトリの外に置くこと。public/ 内のファイルはNginxが直接配信してしまい、Railsの認可チェックを通らない:
# 推奨ディレクトリ構造
/var/www/myapp/
├── current/
│ ├── public/ ← Nginx が直接配信(認証不要のファイル)
│ │ └── assets/
│ └── ...
└── private/ ← Rails(x_sendfile)経由でのみ配信
└── documents/
└── secret.pdf
4. Content-Disposition の適切な設定
# ブラウザ内表示(PDF等)
send_file file_path, disposition: 'inline', type: 'application/pdf'
# 強制ダウンロード
send_file file_path, disposition: 'attachment', filename: 'document.pdf'
filename には信頼できる値(DB から取得したものなど)を使い、ユーザー入力をそのまま使わないこと。
5. 有効期限付きトークンによるURLの保護
定期的にアクセスが必要なユーザーへのリンクには、有効期限付きトークンを発行することを検討する:
# app/models/download_token.rb
class DownloadToken < ApplicationRecord
belongs_to :document
belongs_to :user
before_create :generate_token
def self.valid_for(document, user, expires_in: 10.minutes)
create!(
document: document,
user: user,
expires_at: expires_in.from_now
)
end
def expired?
expires_at < Time.current
end
private
def generate_token
self.token = SecureRandom.urlsafe_base64(32)
end
end
# ダウンロードURLの生成
token = DownloadToken.valid_for(document, current_user)
download_url = download_url(token: token.token)
Rails 7.x / 8.x での変更点
Rails 7.x
Rails 7.0 以降では config.action_dispatch.x_sendfile_header の動作は変わっていないが、Zeitwerkによる自動読み込みの変更や、デフォルトのミドルウェアスタックが変わっているため、設定が正しく反映されているか確認する。
# config/environments/production.rb
Rails.application.configure do
config.action_dispatch.x_sendfile_header = "X-Accel-Redirect"
# ... その他の設定
end
Rails 8.x
Rails 8.0 では Rack::Sendfile ミドルウェアが引き続き使われており、x_sendfile の仕組みは変わっていない。Solid Cache/Solid Queue などの新機能との組み合わせも問題なく動作する。
現在のミドルウェアスタックで Rack::Sendfile が含まれていることを確認:
rails middleware
# ...
# use Rack::Sendfile
# ...
Rack::Sendfile ミドルウェアが X-Sendfile-Type や X-Accel-Mapping などの環境変数も参照する点を覚えておくと、デバッグ時に役立つ。
パフォーマンス比較
実際の違い
| 方式 | Railsプロセスの占有時間 | ファイル転送の担当 |
|---|---|---|
通常の send_file(x_sendfileなし) |
ファイル転送完了まで(数秒〜数十秒) | Railsプロセス |
| x_sendfile(Nginx/Apache) | ヘッダーを返すだけ(数ms) | Nginx/Apache |
Railsのワーカープロセス数が限られている場合(Pumaのスレッド数など)、x_sendfileを使わないと大きなファイルの配信リクエストが増えた際にスループットが大幅に低下する。
トラブルシュート
| 症状 | 原因 | 解決策 |
|---|---|---|
| ファイルが配信されず空のレスポンスが返る | Nginx の internal ロケーションのパス設定ミス |
X-Accel-Redirect ヘッダーのURIと Nginx の location が一致しているか確認 |
500 Internal Server Error |
ファイルパスが存在しない | File.exist? で事前チェックする |
| 開発環境でファイルが見つからない | 開発環境に x_sendfile_header を設定してしまっている | development.rb には設定しない |
| 本番環境でヘッダーが設定されない | Rack::Sendfile ミドルウェアが無効化されている |
rails middleware でスタックを確認 |
Apache で X-Sendfile が無視される |
mod_xsendfile が有効化されていない |
a2enmod xsendfile で有効化後、再起動 |
| ファイル名が文字化けする | マルチバイト文字を含む filename の扱い | filename に ASCII 文字を使うか、filename* ヘッダーを使う |
まとめ
Railsの x_sendfile は、本番環境でのファイル配信パフォーマンスを大きく改善する設定だ。
- Rails側:
config.action_dispatch.x_sendfile_headerを設定するだけで既存のsend_fileコードがそのまま動く - Nginx:
X-Accel-Redirect+internalディレクティブで安全に実装できる - Apache:
mod_xsendfile+XSendFilePathでホワイトリスト制限付きで実装できる - セキュリティ: 認可チェックはRails側で必ず実施し、ディレクトリトラバーサル対策を徹底する
Rails 7.x/8.x でも設定方法は変わらないため、既存のRailsアプリに追加コストなく導入できる。ファイルダウンロード機能を持つRailsアプリには必須の最適化と言える。


