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

Pain12's avatar

Symlink over SSH Sharedhosting

Hey guys, I have a website on a shared hosting provider. But my file upload doesn't work properly. The file is successfully uploaded to the folder storage/app/public/uploads but it doesn't show on my site if I call the file. On localhost everything works fine

What I did to deploy the website is.

Login over SSH:
-git pull my project
-install everything necessary
and then I ran 
php artisan storage:link

in my Laravel project folder.

My public folder is still inside the laravel folder as default. Nothing changed from the folder structure. In the option of the web host I set the target folder of the domain to the laravelproject/public.

When I log in via FTP everything seems fine. When I click on the storage folder in public it redirects me to storage/app/public.

Does anyone know why this can happen? Could it be that symlinks are deactivated on this shared hosting server? And if yes what would be the alternative that is secure.

0 likes
15 replies
Snapey's avatar

So your filename should be /storage/imagename.png for instance ?

1 like
Pain12's avatar

my controller looks like this

if($request->hasFile('image')){
        //Get filename with the extension
        $filenameWithExt = $request->file('image')->getClientOriginalName();
        //Get just filename
        $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
        //Get just ext
        $extension = $request->file('image')->getClientOriginalExtension();
        //Filename to store
        $fileNameToStore= $filename.'_'.time().'.'.$extension;
        //Upload Image
        $path = $request->file('image')->storeAs('public/uploads', $fileNameToStore);
        } 
        else 
        {
        $fileNameToStore = 'noimage.jpg';
        }

$post->image = $fileNameToStore;
$post->save();

And I try to view the file with

<a><img style="width:100%" onclick="test()" id="{{$post->id}}"class="z-depth-1 img-thumbnail" src="./storage/uploads/{{$post->image}}" alt="video"
                        data-toggle="modal" data-target="#modal-{{$post->id}}"></a>

so yes file is named storage/upload/imagename.png

Snapey's avatar

remove the dot in

src="./storage/uploads/{{$post->image}}"

check the generated src in the browser source and check if you can get the file by pasting the url into the address bar.

Note this will NOT work correctly if you can see /public/ in your URL

jlrdw's avatar

The way you installed, you should check your .env file to make sure it's not exposed.

Why would you need a symlink, you could just put images under public_html ( an image folder).

Use asset helper to serve them.

Did you properly install laravel above public_html.

Pain12's avatar

@snapey removing the dot didn't change anything. The link in the browser is src="/storage/uploads/chip_1585082904.jpg" and if I pas the link in the browser I get the error:

Server Error
403
Forbidden
You do not have permission to access this document.

@jlrdw why would my .env file be exposed? My Laravel project is over the folder public. MyProject/public -> here is where I linked the hosting to look for the index.html

And on localhost everything works fine. Pushed it to git and installed everything on the webserver. Same folder structure.

Snapey's avatar

Everything sounds ok, but the error you are getting is presumably direct from your web server, not generated by laravel

The only thing I can suggest is check your permissions. The public storage folder should have read only permission

@jlrdw the reason people write images into storage folder (and then symlink it to the outside) is so that you then don't need to give the web server any permission to write to any external facing folders. Yes files can be saved directly on the public side, but you have to then give write permissions

1 like
Pain12's avatar

@snapey Symlinks are not allowed on the server that why I am now trying to upload the file directly to the public path. But if i use

 $path = $request->file('image')->storeAs('public/uploads', $fileNameToStore);

it always stores the file in the storage folder even if I change the disks to something like this.

