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

escozul's avatar

Moving to a new server and js doesn't load

So here it is.

We have a working laravel 7.28.4 laravel on php 7.2 and have to move it from a centos cpanel server to an ubuntu server.

So here is what we did.

  1. Enabled php7.2 on the new installation
  2. Installed Laravel globally for the specific user: composer global require laravel/installer
  3. Made an installation of laravel 7 on the user: php7.2 /usr/local/bin/composer create-project --prefer-dist laravel/laravel:^7 www.domain.com
  4. Tested it. It was working just fine. running php7.2 artisan --version returned version 7.30.6
  5. tested through web browser. Laraverl test site came up without issue. Environment seemed ready to receive the website
  6. compressed all files and exported database from old server
  7. copied both files and database from old server to the new one.
  8. extracted and imported.
  9. Found using grep and replaced using sed all occurences of the old server's home folder.
  10. Changed all .env settings to reflect the home folder, site url and databases used on the new serer.

After all that we tested to see if the website was comming up It was but with errors Mainly js errors. In reality only js errors seemed that the js controller was not being triggered in the routes and all javascript files returned error 404. They literally produced the 404 error whenever you even tried to directly access them.

After searching and searching we realised that it was not the controller's fault. the js files produce a 404 error when paired with nginx. It seems that once nginx picks up .js as the extension of an asset, it takes it and produces a 404 error before passing it through the routes/web.php. If we use different extensions (eg .javascript instead of .js) the files are being served.

It seems to me that when nginx "detects" a request for a .js file, it does not pass it through laravel and checks if the file physicaly exists in the file tree. When it's not found there (our js files are created using a js controller) it returns a 404 error before giving the chance to laravel to serve the file.

can you think of a way around it?

0 likes
17 replies
Sinnbeck's avatar

Can you share the url so we can see the error? Also show the nginx config.

escozul's avatar

@Sinnbeck sure: https://sxm.androutsos.co/

Running nginx -V gives:

nginx -V
nginx version: nginx/1.21.4
built with OpenSSL 3.0.2 15 Mar 2022
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/home/clp/packaging/nginx/tmp/nginx-1.21.4=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/ngx-brotli --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/ngx-pagespeed --with-http_addition_module --with-http_geoip_module=dynamic --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_xslt_module=dynamic --with-stream=dynamic --with-stream_ssl_module --with-stream_ssl_preread_module --with-mail=dynamic --with-mail_ssl_module --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/http-auth-pam --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/http-dav-ext --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/http-echo --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/http-upstream-fair --add-dynamic-module=/home/clp/packaging/nginx/tmp/nginx-1.21.4/debian/modules/http-subs-filter

I can also post the contents of /etc/nginx/nginx.conf if you want

escozul's avatar

@Sinnbeck I need gelp with that. I'm not familiar with certbot.

Little note, the site is working on the old server. This error is something we picked up when we moved files and db

escozul's avatar

No, I don't get an error. https perfectly fine... What error do you get?

Could you please refresh the page? In any case, it's still on self-signed certificate. Should not be a problem once we install everything. My error is not with the certificates

I get a 404 error when trying to call js from the routes.

escozul's avatar

:/

ok... I need to check that.

But it's another issue though. My problem is with the app.js not loading on that page.

sxm.androutsos.co/app.js returns a 404 error.

escozul's avatar

https should be fixed now. js not loading is still the issue at hand

escozul's avatar

Hello,

Posting as a follow up here.

After lots of trial and error, I figured out what the issue was with the nginx configuration of the vhost.

Here is the part that prodces the issue:

location ~* ^.+\.(css|js|jpg|jpeg|gif|png|ico|gz|svg|svgz|ttf|otf|woff|woff2|eot|mp4|ogg|ogv|webm|webp|zip|swf)$ {
    add_header Access-Control-Allow-Origin "*";
    expires max;
    access_log off;
  }

