How can I change the line endings used by fputcsv?

You could use stream filters to accomplish this. This example writes to a physical file, but it should work fine for php://output as well.

// filter class that applies CRLF line endings
class crlf_filter extends php_user_filter
{
    function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in)) {
            // make sure the line endings aren't already CRLF
            $bucket->data = preg_replace("/(?<!\r)\n/", "\r\n", $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}
// register the filter
stream_filter_register('crlf', 'crlf_filter');

$f = fopen('test.csv', 'wt');
// attach filter to output file
stream_filter_append($f, 'crlf');
// start writing
fputcsv($f, array('1 1', '2 2'));
fclose($f);

Since PHP 8.1 fputcsv accepts a new $eol parameter to do this. By default is "\n" but can change it to "\r\n".

fputcsv($stream, $fields, eol: "\r\n");

Not sure if you can do this with PHP itself. There may be a way to change PHP's EOL for file writing, but it's probably system dependent. You don't have a windows system you could ping, do you? ;)

As for a real solution, instead of str_replace line-by-line, you could use the Linux program unix2dos (inverse of dos2unix) assuming you have it installed:

fputcsv($fh ...)
exec("unix2dos " . escapeshellarg($filename));

  1. Create the file with \n line endings on a Linux machine
  2. FTP the file as ASCII from the Linux machine to a Windows machine
  3. Hey presto! All line endings are now \r\n in the file on the Windows machine