'disks' => [

        'local' => [
            'driver' => 'local',
            'root' => public_path('app'),
        ],

        'public' => [
            'driver' => 'local',
            'root' => public_path('app/public'),
            'url' => env('APP_URL').'/public',
            'visibility' => 'public',
        ],

How should my function and disk looks like if i want to use the

storeAs function?
Snapey's avatar

From the docs

If you do not want a file name to be automatically generated, you may use the storeAs method, which accepts the path, file name, and disk name as its arguments:

$path = $request->file('image')->storeAs('uploads', $fileNameToStore, 'public');

but your public disk definition seems wrong also

I would expect more like

        'public' => [
            'driver' => 'local',
            'root' => public_path(),
            'url' => env('APP_URL'),
            'visibility' => 'public',
        ],
1 like
Pain12's avatar

hmm it still uploads in the storage folder. Maybe I need to clear the cash or something? The upload lands in storage/app/public/uploads if I use

$path = $request->file('image')->storeAs('uploads', $fileNameToStore, 'public');
      'public' => [
            'driver' => 'local',
            'root' => public_path(),
            'url' => env('APP_URL'),
            'visibility' => 'public',
        ],

what I found is this : https://stackoverflow.com/questions/44577380/how-to-upload-files-in-laravel-directly-into-public-folder

But when I create a new disk and try to use it with

storeAs('uploads', $fileNameToStore, 'public');

I get the error

Disk [public_uploads] does not have a configured driver.

New disk

 'public_uploads' => [
            'driver' => 'local',
            'root'   => public_path() . '/uploads',
        ],

new function

$path = $request->file('image')->storeAs('', $fileNameToStore,'public_uploads');

Edit: found https://laracasts.com/discuss/channels/laravel/image-upload-using-storage-function-to-public-folder Will try

Edit2: For me worked

$request->file('photo')->move(public_path("/uploads"), $newfilename);
jlrdw's avatar

Just make a constant something like:

define('ASSET', realpath(dirname(__FILE__)). DS . 'assets' . DS);

And when uploading:

// code for request etc etc
$destinationPath = ASSET . 'upload/imgdogs';
// more upload code etc etc

// then

$file->move($destinationPath, $newname);

I.e.,

        if (isset($_POST['submit'])) {
            $lid = DB::table('dc_dogs')->count();
            $lid = $lid + 1;
            $file = Request::file('ufile');
            $file_name = $file->getClientOriginalName();
            $file_ext = $file->getClientOriginalExtension();

            $fileInfo = pathinfo($file_name);
            $filename = $fileInfo['filename'];
            $newname = $filename . $lid . "." . $file_ext;
            $destinationPath = ASSET . 'upload/imgdogs';
            $file->move($destinationPath, $newname);

            $dogpic = $newname;
            $dogname = ucfirst(Request::input('dogname'));
            $sex = ucfirst(Request::input('sex'));
            $comments = Request::input('comments');
            $adopted = !empty(Request::input('adopted')) ? '1' : '0';
            $lastedit = date("Y-m-d H:i:s");


            $postdata = array(
                'dogpic' => $dogpic,
                'dogname' => $dogname,
                'sex' => $sex,
                'comments' => $comments,
                'adopted' => $adopted,
                'lastedit' => $lastedit
            );

            DB::table('dc_dogs')->insert($postdata);
        }

Never had an issue with that, never no error, never no problem, but validate as needed.

Edit: You have to define DS

defined('DS') || define('DS', DIRECTORY_SEPARATOR);

Saves having to type DIRECTORY_SEPARATOR every time.

Pain12's avatar
Pain12
OP
Best Answer
Level 1

I found a short answer to the problem just move the file to the public with

$request->file('photo')->move(public_path("/uploads"), $newfilename);
jlrdw's avatar

There you go. How are you ensuring unique names.

Pain12's avatar
	$filenameWithExt = $request->file('image')->getClientOriginalName();
        //Get just filename
        $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
        //Get just ext
        $extension = $request->file('image')->getClientOriginalExtension();
        //Filename to store
        $fileNameToStore= $filename.'_'.time().'.'.$extension;

My first post =D

jlrdw's avatar

@pain12 If you would and don't mind, show your complete code, I will bookmark to help others in the future.

May questions come up on image uploading.

Pain12's avatar

My whole function looks like this. Just that the validation message doesn't work.

public function store(Request $request)
    {

        $rules = array(
            'title' => 'required',
            'body' => 'required',
            'image' => 'file|image|nullable|max:5000',
            'monday' => 'required_without_all:tuesday,wednesday,thursday,friday,saturday,sunday,immer',
            'tuesday' => 'required_without_all:monday,wednesday,thursday,friday,saturday,sunday,immer',
            'wednesday' => 'required_without_all:monday,tuesday,thursday,friday,saturday,sunday,immer',
            'thursday' => 'required_without_all:monday,tuesday,wednesday,friday,saturday,sunday,immer',
            'friday' => 'required_without_all:monday,tuesday,wednesday,thursday,saturday,sunday,immer',
            'saturday' => 'required_without_all:monday,tuesday,wednesday,thursday,friday,sunday,immer',
            'sunday' => 'required_without_all:monday,tuesday,wednesday,thursday,friday,saturday,immer',
            'immer' => 'required_without_all:monday,tuesday,wednesday,thursday,friday,saturday,sunday'
        );

        $message = array(
            'title.required' => 'The :attribute field is required.');
            // 'title' => 'Titel benötigt' ,
            // 'body' => 'link benötigt',
            // 'image' => '',
            // 'monday' => 'test',
            // 'tuesday' => 'test',
            // 'wednesday' => 'test',
            // 'thursday' => 'test',
            // 'friday' => 'test',
            // 'saturday' => 'test',
            // 'sunday' => 'test',
            // 'immer' => 'test');  
            
        $this->validate($request,$rules,$message);
   
        //Handle File Upload
        if($request->hasFile('image')){
        //Get filename with the extension
        $filenameWithExt = $request->file('image')->getClientOriginalName();
        //Get just filename
        $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
        //Get just ext
        $extension = $request->file('image')->getClientOriginalExtension();
        //Filename to store
        $fileNameToStore= $filename.'_'.time().'.'.$extension;
        //Upload Image to public folder
        $path = $request->file('image')->move(public_path("/uploads"), $fileNameToStore);
        } 
        else 
        {
        $fileNameToStore = 'noimage.jpg';
        }     
    
        // Create Post
        $post = new Post;
        $post->title = $request->input('title');
        $post->body = $request->input('body');

        $post->monday = $request->has('monday') ? 1 : 0;
        $post->tuesday = $request->has('tuesday') ? 2 : 0;
        $post->wednesday = $request->has('wednesday') ? 3 : 0;
        $post->thursday = $request->has('thursday') ? 4 : 0;
        $post->friday = $request->has('friday') ? 5 : 0;
        $post->saturday = $request->has('saturday') ? 6 : 0;
        $post->sunday = $request->has('sunday') ? 7 : 0;
        $post->immer = $request->has('immer') ? true : false;

        $post->timefrom = $request->input('timefrom');
        $post->timeto = $request->input('timeto');

        $post->ort =$request->input('ort');
       
        $post->user_id = auth()->user()->id;
        $post->image = $fileNameToStore;
        $post->save();


        return redirect('/posts')->with('success', 'Post Created');
    }
1 like

Please or to participate in this conversation.