So all requests for any of these extensions above (see that js is icluded there?) are handled by nginx and some parameters are set like disabling the access log and setting the cache expiration to max (I think it's about 30 years). That part also makes these files have the Access-Control-Allow-Origin header. That's all fine and nice but what it actually does is that it makes nginx look for the file before passing it to laravel. When nginx can't find it, it returns a 404 nginx error.

Now here is the thing. My app has a handler for 404 errors. It produces a nice page with a 404 png and the logo of my site. Not for those files though. They never reach laravel to be routed. The error is thrown immediately by nginx.

I really found no way around it. The only thing I could do was to just remove js from the list of extensions and that way nginx was no longer throwing a 404 error.

However I came across a different issue after getting the request to laravel. Laravel was serving all those javascript files as raw text. And that's when chrome's strict mime type error popped up! because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled My javascript controller does not pass a mime header along with the js files so they are perceived as text files, not executable by chrome (and most browsers) and do not run!

I guess I should create a new thread with that issue but there it is. If I see no answers here by tomorrow I will start a new thread with this new issue.

Lumethys's avatar

@escozul why do you need a JS "Controller"? JS is not supposed to served by controllers

escozul's avatar

@Lumethys Hello,

I'm quite new with Laravel. I am mainly a js and php programmer. This project was passed on to me and I'm trying to migrate it to a new server. The previous programmer made that choice. I'm not quite sure why he chose to use a JS controller instead of writing the pure js scripts and putting them all inside a folder.

He did explain to me that some js files include variables and sql calls to the database but I don't see why using a controller would help with that.

For the record, I copied all JS and placed it inside the public folder (all js is called directly like so: www.domain.com/file.js) and they all worked.

I want to fix this issue in order to preserve the original structure of the website after the migration. I want to keep all intervention to a minimum.

The js controller is not something particularly complex. It just returns the contents of specific views for each route. Here it is:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class JavascriptController extends Controller {

    public function sxm_javascript() {
        return view('_javascript.sxm_javascript');
    }

    public function sxm_home() {
        return view('_javascript.sxm_home');
    }

    public function sxm_submit_one() {
        return view('_javascript.sxm_submit_one');
    }

    public function sxm_submit_two() {
        return view('_javascript.sxm_submit_two');
    }

    public function sxm_listing() {
        return view('_javascript.sxm_listing');
    }

    public function sxm_search_results() {
        return view('_javascript.sxm_search_results');
    }

    public function sxm_myspiti_searches() {
        return view('_javascript.sxm_myspiti_searches');
    }

    public function sxm_myspiti_favorites() {
        return view('_javascript.sxm_myspiti_favorites');
    }

    public function sxm_myspiti_notes() {
        return view('_javascript.sxm_myspiti_notes');
    }

    public function sxm_myspiti_hidden() {
        return view('_javascript.sxm_myspiti_hidden');
    }

    public function sxm_myspiti_profile() {
        return view('_javascript.sxm_myspiti_profile');
    }

    public function sxm_myspiti_alerts() {
        return view('_javascript.sxm_myspiti_alerts');
    }

    public function sxm_myspiti_listings() {
        return view('_javascript.sxm_myspiti_listings');
    }

    public function sxm_auth() {
        return view('_javascript.sxm_auth');
    }

    public function sxm_contact() {
        return view('_javascript.sxm_contact');
    }
}

The views have the contents of each file. Pretty simple really. That means that the files contain no headers whatsoever so they are served as text/html which triggers the content protection in the browser and the code is not run.

Lumethys's avatar

@escozul that is a very bad practice coming from whoever decided they should do that

AND, what did you say? JS file executing SQL query? That is a pretty BIG red flag right there, JS is supposed to run on the front-end, where do it get the credentials to go through your database?

With this kind of brainless set-up, I assume you are not running nodejs on the backend, right? Then there is some pretty big security concern right there

To be honest, I just fixed an intern's work at my company the other day, who split db credential right into the frontend. They think just put {{ config('db_password) }} it will be safe, but in fact it just spit the password right into html

You may want to re-check their logic to prevent some catastrophe from happening

1 like
escozul's avatar

@Lumethys Hello. I too asked exactly that yesterday. "How will you be running SQL calls from js? That's run on the client side and MySql should not be reachable by any other way than the localhost." so even if passwords and db are passed on the js, they should not run. So these things might expose vulnerabilities and... they should not run in the first place.

At this point however, I'm not quite sure where that happens in the js files. I do not believe the SQL is accessible from other than the localhost on the old server. If it is some special setup that allows connection from remote servers (there is a way to do that but it's not standard) I won't be transferring that to the new installation. I believe he meant something else and didn't have time to explain in detail. I've only contacted the old programmer briefly over phone calls.

I'll need to first migrate the site and then check that out line by line. I will definitely be requesting suggestions if I find some sort of security issue.

Lumethys's avatar

