When I built the download proxy for ramrom.io, my first version had a bug that only showed up on long videos. Short clips downloaded fine. Then someone tried a ten-minute video and the download died halfway through with a blank page. The server had run out of memory.

The problem was embarrassingly simple. My code fetched the whole video file into a PHP variable, then sent it out with echo. For a 50 MB clip that worked. For a 300 MB video it did not, because the server only gives PHP a fixed amount of memory — on my shared hosting, 128 MB. The file itself was bigger than the memory I was allowed to use. PHP killed the script, the visitor got nothing, and my error log filled up.

The fix is to stop holding the whole file at once and stream it instead. Think of it like pouring water through a hose rather than filling a bucket and tipping it out. You read a small chunk from the source, write that chunk straight to the visitor, and repeat until the file is done. At any moment you are only holding one chunk in memory — a few kilobytes, not hundreds of megabytes.

In PHP that looks like this:

$handle = fopen($sourceUrl, 'rb'); if ($handle === false) { http_response_code(502); exit('Could not open the source file.'); } header('Content-Type: video/mp4'); header('Content-Disposition: attachment; filename="video.mp4"'); while (!feof($handle)) { echo fread($handle, 8192); flush(); } fclose($handle); 

The 8192 is the chunk size: 8 KB at a time. It is small enough that memory usage stays flat no matter how big the file is. You could use 64 KB or 256 KB and it would still work; the point is that the number stays fixed while the file size grows.

There are a few details that matter if you want this to work properly on real shared hosting.

First, turn off output buffering before you start streaming, or PHP will quietly collect your chunks into a buffer and you are back to holding the whole file. A call to ob_end_clean() at the top, plus flush() after each chunk, keeps the data moving.

Second, disable the time limit. PHP scripts on shared hosting often get 30 seconds before they are killed, and a big download on a slow connection takes longer than that. set_time_limit(0) tells PHP to let this script run until the file is done. Some hosts ignore it, so check your own limits — but on mine it works, and without it the longest downloads would always die at the same point.

Third, check whether the visitor is still there. If someone cancels a download halfway through, your loop keeps reading and writing to a connection nobody is listening to. Calling connection_aborted() inside the loop and breaking out early saves bandwidth and server time.

Fourth, never trust the size you are told. Some providers send a Content-Length header that is wrong, or send chunked responses with no length at all. If you forward a wrong length to the visitor, their browser may stop early or hang. I only forward Content-Length when I have verified it against what actually arrives, and otherwise I let the download finish by itself.

One thing I deliberately did not do: I never save the file to disk on my server first. Writing the video to a temp file and then serving it would double the work — download it, store it, then read it back out — and it would fill the disk with other people's videos. Streaming straight through means nothing is stored, the disk stays empty, and a visitor's download is only ever in memory in 8 KB pieces.

The lesson is a general one, and it applies well beyond download proxies. Whenever your code handles data that can grow — a file, an export, a log — ask yourself whether you are holding all of it at once. If you are, you have a hidden size limit waiting for the first big input to find it. Stream it in chunks, and the size stops mattering.