Send Files Faster & Better with PHP & X-Sendfile

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.

6 Response Comments

  • Lammo  January 7, 2013 at 3:16 am

    Thanks. It didn’t even occur to me I could check for the availability of the module like that before sending an X-Sendfile header.

    It’s a good day when you learn something new.

    Reply
  • Jaume Sola  July 23, 2013 at 8:17 am

    Great auto-detection snipped. On CentOS you can also install mod_xsendfile with yum (EPEL repository).

    Reply
  • Jim Anders  January 19, 2014 at 10:25 am

    Thank you Tan Hong very much. Very nice explanation and neat PHP coding.

    Reply
  • Anachronist  January 22, 2014 at 7:34 pm

    I’m wondering, does the X-Sendfile header need to occur before all other headers? Or would it make sense to send the other headers first?

    Reply
  • Oliver Leitner  April 7, 2014 at 12:28 pm

    @ Anachronist

    i always declare the other headers first, i find this more logical, you put on your clothes before you leave home too…

    Reply

Leave A Comment

Please enter your name. Please enter an valid email address. Please enter a message.