readfile
Reads a file and writes it straight to the output buffer, returning the number of bytes sent.
readfile(string $filename, bool $use_include_path = false, ?resource $context = null): int|false- Added in PHP 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$filename | string | Path or stream URL of the file to output. |
$use_include_pathoptional | bool= false | When true, the file is also looked for along the include_path. |
$contextoptional | ?resource= null | A stream context resource created with stream_context_create(). |
Return value
int|falseThe number of bytes read and written on success, or false on failure.
Errors
Emits an E_WARNING and returns false when the file cannot be opened.
readfile() opens a file, copies it to the output stream, and closes it — in one call, without building a PHP string of the contents. That last point is the whole reason it exists: a 2 GB video goes out through a small internal buffer instead of blowing past memory_limit.
When to reach for it
Use it in a download endpoint that serves files from outside the document root after an authorisation check. Send the headers first — Content-Type, Content-Length: filesize($path), and Content-Disposition: attachment; filename="..." — then call readfile() and nothing else. It is also handy for inlining a static HTML fragment that must not be parsed as PHP.
What trips people up
Output buffering silently defeats the memory advantage. If output_buffering is on, or a framework has called ob_start(), the entire file accumulates in the buffer anyway. Call ob_end_flush() (or while (ob_get_level()) ob_end_clean();) before readfile() in a download handler.
Also: any byte your script emits before the call — a stray newline after ?>, a BOM, a warning — is prepended to the file and corrupts the download. And never pass a user-supplied path straight in; resolve it with realpath() and confirm it sits inside your intended directory, or you have handed out arbitrary file read.
Example
When to use it
- Serve a private PDF or invoice from outside the web root only after checking the session.
- Stream a large video or backup archive without exhausting memory_limit.
- Emit a cached HTML fragment verbatim instead of including it as PHP.
Frequently asked questions
What is the difference between readfile and file_get_contents?
readfile() writes the file directly to output and returns a byte count; file_get_contents() returns the contents as a string you can manipulate. Use readfile() for downloads because it never holds the whole file in memory.
Why is my file download corrupted when using readfile?
Almost always because something was output before the call — whitespace after a closing ?> tag, a UTF-8 BOM, or a PHP warning — and those bytes get prepended to the file. Remove trailing ?> tags and clear the output buffers before sending.
Does readfile use a lot of memory for large files?
Not on its own; it streams through a small internal buffer. It does if output buffering is enabled, because the buffer then accumulates the entire file, so close the buffers first.
More examples
A guarded download endpoint
realpath() collapses any ../ in the request, and the str_starts_with() check confirms the result is still inside storage/ — together they stop path traversal. Clearing the buffers first is what keeps memory flat for a large file.
<?php
$base = realpath('storage');
$path = realpath('storage/' . ($_GET['f'] ?? ''));
if ($path === false || !str_starts_with($path, $base . DIRECTORY_SEPARATOR)) {
http_response_code(404); exit;
}
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($path));
header('Content-Disposition: attachment; filename="' . basename($path) . '"');
while (ob_get_level()) { ob_end_clean(); }
readfile($path);Capturing readfile output
The buffer captures what readfile() printed, showing that readfile() writes to output rather than returning content. If you actually want the string, file_get_contents() does it in one step and without the buffer dance.
<?php
file_put_contents('data.csv', "a,b\n1,2\n");
ob_start();
readfile('data.csv');
$contents = ob_get_clean();
echo strtoupper($contents);
echo "Use file_get_contents() when you need the string, not the output.\n";
unlink('data.csv');Serving a byte range
readfile() always sends the whole file from byte zero, so HTTP Range requests need fopen(), fseek() and fpassthru() instead. The printed count is the remaining byte total after the seek.
<?php
file_put_contents('big.bin', str_repeat('ABCDEFGHIJ', 10));
// readfile() cannot skip - use fopen/fseek/fpassthru for ranges
$fh = fopen('big.bin', 'rb');
fseek($fh, 20);
echo fpassthru($fh), " bytes from offset 20\n";
fclose($fh);
unlink('big.bin');
Discussion