Laravel 7. How to download a file from server (FTP)?

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Laravel 7: Mastering Remote File Downloads via FTP

As senior developers working with modern PHP frameworks like Laravel, we frequently encounter scenarios where application logic needs to interact with external file systems, such as downloading assets from an FTP server. Laravel’s Filesystem abstraction layer is incredibly powerful, allowing us to treat local disks, S3 buckets, and remote protocols uniformly. However, configuring custom drivers like FTP can introduce specific hurdles, especially when dealing with remote access and permissions.

This post dives into a common challenge: attempting to download files from an FTP server using the Laravel Filesystem, troubleshooting the resulting errors, and understanding the correct way to serve file content in a web response. We will walk through the exact problem presented and provide a robust solution.

Setting Up the Remote FTP Disk

The first step in utilizing remote storage within Laravel is defining the connection parameters within your configuration file. This tells Laravel how to communicate with the external server.

You correctly set up the FTP driver in config/filesystems.php:

'ftp' => [
    'driver' => 'ftp',
    'host' => 'ftp.domain.org',
    'username' => 'username',
    'password' => 'password',
    'passive' => true,
    'timeout' => 30,
    'root' => '/',
    'url' => '/'
],

While this configuration successfully establishes the underlying connection test, the failure during the download operation points to an issue with how the download() method interacts with the remote stream or permissions on the server side.

Troubleshooting the Download Failure

When you execute code like $file = Storage::disk('ftp')->download($file_path);, and receive a FileNotFoundException, it usually means one of two things: either the connection timed out mid-stream, or the specific FTP driver implementation within Laravel cannot correctly resolve the file path on the remote server, even if the initial connection was successful.

In many cases involving custom drivers like FTP, direct stream downloading is problematic because standard PHP stream wrappers might not handle the necessary session management required by FTP protocols seamlessly across all environments.

A more reliable approach, especially when dealing with external resources, is to fetch the file content directly using a lower-level mechanism if the high-level download method fails. Instead of relying solely on Storage::download(), we can manually manage the stream to ensure robust error handling and direct control over the response.

Displaying Remote File Content in the Browser

The secondary issue—how to display the file content instead of forcing a download—is equally important. The attempt using response()->file($file_path) often fails for remote files because this method expects a local path or a path accessible directly by the PHP process, which is not the case when dealing with an abstracted remote FTP stream managed by the filesystem driver.

To successfully serve content downloaded from a remote source, we need to bypass the abstraction layer momentarily and handle the file fetching manually using the established connection details. This allows us to control exactly what data is sent back to the user.

Here is a pattern that addresses both problems: establishing the connection, reading the file stream, and returning the correct HTTP response headers.

use Illuminate\Support\Facades\Storage;

// ... inside your controller method

$disk = Storage::disk('ftp');
$file_path = '/folder/aaa.txt'; // The path on the FTP server

try {
    // Attempt to get the stream directly for robust handling
    $stream = $disk->get($file_path);

    if ($stream === false) {
        throw new \Exception("File not found or access denied on FTP server.");
    }

    // Return the file content with appropriate headers
    return response($stream, 200, [
        'Content-Type' => 'text/plain', // Adjust MIME type as needed
        'Content-Disposition' => 'inline; filename="' . basename($file_path) . '"',
    ]);

} catch (\Exception $e) {
    // Handle error appropriately
    return response()->json(['error' => $e->getMessage()], 500);
}

Conclusion

Working with remote file systems via Laravel requires moving beyond the high-level convenience methods when custom drivers are involved. While the Storage facade provides an excellent interface for local and cloud storage, complex protocols like FTP demand a more granular approach. By understanding where the abstraction layer breaks down—specifically around stream handling and permissions—we can implement robust fallback logic. Mastering these low-level interactions ensures that your applications remain reliable, regardless of whether the file resides on your local disk or across an external FTP server. For deeper insights into Laravel's ecosystem and best practices for building scalable applications, always refer to the official documentation at laravelcompany.com.