Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

robertp1980's avatar

Serving private image by route!

I have a simple route to serve a image from a private directory. But why i get an 404 error, but the file is correct loaded on my path like /profile/picture/picture1.jpg?

Route::get('profile-picture/{path}', function ($path) {
    return response()->file(storage_path("app/private/" . $path));
});

When i load the image hardcoded without the {path}, its everything fine.

0 likes
18 replies
Snapey's avatar

@robertp1980 duplicate question

my mistake, you must have deleted Lary's answer and changed your question to remove the where condition

Snapey's avatar

Do you have other routes using 'profile-picture' ?

Snapey's avatar

change code to this and check the full path is as expected

Route::get('profile-picture/{path}', function ($path) {
    dd(storage_path("app/private/" . $path));
    return response()->file(storage_path("app/private/" . $path));
});
robertp1980's avatar

Yes, the path is full provided "/var/www/html/storage/app/private/test.png". But i have noticed something.

I have hardcoded to file name to test something, and when i open the url like ".../profile-picture/test", everything works ok, but when i put also the dot before the suffix".../profile-picture/test.png" i get the 404 error in the response.

My Problem ist not, that the file is not loading, rather then i got an 404 error when i load the file with the suffix in it:

".../profile-picture/test" -> File is showing - No 404

".../profile-picture/test.png" -> File is showing - 404

Snapey's avatar

probably being served as image, not routed to index.php. I'm guessing your 404 is a webserver error, not a laravel 404 page

So it will be a case of finding a way to fix the rewrites, without impacting other static assets, or either omiting the extension or substituting the. for another valid char ( then undoing it in your code) eg profile-picture/test^png

or passing as two parameters like

profile-picture/test/png
Route::get('profile-picture/{path}/{ext}', function ($path, $ext) {
    return response()->file(storage_path("app/private/" . $path . '.' . $ext));
});
tangtang's avatar

@robertp1980

or you can add where logic in your route.

Route::get('profile-picture/{path}', function ($path) {
    return response()->file(storage_path("app/private/" . $path));
})->where('path', '(.*)');

this will allow URLs with dots in the parameter with using ->where('path', '(.*)')

you are essentially allowing any characters, including dots, in the {path} parameter. This should allow you to access files with dots in their names without encountering a 404 error.

but why using {path} in route, aren't you want to serve a image from a private directory, I assume this directory is really private and you dont want user even know what the file name is.

you can do that with this code reference

route

Route::get('profile-picture/{id}', 'PictureController@show')->name('profile-picture.show');

controller

public function show($id)
{
		$picture= ProfilePicture::find($id);

        if (Storage::exists($picture->path)) { // assume you save the path in column database named `path`
            return response()->file(Storage::path($picture->path));
        } else {
            return response()->file(Storage::path('private/no-photo.jpg'));
        }
}

blade

// assume you compact this `$profile` from controller to this blade
<img src="{{ route(profile-picture.show', ['id' => $profile->id]) }}" />

with this code user dont even know file name, inspect element just show

<img src="https://domain.test/profile-picture/2">

1 like
robertp1980's avatar

@tangtang My approach is to show/load images, restrict them by an middleware. I want achive, that user can create own content ( with images etc.) and publish them bei on/off. when the content is off, that middleware returns false and the image is not accassible. At the top, that was only my example to test something. The Problem is, that i always get an 404 error response error, but the image is correctly loaded.

Like @snapey says, its must be a apache/nginx/htaccess issue. Cause i get this results

/test.jpg - 404 - Image correct loaded

/test.foo - NO 404 - Image correct loaded

/test - NO 404 - Image correct loaded

I am using DDEV for my dev enviroment.

Snapey's avatar

@tangtang

this will allow URLs with dots in the parameter with using ->where('path', '(.*)')

only if the request reaches the laravel framework

1 like
robertp1980's avatar

Ok, i found, that this must be a ddev nginx bug or server configuration etc. on my xampp etc, its run well without the 404.

But my other question is, if i serve the images over the route file response etc. and my laravel app takes 6mb on each call, with maybe 40 images on the frontpage etc, it would take 40x6mb on the resource side on my server?

Snapey's avatar

no, each image request is its own request lifecycle

robertp1980's avatar

Yes, but i mean, when i open the fronpage with the 40 images, each image request call a 6mb laravel instance for the image response, right?

Snapey's avatar

@robertp1980 You should be more concerned that each of 40 images has to boot the framework, makeing this process quite slow.

This is why only in rare and specific security situations do we serve images through the framework.

robertp1980's avatar

Ok. What would be the best apprach, to show private images to public, without to use CDN like S3 with temporary urls etc? When model ist published -> can access/view images,

When unpublished, cant access/view images.

My solution would be maybe, to copy/delete images to/from public dir, when the model is published/unpublishd.

Or is maybe a better solution?

Snapey's avatar

@robertp1980 Make sure you give each image an impossible to guess URL, eg UUID or long random string.

Put the image in public space.

When the model is unpublished, don't include the reference to the image in any view.

When the model is moved from published to unpublished, move the image to a new random filename so that previously shared links no longer work.

Please or to participate in this conversation.