@escozul i think the best move to just ignore that Js controller and put a script tag in any file that need it and let laravel handle it like it supposed to do

Honestly, your case is something very obscure and i doubt you will find a solution here. There are a lot of thing that may come into play here:

+The default behavior is Laravel will you asset bundler to serve JS and CSS, which maybe Webpack, or Vite, that could affect how this work

+namespace and autoload: composer and laravel had their own way of caching route. Is that path registered? can a controller actually access that folder? Does autoload had some special case to js file? OR do Laravel had special way of handling .js file inside php app?

+server config, many server treat frontend asset differently from other file, not too long ago i saw someone had some problem with how nginx treat .js file

It could be any of those things, or some combination of them

1 like
escozul's avatar
escozul
OP
Best Answer
Level 1

@Lumethys Hello. 2 things: First: I fixed it. I got the suggestion from the old programmer to add -> header('Content-Type', 'application/javascript'); at the return of every view on the js controller I posted above. That did not work of course. Laravel documentation gives another example: https://laravel.com/docs/8.x/responses#view-responses So I changed the js controller and made it as so:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class JavascriptController extends Controller {

    public function sxm_javascript() {
        //return view('_javascript.sxm_javascript');
        return response()
        -> view('_javascript.sxm_javascript')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_home() {
        //return view('_javascript.sxm_home');
        return response()
        -> view('_javascript.sxm_home')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_submit_one() {
        //return view('_javascript.sxm_submit_one');
        return response()
        -> view('_javascript.sxm_submit_one')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_submit_two() {
        //return view('_javascript.sxm_submit_two');
        return response()
        -> view('_javascript.sxm_submit_two')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_listing() {
        //return view('_javascript.sxm_listing');
        return response()
        -> view('_javascript.sxm_listing')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_search_results() {
        //return view('_javascript.sxm_search_results');
        return response()
        -> view('_javascript.sxm_search_results')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_searches() {
        //return view('_javascript.sxm_myspiti_searches');
        return response()
        -> view('_javascript.sxm_myspiti_searches')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_favorites() {
        //return view('_javascript.sxm_myspiti_favorites');
        return response()
        -> view('_javascript.sxm_myspiti_favorites')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_notes() {
        //return view('_javascript.sxm_myspiti_notes');
        return response()
        -> view('_javascript.sxm_myspiti_notes')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_hidden() {
        //return view('_javascript.sxm_myspiti_hidden');
        return response()
        -> view('_javascript.sxm_myspiti_hidden')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_profile() {
        //return view('_javascript.sxm_myspiti_profile');
        return response()
        -> view('_javascript.sxm_myspiti_profile')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_alerts() {
        //return view('_javascript.sxm_myspiti_alerts');
        return response()
        -> view('_javascript.sxm_myspiti_alerts')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_myspiti_listings() {
        //return view('_javascript.sxm_myspiti_listings');
        return response()
        -> view('_javascript.sxm_myspiti_listings')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_auth() {
        //return view('_javascript.sxm_auth');
        return response()
        -> view('_javascript.sxm_auth')
        -> header('Content-Type', 'application/javascript');
    }

    public function sxm_contact() {
        //return view('_javascript.sxm_contact');
        return response()
        -> view('_javascript.sxm_contact')
        -> header('Content-Type', 'application/javascript');
    }
}

You can see I've commented out how the code was and I converted it in a way that it spits out also the header of the content type. I suppose I can also add the Access-Control-Allow-Origin there that nginx wants for js files. If I require that I might add it.

Second: Thank you for your responses and any insight. However, changing and improving a site that was programmed by another one is very complex. Mainly because I do paid work. So, I'm getting paid for the migration at this point and I can only make suggestions about the security of the installation. I will use our conversation here so that I do not appear biased. I will present my concerns and back them with your comments, but it's up to the owner of the page to decide if he is willing to pay for the developing time.

In conclusion: thank you for your help and time. Your contribution was invaluable and I have to say, seing how laravel works, how well it's documented, how scalable it is and how helpful the community is, makes me want to learn more and use it on future projects as fast as possible.

Lumethys's avatar

@escozul i'm glad that you found a solution, with these kind of hacks it could take anywhere from a few hours to a few weeks to figured out

But even then it is not guaranteed to work consistently, well, either case, let the client decide what to do seem to be the best move here

1 like

Please or to participate in this conversation.