PHP readfile uses the page buffer to store the file before send it. If it fails due to insufficient memory it does not throw a memory error only fail.
Ensure that the page that performing the download is not using page buffer( http://us1.php.net/manual/es/ref.outcontrol.php)
Also is better for big files to use fopen an fread to read the file and put the content.
Also with fread you can make some code to allow the download to be resumed.
Here there's a good example: Resumable downloads when using PHP to send the file?
To disable ouput buffering you should try to clean and end buffer before process start with ob_end_clean() or disable the output buffering using ini_set('output_buffering', 0);
In the readfile documentation there's an example about how to use fread for long files instead of readfile:
http://ca2.php.net/manual/en/function.readfile.php#48683
function readfile_chunked($filename,$retbytes=true) {
$chunksize = 1*(1024*1024); // how many bytes per chunk
$buffer = '';
$cnt =0;
$handle = fopen($filename, 'rb');
if ($handle === false) {
return false;
}
while (!feof($handle)) {
$buffer = fread($handle, $chunksize);
echo $buffer;
ob_flush();
flush();
if ($retbytes) {
$cnt += strlen($buffer);
}
}
$status = fclose($handle);
if ($retbytes && $status) {
return $cnt; // return num. bytes delivered like readfile() does.
}
return $status;
}
Also there's an example with partial download support in the same page:
function smartReadFile($location, $filename, $mimeType='application/octet-stream') {
if(!file_exists($location)) {
header ("HTTP/1.0 404 Not Found");
return;
}
$size=filesize($location);
$time=date('r',filemtime($location));
$fm=@fopen($location,'rb');
if(!$fm) {
header ("HTTP/1.0 505 Internal server error");
return;
}
$begin=0;
$end=$size;
if(isset($_SERVER['HTTP_RANGE'])) {
if(preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)){
$begin=intval($matches[0]);
if(!empty($matches[1])){ $end=intval($matches[1]); }
}
}
if($begin>0||$end<$size){
header('HTTP/1.0 206 Partial Content');
}else{
header('HTTP/1.0 200 OK');
}
header("Content-Type: $mimeType");
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Accept-Ranges: bytes');
header('Content-Length:'.($end-$begin));
header("Content-Range: bytes $begin-$end/$size");
header("Content-Disposition: inline; filename=$filename");
header("Content-Transfer-Encoding: binary\n");
header("Last-Modified: $time");
header('Connection: close');
$cur=$begin;
fseek($fm,$begin,0);
while(!feof($fm)&&$cur<$end&&(connection_status()==0)) {
print fread($fm,min(1024*16,$end-$cur));
$cur+=1024*16;
}
}