DrupalでPDFを公開時刻まで隠す方法:プライベートファイル領域による安全な時限公開

Drupal
2026.09.14
塩川
エンジニア

IR資料や会員向けの機密PDFを、指定した公開時刻まで確実に隠したい。多くのDrupalサイトでは、Schedulerなどのスケジュール系モジュールでノードの公開状態を制御していますが、PDFファイルそのものがパブリックファイル領域に置かれている限り、URLを知っている第三者からの直接アクセスは防げません。

ノードを非公開にしていても、PDF本体は依然として取得可能な状態にある──これが典型的な落とし穴です。

本記事では、モジュール追加によるアクセス制御拡張で発生しがちな副作用を整理したうえで、Drupal標準のプライベートファイル領域を用いた時限公開の実装方針を解説します。IR開示や会員限定コンテンツの運用で責任範囲を明確にしたい担当者の方に向けた内容です。

目次

    パブリックファイル領域では時限公開が成立しない理由

    Drupalのファイルフィールドは、既定でパブリックファイル領域を利用します。この領域に格納されたPDFは、Webサーバーが直接配信するため、Drupalのアクセス制御を経由しません。つまり、ノード側で「公開日時」を未来に設定していても、PDFのURLさえ推測または漏洩すれば、公開前の資料が取得できてしまいます。

    IR資料のように、開示タイミング自体が価値を持つコンテンツでは、Drupalの画面上で非公開にしていても、その前提はファイル配信レイヤーには適用されないため、この構造は責任分界の観点から見過ごせません。

    アクセス制御モジュール追加で起きがちな副作用

    この課題に対して、まず検討されるのがモジュール追加による解決策です。代表的には以下のような選択肢があります。

    • Scheduler(コントリビュートモジュール):ノードの公開・非公開を日時で切り替える
    • Permissions by Term / Group(いずれもコントリビュートモジュール):分類やグループ単位でアクセスを制御する
    • Private Files Download Permission(drupal.org掲載のコントリビュートモジュール)や、それに類するカスタム実装:プライベートファイルのダウンロード権限を細かく制御する

    これらを組み合わせると要件は満たせるように見えますが、実運用では次のような調整が増えます。

    第一に、権限設計の複雑化です。ロールごとの表示可否、ダウンロード可否、プレビュー可否が交差し、最上位管理者以外は全体の挙動を把握・管理しきれない状態になります。第二に、キャッシュ層との整合性確保です。Drupalの内部キャッシュ、リバースプロキシ、CDNのいずれかがファイルURLをキャッシュしていると、公開前のPDFが配信され続けるリスクが残ります。第三に、モジュールのアップデートへの追従コストです。セキュリティリリースのたびに検証環境での確認工数が発生し、標準化された運用フローに乗せづらくなります。

    要件に対して機能が過剰になり、副作用のほうが運用負荷を押し上げる、というのが典型的な流れです。

    プライベートファイル領域を用いた解決策

    こうした課題に対して有効なのが、Drupal標準のプライベートファイル領域(private file system)を用いる方針です。プライベート領域に配置されたファイルは、Webサーバーが直接配信せず、Drupalのブートストラップを経由してアクセス権が判定されます。つまり、ファイルを参照しているエンティティの公開状態と閲覧権限が、そのままPDFの配信可否に反映されます。

    なお、以下の構成では時限公開の制御にSchedulerモジュール(コントリビュート)を使用することを前提とします。Schedulerを導入しない場合は、カスタムcron処理やContent Moderationを用いた代替手段を別途設計する必要があります。

    実装の順番は次のとおりです。

    1. settings.php にプライベートパスを設定する

    $settings['file_private_path'] を、Webルート外のパスで設定します。この設定はDrupal 8以降 settings.php 専用で、管理画面からは変更できません。環境差異の吸収やバージョン管理への組み込みという観点からも、本番環境ではsettings.phpでの設定を推奨します。

    2. 対象フィールドの保存先をプライベートに変更する

    変更するのは、フィールド自身の設定画面(管理 > 構造 > 対象エンティティ > フィールドの管理 > 該当フィールド)にある「アップロード先」です。

    ここで注意が必要なのは、そのフィールドに既にファイルが1件でも登録されていると、この選択肢はグレーアウトして画面からは変更できないという点です。データが入っている本番相当の環境では、次のいずれかで対応します。

    • 設定のエクスポート/インポート(単一設定インポートで field.storage.* を上書き)
    • 設定ファイルを直接編集してから drush config:import でインポートする、または drush php-eval を用いてプログラム的に設定を更新する

    config:set はスカラー値の単純な上書き用コマンドであり、フィールドストレージ設定のような入れ子構造を持つYAMLキーの書き換えには不向きです。誤って使用するとYAML構造を壊すおそれがあるため、確認用途に留めてください。作業前には必ず drush config:export でバックアップを取得します。

    また、サイト全体の設定である管理画面「ファイルシステム」では、フィールド単位の保存先は変更できません(そこで変更できるのは既定のダウンロード方式のみです)。この2つは混同しやすいので、切り分けて理解してください。

    3. 既存のパブリックPDFをプライベート領域へ移行する

    既存ファイルの移動には、Drupal 9.3以降であれば file.repository サービスの move() を使うのが標準です。実ファイルの移動とFileエンティティのURI更新を1回の呼び出しでまとめて行うため、URI更新漏れによる不整合が起きません。それ以前のバージョンでは、非推奨の file_move() 関数を使用するか、FileSystemサービスの move() とFileエンティティの保存処理を組み合わせて対応してください。

    4. 予約公開を設定する

    Schedulerで公開日時を設定し、時限公開を制御します。

    5. キャッシュ経路からプライベートパスを除外する

    リバースプロキシ・CDNのキャッシュ対象からプライベートファイルのパスを除外します。

    この構成では、追加モジュールはSchedulerなど最小限に留まります。アクセス判定はDrupalコアの責任範囲で完結するため、権限設計もロールとエンティティの公開状態の二軸に整理できます。

    実装時に見落としやすい設計項目

    プライベートファイル領域は強力ですが、設計項目が増える点は事前に押さえておく必要があります。

    プライベートファイルはリクエストごとにPHPを経由する仕様のため、大容量PDFや同時アクセスが集中する局面では、応答遅延やサーバー負荷の増大が生じる点に注意が必要です。IR開示直後の急激なアクセス増加が想定される場合は、認証付きCDNの併用を優先的に検討します。

    公開時刻を境にパブリック領域へ差し替える運用も選択肢として存在しますが、避けるべき点もあります。この方法はファイルURLの変更を伴うため、外部サイトからのリンクや既存の共有URLに影響が生じるリスクがあります。また、差し替えタイミングの制御にもカスタム実装が必要となるため、適用は限定的なユースケースに留めるのが実務的です。

    性能面の設計に加えて、バックアップ経路の再設計も必要です。プライベート領域はWebルート外に配置されるため、既存のバックアップスクリプトがパブリック領域のみを対象にしている場合、対象パスの追加が必要になります。

    運用フローとしての標準化

    時限公開の要件が繰り返し発生するサイトでは、実装だけでなく運用フローの標準化までを含めて設計することを推奨します。具体的には、公開予定PDFの登録手順、確認者の責任範囲、公開直前のプレビュー方法、公開後のキャッシュ確認までを手順書に落とし込みます。これにより、担当者が交代しても同じ品質で運用を継続できます。

    よくある質問

    Q. 既にパブリック領域に大量のPDFを配置しています。すべてプライベートに移行すべきでしょうか。

    時限公開や会員限定といった要件があるPDFに限定して移行するのが現実的です。恒常的に公開している資料までプライベート化すると、リクエストごとにPHPを経由するため性能面での影響が出ます。要件ベースで対象を切り分けることを推奨します。

    Q. Schedulerでノードを公開設定にすれば、プライベートPDFも自動的に配信されますか。

    プライベートファイルへのアクセス可否は、コアの file モジュールが実装する hook_file_download()file_file_download())が判定します。この処理はエンティティタイプを限定せず、「そのファイルを参照しているエンティティ」を横断的に探し、閲覧権限があるかどうかでダウンロードを許可します。

    ここで重要なのは、判定対象になるのは「ファイルを直接参照しているエンティティ」であるという点です。メディアライブラリ経由でPDFを管理している構成では、ファイルを直接参照しているのはMediaエンティティであり、そのMediaを貼り付けている記事ノードではありません。したがって次の関係になります。

    • 記事ノードを非公開にしても、Mediaが公開状態のままならPDFは取得できる
    • PDFを隠したいなら、ノードではなくMediaエンティティ側の公開状態を制御する必要がある

    Mediaにも予約公開を設定する(あるいはノードとMediaの公開日時を連動させる)運用設計が必要です。

    加えて、CDNやリバースプロキシで応答がキャッシュされていると、Drupal側の判定結果が反映されない場合があります。実装後は必ず、公開前のURLを未ログイン状態で叩いて403が返ることと、CDNのキャッシュヒット状況(x-cache / age レスポンスヘッダなど)の両方を確認してください。

    Q. プライベートファイル領域を使う場合、Drupalのバージョンによる制約はありますか。

    Drupal 9・10・11で標準機能として利用できます。かつてはDrupal 7でも同様の機能が提供されていましたが、Drupal 7は2025年1月5日に公式サポートを終了しています(参照:drupal.org/drupal-7-eol)。ただし、Acquia・Tag1 Consulting等が提供するVendor Extended Support(VES)を契約している場合は、一定期間セキュリティパッチの提供が継続されます(Pantheonは独自のサポート方針があるため、利用中の場合は公式サイトを参照してください)。いずれにせよ、新規実装の基準バージョンとしては推奨されません。既にDrupal 7を利用している場合は、移行計画とあわせて検討することをおすすめします。バージョンごとにsettings.phpの記述や推奨ディレクトリ構成に差があるため、公式ドキュメントと自社の環境要件を突き合わせて設計してください。

    LYZONが提供するWebサービス

    Webに関するお悩みは、まずLYZONへ

    構築・運用・AIまで、Webに関わる課題をワンストップで対応。まず何ができるかをご覧ください。