HOWTO · PHP

File Download With CURL in PHP

Download files in PHP with the cURL extension. Check availability, stream responses to disk, and handle transfer and HTTP errors.

On this page

Use PHP’s cURL extension to request a URL and stream its response to a local file. The PHP extension is separate from the curl command-line program: PHP code uses functions such as curl_init() and options such as CURLOPT_FILE.

What cURL does in PHP

cURL provides PHP with an interface for making requests through libcurl. For a download, the key choice is where response bytes go. Setting CURLOPT_FILE to an open file handle writes the response as it arrives, instead of building the complete response string in PHP memory. The example below uses a local fixture so it can run without an external service; replace $url with an HTTPS download URL in an application.

Check PHP cURL availability

Check the extension in the same PHP environment that will run the download. The command-line PHP installation and a web server’s PHP installation can load different configuration files. Run php -m in a terminal and look for curl, or call extension_loaded('curl') in the application. A phpinfo() page also lists cURL, but remove or protect that page because it exposes configuration details.

If the extension is missing, enable or install it using the instructions for that PHP build and restart the relevant PHP process. On managed hosting, ask the provider to enable the PHP cURL extension. Do not confuse enabling this extension with installing the separate curl command-line tool.

Download a file to disk

This self-contained PHP example creates a local source file and saves it to a second file. When the cURL extension is available, it streams the response with CURLOPT_FILE and checks the transfer result and (for HTTP or HTTPS) the response status. If the extension is unavailable, the local fixture uses PHP’s copy() stream wrapper so the example still runs; that fallback does not test cURL. In an application with cURL enabled, replace $url with the intended HTTPS download URL.

Run this example:

<?php
$sourcePath = tempnam(sys_get_temp_dir(), 'curl-source-');
$destinationPath = tempnam(sys_get_temp_dir(), 'curl-download-');

if ($sourcePath === false || $destinationPath === false) {
    fwrite(STDERR, "Could not create temporary files.\n");
    exit(1);
}

file_put_contents($sourcePath, "Sample download\n");
$url = 'file://' . $sourcePath;
$isHttp = strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0;
$downloaded = false;
$failure = '';

if (function_exists('curl_init')) {
    $handle = curl_init($url);
    $destination = fopen($destinationPath, 'wb');

    if ($handle !== false && $destination !== false) {
        curl_setopt($handle, CURLOPT_FILE, $destination);
        $transferred = curl_exec($handle);
        $error = curl_error($handle);
        $status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
        $downloaded = $transferred !== false && (!$isHttp || ($status >= 200 && $status < 300));
        $failure = $error !== '' ? $error : "HTTP status $status";
    } else {
        $failure = 'Could not initialize cURL or open the destination file.';
    }

    if ($handle !== false) {
        curl_close($handle);
    }
    if ($destination !== false) {
        fclose($destination);
    }
} else {
    $downloaded = copy($url, $destinationPath);
    if (!$downloaded) {
        $failure = 'PHP cURL is unavailable and the local stream copy failed.';
    }
}

if (!$downloaded) {
    fwrite(STDERR, "Download failed: $failure\n");
    unlink($destinationPath);
    unlink($sourcePath);
    exit(1);
}

printf("Saved %d bytes.\n", filesize($destinationPath));
unlink($destinationPath);
unlink($sourcePath);
Saved 16 bytes.

Opening the destination in binary write mode (wb) replaces an existing file and avoids text-mode transformations on platforms that distinguish text and binary files. In an application, validate the destination directory and filename before opening it; never derive a trusted filesystem path directly from an untrusted URL. Check fopen() and curl_init() before using their return values, and close both resources even when a transfer fails. For larger programs, use try/finally or equivalent cleanup so early returns do not leave handles open.

Handle cURL and HTTP errors

A failed curl_exec() indicates a transfer-level problem, such as a DNS, TLS, timeout, or connection failure. Inspect curl_error($handle) before closing the handle, and use curl_errno($handle) when code needs a stable error number. A successful cURL transfer does not by itself mean the server returned a successful HTTP response: HTTP 404 and 500 responses are still responses. For HTTP downloads, inspect CURLINFO_RESPONSE_CODE and decide which status range your application accepts.

Also check the destination handle before starting the request. A permission problem, missing directory, or full disk can prevent the local file from being created or written. Log a useful internal diagnostic, but show users a concise message and avoid exposing credentials or sensitive server details. If a partial file should not be mistaken for a complete download, delete it after any transfer or status failure, as the example does for its failure path.

Download large files and report progress

Writing with CURLOPT_FILE keeps the response body out of a single PHP string, so memory use does not grow with the entire file size. The destination still needs enough free disk space. Set connection and total timeouts to match the service and expected file size; for long transfers, increase the total timeout deliberately rather than leaving a request unbounded. A server may omit the total length, so progress can be unknown until the transfer finishes.

To display progress, enable callbacks with CURLOPT_NOPROGRESS set to false and provide CURLOPT_XFERINFOFUNCTION on supported PHP/libcurl versions. The callback receives downloaded and total byte counts; calculate a percentage only when the total is greater than zero. Progress callbacks may run frequently, so avoid expensive work or writing a new log line for every update. The callback and timeout values depend on the application’s PHP/libcurl environment and are not demonstrated as executed output here.

FAQ

Is PHP cURL the same as the curl command?

No. PHP cURL is an extension called from PHP code. The curl command-line program is a separate tool with its own options, such as -o; those command-line options are not PHP syntax.

Can I download from an HTTPS URL?

Yes. Use an https:// URL and keep certificate verification enabled. If verification fails, fix the CA certificate configuration rather than disabling TLS checks in production.

Why did cURL succeed when the download still failed?

For HTTP, inspect the response status separately from curl_exec(). A completed transfer can contain an error page or a non-success status. Also verify that PHP can write to the destination and that enough disk space is available.

How do I track download progress?

Enable the transfer callback and use its byte counts, accounting for an unknown total. The callback reports transfer progress; it does not guarantee that the server supplied a reliable content length.