File delivery 

Once a file passed the access check, it still has to be transferred to the client. By default, the extension does that in PHP. This chapter explains why this may become a bottleneck on busy websites and how to hand the transfer over to the web server instead.

Why PHP delivery can become a bottleneck 

With the rewrite rules from the administrator manual, every request to /fileadmin/ (except for the "_processed_" folder) is answered by TYPO3. This includes images embedded in pages and files without any restriction.

When PHP streams a file, the PHP worker (e.g., a PHP-FPM child process) remains busy for the whole transfer, not only for the access check. As soon as the client reads slower than the server writes, PHP waits for the network. A 20 MB PDF downloaded by a mobile client at 1 Mbit/s keeps one worker busy for about 160 seconds, while using almost no CPU.

The number of workers is limited (pm.max_children). A few slow downloads are enough to make regular page requests wait in the PHP-FPM listen queue. This does not show up as CPU or memory load: watch the active processes and the listen queue on the PHP-FPM status page instead.

Long transfers may also be terminated by limits such as PHP-FPM's request_terminate_timeout, regardless of PHP's own time limit.

Delivery modes 

The extension configuration option File delivery (delivery) selects who transfers the file:

php (default)
The file is streamed by PHP. This works everywhere and needs no web server configuration.
x-sendfile
PHP answers with an empty body and an X-Sendfile header holding the absolute path of the file. The web server then transfers the file itself, e.g., Apache with mod_xsendfile. The PHP worker is released as soon as the access check is done.
x-accel-redirect
Same principle for Nginx, using its built-in X-Accel-Redirect header and an internal location. No additional module is needed.

In every mode, the access check stays in TYPO3 and the response carries a Last-Modified header. Conditional requests (If-Modified-Since) for an unchanged file are answered with 304 Not Modified by the extension itself, before any transfer is handed over. With x-sendfile and x-accel-redirect, the web server also takes care of byte ranges (206 Partial Content), so interrupted downloads can be resumed.

Apache with mod_xsendfile 

Install and enable the module, e.g., on Debian or Ubuntu:

apt install libapache2-mod-xsendfile
a2enmod xsendfile
Copied!

Then allow it in your virtual host, for the directories of your storages:

XSendFile On
XSendFilePath /var/www/example.com/public/fileadmin
Copied!

Please note:

  • XSendFilePath is only allowed in the server or virtual host configuration, not in .htaccess.
  • The extension sends the resolved path of the file. mod_xsendfile only serves it if it lies within XSendFilePath after resolving symbolic links as well. If fileadmin/ is a symbolic link, e.g., to the shared/ directory of a deployment tool, XSendFilePath must name the resolved directory, not the path of the current release.
  • Headers set by TYPO3 (Content-Type, Cache-Control, ...) are kept.

Nginx with X-Accel-Redirect 

Add an internal location that maps to the public directory of your TYPO3 installation. Its path must match the extension configuration option Internal location (delivery_internal_location, default: /_fal_protect_internal/):

location ^~ /_fal_protect_internal/ {
  internal;
  alias /var/www/example.com/public/;
}
Copied!

The internal directive ensures that clients cannot request this location directly. Files of storages outside of the public directory are still streamed by PHP.

Verifying the setup 

First, make sure a protected file can be downloaded completely and that the response contains neither X-Sendfile nor X-Accel-Redirect, as the web server consumes these headers:

curl -sI https://example.com/fileadmin/some/document.pdf
Copied!

Then check that the PHP worker is released. Download a large file at a low rate and watch the PHP-FPM status page in the meantime:

curl -o /dev/null --limit-rate 50k https://example.com/fileadmin/some/large.pdf
Copied!

With PHP delivery, one worker stays active until the download is finished. With x-sendfile or x-accel-redirect, it is idle again right away.