Download a password-protected file safely with PHP cURL

Fetch a fixed HTTPS resource with HTTP Basic authentication, bounded response size and timeouts, then save without overwriting an existing file.

HTTP Basic authentication is common on private exports and staging sites. It is not the same as signing into a form: this example only works when the remote server supports Basic authentication and you have permission to download the file.

Keep the credentials and output private

Run this as a CLI script with PHP 8.3+ and cURL 7.85+ installed. Supply EXPORT_USERNAME and EXPORT_PASSWORD through your private runtime configuration, not literals committed to source or pasted into a shell command. Replace the fixed URL and output path with your own authorised locations.

<?php
declare(strict_types=1);

$username = getenv('EXPORT_USERNAME');
$password = getenv('EXPORT_PASSWORD');
if (!is_string($username) || $username === ''
    || !is_string($password) || $password === '') {
    throw new RuntimeException('Download credentials are not configured.');
}

$body = '';
$limit = 2 * 1024 * 1024;
$request = curl_init('https://exports.example.com/report.csv');
curl_setopt_array($request, [
    CURLOPT_HTTPAUTH => CURLAUTH_BASIC,
    CURLOPT_USERPWD => $username . ':' . $password,
    CURLOPT_PROTOCOLS_STR => 'https',
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_WRITEFUNCTION => static function ($handle, string $chunk) use (&$body, $limit): int {
        if (strlen($body) + strlen($chunk) > $limit) {
            return 0;
        }
        $body .= $chunk;
        return strlen($chunk);
    },
]);

if (curl_exec($request) === false
    || curl_getinfo($request, CURLINFO_RESPONSE_CODE) !== 200) {
    throw new RuntimeException('Download failed; no output file was created.');
}

$oldMask = umask(0077);
$output = fopen('/absolute/private/export.csv', 'xb');
umask($oldMask);
if ($output === false) {
    throw new RuntimeException('Could not create a new private output file.');
}
try {
    if (fwrite($output, $body) !== strlen($body)) {
        throw new RuntimeException('Output is incomplete; do not use this file.');
    }
} finally {
    fclose($output);
}

The destination directory must already exist, be controlled by you and sit outside the web root. Exclusive xb creation refuses an existing file. A disk-write failure can leave an incomplete new file: inspect it before retrying, rather than treating its existence as success.

No redirects are followed, so credentials are not carried into an unexpected redirect chain. Keep the hostname fixed; do not make it a visitor-supplied URL. Check the expected content format too before consuming an export. Never disable certificate checks to “fix” a failed download.

References: PHP cURL options and exclusive file creation.

Original version14 July 2013

Kept here for reference and earlier links. The updated guide above is the recommended starting point; older code may depend on retired services or different software versions.

cURL can be used to grab data, information, or even a whole webpage from a designated URL.

This can be very useful for grabbing information between sites, exporting data and manually retrieving it, without having to use database connections.

If the external website has a password with .htaccess or the like then some extra setup is needed.

With the following example, you choose a text file to grab information from on an external website, and then where to save it to on the local website.
This, of course, doesn't have to be a TXT file, could be HTML, CSV or any data really.

<?php
$username = 'theUsername'; // Change this to your username
$password = 'thePassword'; // Change this to your password
$location = 'https://yourWebsite.com/LinkToTheFile.txt'; // Change this to the external website location of the data you want to grab


$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $location);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($ch, CURLOPT_USERPWD, $username.':'.$password);
curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 6.0; da; rv:1.9.0.11) Gecko/2009060215 Firefox/3.0.11');
$contents = curl_exec($ch);
if ($contents === false) {
	trigger_error('Failed to execute cURL session: ' . curl_error($ch), E_USER_ERROR);
}
$file = 'myOutput.txt'; // Change this to choose where to locally save the contents that was grabbed
// Write the contents back to the file
file_put_contents($file, $contents);

?>

Just make sure to change the first 3 variables, to your relevant details, and also the $file variable, to choose where you want to save the output.

Keep exploring

A couple more useful notes.