I believe everyone is familiar with the traditional PHP way of sending files (outside of document root) using functions such as readfile(), fpassthr() or combination of fopen(), fseek(), fread(), etc, if you want to add support for HTTP Range partial and resumable file download.

Although this approach is fine, there is a less known but effective way of sending files using the X-Sendfile HTTP header. X-Sendfile enables your Apache web server to serve files directly from disk, instead of going through your PHP process. This is faster and consumes less memory than using PHP.

What is mod_xsendfile?

mod_xsendfile is a small Apache 2 module that processes X-Sendfile headers registered by the original output handler. If it encounters the presence of such header it will discard all output and send the file specified by that header instead using Apache internal including all optimizations like caching-headers and sendfile or mmap if configured.

It is useful for processing script output, e.g. PHP, Perl or any CGI.

Benefits of X-Sendfile

  • Uses Apache internals
  • Optimal delivery through sendfile and mmap (if available)
  • Sets correct cache headers such as Etag and If-Modified-Since as if the file was statically served.
  • Processes cache headers such as If-None-Match or If-Modified-Since.
  • Support for ranges (partial download)

Setup mod_xsendfile module

Because this is not a standard Apache module, you will need to download, compile and install the mod_xsendfile module source.

  • Download source – http://tn123.org/mod_xsendfile/
  • Compile and install
    path/to/apxs -cia mod_xsendfile.c
  • You may have to load the module manually in your Apache configuration file; httpd.conf
    LoadModule xsendfile_module /path/to/modules/mod_xsendfile.so

Next, you have to enable mod_xsendfile by modifying your Apache configuration file; httpd.conf

# Enable mod_xsendfile
XSendFile on

# XSendFilePath allow you to add additional paths to some kind of white list. All files within these paths are allowed to get served through mod_xsendfile
XSendFilePath /home/userxyz

When you are done, restart Apache web server.

Usage

The traditional PHP way of sending a file:

header ('Content-Type: ' . $documentMIME);
header ('Content-Disposition: attachment; filename="' . $actualFilename . '"');
@ob_end_clean();
@ob_end_flush();
readfile($pathToFile);
exit;

With X-Sendfile:


// Get a list of loaded Apache modules
$modules = apache_get_modules();

if (in_array('mod_xsendfile', $modules)) {
// If mod_xsendfile is loaded, use X-Sendfile to deliver.. (optional: I have this as failover to use PHP readfile() if mod_xsendfile is unavailable)
header ('X-Sendfile: ' . $pathToFile);
header ('Content-Type: ' . $documentMIME);
header ('Content-Disposition: attachment; filename="' . $actualFilename . '"');
exit;
} else {
// Otherwise, use the traditional PHP way..
header ('Content-Type: ' . $documentMIME);
header ('Content-Disposition: attachment; filename="' . $actualFilename . '"');
@ob_end_clean();
@ob_end_flush();
readfile($pathToFile);
exit;
}

That’s It.