I have previously written a quick post on making files downloadable through PHP scripts. The example in the post reads the file itself into a variable, and as pointed out in the comments, it’s not necessarily a very good idea especially if you deal with large files.
Recently at work, we needed a reliable way to send files to users’ browser, and I decided to take a look at mod_xsendfile, as suggested by Tom Graham.
It’s also supported by other web servers such as Lighttpd and nginx.
Benefits of xsendfile over other methods
The advantages of xsendfile come from dealing with bigger files. If you try reading a big file into memory for sending through PHP, or otherwise send one through PHP, you may hit various roadblocks:
- You may hit the PHP memory limit
- You may hit PHP’s max execution time limit
If you don’t encounter these two, you may find that sending the file through the PHP process is slower, or it may eat more memory from your server than if it was sent by Apache.
mod_xsendfile solves all these problems.
Setting up mod_xsendfile
Since mod_xsendfile is not a standard Apache module, you will need to compile and install it yourself. This may be a problem on some hosting providers who won’t let you do this / refuse to install it for you!
Installing the module is quite simple:
- Download the module
- Make sure you have apxs or apxs2 installed. On Debian style systems this is usually in apache2-prefork-dev or apache2-threaded-dev packages
- apxs2 -cia mod_xsendfile.c
- You may need to manually load the module by using the following in your Apache configuration:
LoadModule xsendfile_module /path/to/modules/mod_xsendfile.so
You now have the module installed. To use it, you will need to add the following settings to your Apache configuration, or to a .htaccess file:
# enable xsendfile XSendFile On # enable sending files from parent dirs XSendFileAllowAbove On
The second one is optional, but it allows a useful behavior. You may want to store your protected files under the web root, so to allow xsendfile to access the files, you will need to enable XSendFileAllowAbove – otherwise you will only be able to access files that are in the same directory or in child directories of the directory, where the parsing script is.
Sending a file with xsendfile is very straightforward:
<?php //We want to force a download box with the filename hello.txt header('Content-Disposition: attachment;filename=hello.txt'); //File is located at /home/username/hello.txt header('X-Sendfile: /home/username/hello.txt');
You could omit the first header, in which case the browser would not necessarily show a download file dialog. Also, the X-Sendfile header will not show up in the user’s browser, and they will never see the real location of the file they received.
You will not need to send a Content-Length header, as Apache will take care of that for you.
There are multiple ways to send and store files. I think using mod_xsendfile is a quite good approach, as it avoids the problems associated with sending the files in a PHP script. It also allows Apache to do it’s job – sending files to users – while still giving you the ability to, for example, perform authentication using a PHP script.
Support in other web servers
For information on how to use x-sendfile with Lighttpd, see this comment by Kawsar Saiyeed.
For information on using x-sendfile with nginx, see this comment by Alexey Shockov