I have found a possible way how to handle multiple Laravel projects dynamically in one NGINX server (so in one foldder there may be multiple folders for each laravel project. The entry point of each laravel project is /public
folder where the index.php
is located). The main idea behind this is to easily install the new laravel projects just by adding a new folder with it and be able to access it via my subdomain and path. Like www.webs.example.com/my-new-project
All credit to Henno
This is a NGINX conf for my domain
# Capture $project from /$projectname/controller/action
map $request_uri $project {
~^/(?<captured_project>[a-zA-Z0-9_-]+)/? $captured_project;
default / ;
}
server {
listen 443; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/webs.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/webs.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_name webs.example.com www.webs.example.com;
# Use $project/public as root
root /var/www/laravel/$project/public;
# Use index.php as directory index
index index.php;
# Include the basic h5bp config set (see https://github.com/h5bp/server-configs-nginx)
# include h5bp/basic.conf;
# Process /projectname/the/rest/of/the/url
location ~ ^/([^/]+)/(.*) {
# Save the rest of the URL after project name as $request_url
set $request_url /$2;
# If the saved url refers to a file in public folder (a static file), serve it,
# else redirect to index.php, passing along any ?var=val URL parameters
try_files $uri $request_url /index.php?$is_args$args;
}
# Process any URL containing .php (we arrive here through previous location block)
# If you don't need to serve any other PHP files besides index.php, use location /index.php here
# instead, to prevent possible execution of user uploaded PHP code
location ~ [^/].php(/|$) {
# Define $fastcgi_script_name and $fastcgi_path_info
fastcgi_split_path_info ^(.+?.php)(/.*)$;
# Immediately return 404 when script file does not exist
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
# Define PHP backend location (find yours by grepping "listen ="
# from your PHP config folder, e.g. grep -r "listen =" /etc/php/)
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
# Set SCRIPT_FILENAME to execute
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Include the default fastcgi parameters
include fastcgi_params;
# Overwrite REQUEST_URI (default is $request_uri) with $request_url we saved earlier
fastcgi_param REQUEST_URI $request_url;
}
}
This configuration works fine for web entry point
If I access webs.example.com/<project_name>
(e.g. webs.example.com/myProject) I get a rendered index page. So request is correctly handled to run /var/www/laravel/<project_name>/public/index.php
file.
However, the page is "broken"
And its broken because all resources (like css and js files, images, …) are in folders /public/resources
and /public/images
and are accessed in code like
<link rel="stylesheet" href="{{ asset('/resources/fontawesome/css/all.css') }}">
<img src="{{ url('/') }}{{$banner->image}}" alt="" class="img-fluid">
Which results to url webs.example.com/resources/fontawesome/css/all.css
and webs.example.com/images/banners/1.jpg
respectively (the $banner->image holds /images/banners/1.jpg)
Which are not found for obvious reason- there is a missing part in the URL, the URL does not contain project name as it should.
So correct URL are (<project_name> is dynamic part of URL, like myProject)
webs.example.com/<project_name>/resources/fontawesome/css/all.css
webs.example.com/<project_name>/images/banners/1.jpg
And as I see it, it may be caused by bad NGINX configuration. Because when I try to hardcode web root path to its /public folder (/var/www/laravel/myProject/public), everything works fine. So I am seeking an option to achieve this by default for all projects in some folder. On Apache, this worked fine
In Apache (from which I am migrating to NGINX and in which everything works fine), there is a simple way how to solve it by adding .htaccess
with simple redirecting everything on root to /public
. NGINX has no such possibility.
To make it more clear, I can pass the default .htaccess content on project root folder (on the same level as public folder) that handles this to public redirection
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^ ^$1 [N]
RewriteCond %{REQUEST_URI} (.w+$) [NC]
RewriteRule ^(.*)$ public/$1
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ server.php
</IfModule>
And then there is another .htaccess right in the /public folder as well
<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews -Indexes
</IfModule>
RewriteEngine On
# Handle Authorization Header
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>
2
Answers
So if I’m understanding correctly, the problem isn’t with your web server at all – it’s the URLs being generated by your Laravel app, which are missing the subdirectory?
First thing you should know is that the approach you’re using to serve multiple apps is not recommended or supported by Laravel.
A much better (and properly supported) solution would be serving from a subdomain, e.g.
https://my-new-project.webs.example.com/
. This could be easily done using the same approach you’re taking now, but using the hostname instead of the path to determine which app to serve. See this Server Fault answer for an example of parsing the hostname components.That said, if everything else has been figured out, you should be able to resolve the issue with generated URLs by simply fixing the
APP_URL
variable in.env
to include the subdirectory.But again, I would strongly discourage this subdirectory approach. It’s fragile and likely to be broken by changes in the Laravel codebase.
Did you try to customize the ASSET_URL for every subdirectory ?
Maybe you could set directly the env variable on top of your index.php files :
$_ENV['ASSET_URL'] = 'https://webs.example.com/my-new-project';
But the best way would be to set it dynamically based on the subdirectory name with
config(['app.asset_url' => config('app.url') . $subdirectory]);