Fixing Moodle Performance by Offloading File Serving with X-Sendfile
Why serving large Moodle files through PHP exhausts your FPM worker pool — and how to fix it by enabling X-Sendfile on Apache or X-Accel-Redirect on Nginx so the web server handles the transfer directly.
Every file Moodle serves goes through PHP by default. On sites with large video files or a high number of concurrent downloads, this ties up PHP-FPM workers for the duration of each transfer. Enabling X-Sendfile transfers that work to the web server, freeing PHP instantly.
Why PHP file serving is a bottleneck
When a user accesses a file in Moodle — a video, a PDF, a SCORM
package — the request goes to pluginfile.php. Moodle checks
permissions, resolves the file from moodledata, and streams
it back to the browser. While the download is in progress the PHP-FPM
worker is occupied: it cannot serve other requests.
A single 500 MB video streamed to a user on a slow connection can hold a PHP worker for 10–20 minutes. On a site with a limited FPM pool, a handful of concurrent video viewers can exhaust all available workers, making the entire site unresponsive.
How X-Sendfile works
X-Sendfile is a mechanism where PHP sets a response header instead of sending the file body. The web server intercepts that header, locates the file on disk, and streams it directly to the client. PHP exits immediately — the FPM worker is released.
The header name differs by web server: – Apache:
X-Sendfile (requires mod_xsendfile) –
Nginx: X-Accel-Redirect
Moodle has built-in support for both.
Apache configuration
Install mod_xsendfile if it is not already present:
apt install libapache2-mod-xsendfile
a2enmod xsendfile
In your virtual host configuration, declare the paths that Apache is allowed to serve via X-Sendfile:
XSendFile On
XSendFilePath /var/moodledata
XSendFilePath /var/www/moodle
The path must match the real filesystem path of
moodledata and, if your theme or plugin serves files from
the Moodle root, the Moodle directory as well.
Nginx configuration
Add an internal location block that maps to your moodledata directory:
location /moodledata-internal/ {
internal;
alias /var/moodledata/;
}
The location name is arbitrary — it just needs to match what Moodle will put in the header.
Moodle configuration
In config.php, add:
// Apache:
$CFG->xsendfile = 'X-Sendfile';
$CFG->xsendfilepath = '/var/moodledata';
// Nginx:
$CFG->xsendfile = 'X-Accel-Redirect';
$CFG->xsendfilepath = '/moodledata-internal';
For Nginx the xsendfilepath value is the location block
name (the internal URI), not the real filesystem path.
Verifying it works
After enabling, inspect the response headers on a file download:
curl -I "https://yourmoodle.com/pluginfile.php/1/course/section/0/file.pdf" \
-b "MoodleSession=yoursessioncookie"
Without X-Sendfile the response body comes directly from PHP. With
X-Sendfile the PHP response will have Content-Length: 0 and
the X-Sendfile header will be present (stripped by the web
server before sending to the client, but visible in debug mode).
A simpler check: watch FPM worker utilization during a large file
download with
watch -n1 'php-fpm8.2 -t 2>&1; ps aux | grep php-fpm | grep -v grep | wc -l'.
Without X-Sendfile the count stays elevated for the duration of the
download. With X-Sendfile the worker count drops back to baseline within
seconds.
Caveats
moodledata must not be web-accessible directly.
X-Sendfile only works because Moodle’s PHP code runs first to enforce
access control. The web server then serves the file after PHP has
authorised it. Never point a public Alias or
root directly at moodledata.
Symlinks: if moodledata contains
symlinked directories (some backup or filedir configurations do this),
ensure mod_xsendfile or Nginx is configured to follow them,
or the file lookup will fail.
PHP memory: X-Sendfile also eliminates the memory overhead of reading the file into PHP before sending. This is particularly relevant for ZIP files or large SCORM packages where PHP would otherwise buffer the entire file.
Solin provides Moodle performance tuning, hosting, and infrastructure consulting.
Contact us