using vhost templates - file

I use nginx on Debian. So besides the main configuration file /etc/nginx/nginx.conf there are folders /etc/nginx/sites-available/ with the vhost config files and /etc/nginx/sites-enabled/ with the links to active vhosts.
So let me ask my question first. Because the explanation is long and maybe you don't need to read it...
I want to be able to use several vhost templates like this:
server {
listen 80;
server_name ~^(?<project>.+)\.(?<area>.+)\.loc$;
if ($host ~ ^(?<project>.+)\.(?<area>.+)\.loc$) {
set $folder "$area/$project";
}
...
access_log /var/log/nginx/$area/$project.access.log;
error_log /var/log/nginx/error.log;
...
root /var/www/$folder/;
...
}
and to define which vhost is based on which template. Furthermore it would be great to have a possibility to "extend" the template, so that I can add new settings to my vhosts and redefine the settings inherited from the template.
How can I achieve it?
My current vhost file structure looks like this:
/etc/nginx/sites-available contains following files:
default (default vhost) ax-common-vhost (vhost template)
test.sandbox.loc (vhost based on the template ax-common-vhost; it
includes thatwith the include rule) ...and some further ones...
/etc/nginx/sites-available contains following files:
default -> /etc/nginx/sites-available/default test.sandbox.loc ->
/etc/nginx/sites-available/test.sandbox.loc ...and some further
ones...
The template ax-common-vhost defines some options like root folder dynamically, using the server name:
server {
listen 80;
server_name ~^(?<project>.+)\.(?<area>.+)\.loc$;
if ($host ~ ^(?<project>.+)\.(?<area>.+)\.loc$) {
set $folder "$area/$project";
}
...
access_log /var/log/nginx/$area/$project.access.log;
error_log /var/log/nginx/error.log;
...
root /var/www/$folder/;
...
}
So when I want to create a new vhost, I just create a new vhost file and a link to it -- and don't need to copy&paste the settings and to set the paths manually. I just need to follow the convention, that a host %project%.%area%.loc hast to be placed in [my webroot]/%area%/%project%
I thought, it works over the include rule: The server gets a request x.y.loc, looks for a file named so, openes the file, and finally processes the directives in it (so the include directive and the the whole content of the included template).
But it's not so. Nginx seems just to scan the whole folder /etc/nginx/sites-available/ (alphabetically?) and to hold on the first file / server directive the host name in the request equals/maches to.
That means, the include
include /etc/nginx/sites-available/ax-common-vhost;
is not used. Actually, I've removed the include directives from the vhost files -- and nothing has changed!
And it's a problem. Because when I add a new template, e.g. for my Zend Framework projects (with [project root]/public/ as root):
file ax-zf-vhost
server {
listen 80;
server_name ~^(?<project>.+)\.(?<area>.+)\.loc$;
if ($host ~ ^(?<project>.+)\.(?<area>.+)\.loc$) {
set $folder "$area/$project";
}
...
access_log /var/log/nginx/$area/$project.access.log;
error_log /var/log/nginx/error.log;
...
root /var/www/$folder/public/;
...
}
..., it is ignored, since the server doesn't get any information about, that the vhost myzf.sandbox.loc is based on ax-zf-vhost. Instead of this it just loops the /etc/nginx/sites-available/ folder, finds ax-common-vhost, myzf.sandbox.loc maches to the pattern ^(?.+).(?.+).loc$, and nginx uses ax-common-vhost for myzf.sandbox.loc.
How can this problem be solved?
Thanks

OK, it workz now!
I've changed the server_name block of my basic vhost file, so it porecces only the request with the hostnames listed in the directive:
file ax-common-vhost:
server {
listen 80;
server_name test.sandbox.loc foo.sandbox.loc bar.sandbox.loc;
...
}
instead of the generic server_name ~^(?<project>.+)\.(?<area>.+)\.loc$;.
The vhost files test.sandbox.loc, foo.sandbox.loc, bar.sandbox.loc etc. in /etc/nginx/sites-available/ and the links to them in /etc/nginx/sites-enabled/ are not needed anymore. I've created a link /etc/nginx/sites-enabled/ax-common-vhost to /etc/nginx/sites-available/ax-common-vhost instead.
The approach for the second common vhost file is the same and it works.
The first problem is resolved: The settings can be shared by several vhosts / server blocks and I can easily add new vhosts without duplicating the settings.
But: The vhost file with the settings cannot be extended by another file. Is it possible to do this, so that a file B can "extend" a file A, inherit its settings and overwrite only directives/rules? How can I realize that?

Related

Is there a way to configure Apache to auto-recognize URLs for Django vs React without hard-coding each endpoint?

We're using Apache 2.4 with React 17 and a Django 3.2 (Python 3.9) application. Curious about a better way to set up our Apache configs to route requests to the React and Django apps. Right now, our Apache virtual hosts file hard-codes which routes need to be handled by the React app vs which need to be handled by Django ...
AliasMatch ^/(?!people)(?!states/)(?!countries/)(?!predefined_types/)(?!coop_types/)(?!coops/)(?!data)(?!save_to_sheet_from_form).* /var/www/html/client/build/$0
<Directory "/var/www/html/client/build/">
Options Indexes FollowSymLinks
AllowOverride all
</Directory>
WSGIDaemonProcess ssl_directory home=/var/www/html/web python-home=/var/www/html/web/venv
WSGIProcessGroup ssl_directory
WSGIScriptAlias /coops /var/www/html/web/directory/wsgi.py/coops process-group=ssl_directory
WSGIScriptAlias /data /var/www/html/web/directory/wsgi.py/data process-group=ssl_directory
WSGIScriptAlias /countries /var/www/html/web/directory/wsgi.py/countries process-group=ssl_directory
WSGIScriptAlias /states /var/www/html/web/directory/wsgi.py/states process-group=ssl_directory
WSGIScriptAlias /predefined_types /var/www/html/web/directory/wsgi.py/predefined_types process-group=ssl_directory
WSGIScriptAlias /coop_types /var/www/html/web/directory/wsgi.py/coop_types process-group=ssl_directory
WSGIScriptAlias /people /var/www/html/web/directory/wsgi.py/people process-group=ssl_directory
WSGIScriptAlias /save_to_sheet_from_form /var/www/html/web/directory/wsgi.py/save_to_sheet_from_form process-group=ssl_directory
The Django app, for its part, defines urls in the standard way (in our urls.py file) ...
...
urlpatterns = [
path('data', views.data, name='data'),
path('coops/no_coords', views.coops_wo_coordinates, name='coops_wo_coordinates'),
path('coops/unapproved', views.unapproved_coops, name='unapproved_coops'),
path('coops/', views.CoopList.as_view()),
path('coops/<int:pk>/', views.CoopDetail.as_view()),
path('people/', views.PersonList.as_view()),
path('people/<int:pk>/', views.PersonDetail.as_view()),
path('users/', views.CreateUserView.as_view()),
path('predefined_types/', views.CoopTypeList.as_view()),
path('coop_types/', views.CoopTypeList.as_view()),
path('countries/', views.CountryList.as_view()),
path('states/<country_code>', views.StateList.as_view()),
path('login', views.signin),
path(settings.LOGOUT_PATH, views.signout),
path('user_info', views.user_info),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Is there a more automated way we can get Apache to know what routes shoudl go to Django vs React? Whenever we add a new Django endpoint, we have to add a hard-coded exception in our Apache configs.
Edit: Here is an example how I make a React call to the API ...
const { REACT_APP_PROXY } = process.env;
class PersonService {
getById(id, callback) {
fetch(REACT_APP_PROXY + "/people/" + id)
.then((response) => {
return response.json();
})
.then((data) => {
const person = data;
person.contact_methods.map((contact_method) => {
if (contact_method.type == "PHONE") {
person.phone = contact_method.phone.substring(2);
} else if (contact_method.type == "EMAIL") {
person.email = contact_method.email;
}
});
if (callback) callback(person);
});
}
}
Concerning python there are two wsgi-options that could help you to reduce the required lines and steps.
Using the WSGIScriptAlias directive to map to a directory containing
any number of WSGI applications:
WSGIScriptAlias /wsgi/ /usr/local/wsgi/scripts/
When this is used, the next part of the URL after the URL prefix is
used to identify which WSGI application script file within the target
directory should be used. Both the mount point and the directory path
must have a trailing slash.
Another option is intended primarily to suppress the extension in the
Frontend but could be used too to redirect based on different aspects:
WSGIScriptAliasMatch ^/wsgi/([^/]+) /usr/local/wsgi/scripts/$1.wsgi
In this case, any path information appearing after the URL prefix, will be mapped to a corresponding WSGI script file in the directory, but with a ‘.wsgi’ extension. The extension would though not need to be included in the URL.
The options can be configured with common Apache directives too, and there are additional features like directory index can be configured like that, it's useful to read this page about all the options: https://modwsgi.readthedocs.io/en/master/user-guides/configuration-guidelines.html
Furthermore you've the option to use the powerful mod_rewrite. Certainly there are many more options, most important is that to distinguish between script-types you've to have some aspect in the url or path to decide where to redirect. It can be a suffix and / or usage of WSGIScriptAlias which both would be most simple but could be based on mod_mime perhaps too.
So actually the question in return is how far you're willing to use suffixes or URLs that make a distinction possible and easy enough to be handled by the offered options.
One aspect to consider is that a python app can reference js-files too, so simply a suffix might not be enough but if full paths are provided that's perhaps not too difficult to handle.
With mod_rewrite you have full control over redirection and remapping in Apache
Redirecting and Remapping with mod_rewrite
You should implement something like this:
RedirectMatch "^/docs/(.*)" "http://new.example.com/docs/$1"
or
RewriteRule ^images/([^/]+)$ /assets/images/public/$1 [L]
That redirects each part of application documents to its corresponding handler.
RedirectMatch "^/react-docs/(.*)" "http://react-handler.example.com/react-docs/$1"
As you see you need something in your URL that RedirectMatch or RewriteRule uses Regex on it for separating and sending each document to corresponding handler.
Q&As on mod_rewrite

Nginx config to serve react app from sub-folder

I have a create-react-app that I built, created npm run dist and copied the resulting dist files to /var/www/html/build/
Here is my nginx config:
server{
listen 9095;
server_name <hostname>;
access_log /var/log/nginx/nginx.pass.access.log;
error_log /var/log/nginx/nginx.pass.error.log;
root /var/www/html/build/;
location / {
index index.html;
try_files $uri $uri/ /index.html;
}
}
With this, I can access http://hostname:9095.
What I actually need is to serve the same site with http://hostname:9095/pass (or /PASS) (and not /)
I have tried various re-write config but I can't seem to figure this out.. Help appreciated
The format of a rewrite expression is rewrite <regex> <replacement> [flag];. The simplest way to redirect /pass or /PASS to your /var/www/html/build/index.html page is as follows.
location ~* ^/pass$ {
rewrite ^/(.*)$ /index.html break;
root /var/www/html/build;
index index.html;
}
The location ~* ^/pass directive is a case-insensitive regular expression match against the /pass URI. The ^ symbol ensures the regex matches only the /pass uri, and not something like /some/dir/pass, while the $ symbol ensures a url like /password is not matched as well.
Note, however, that this regex will match /pAsS, /PAss, and /pasS. In other words, it really is case-insensitive. If you wish to match only /pass or /PASS, you should modify the regex accordingly.
Better yet, you can completely circumvent regex URI matching by using an exact URI match.
location = /pass {
...
}
Using an exact match like this speeds up request processing because regular expressions do not need to be parsed.
To match /PASS, you could then simply define another exact location match block.
location = /PASS {
...
}
This then frees up your main location block for your primary server content.
location / {
root /var/somewhere/else;
index index.html;
}
Edit: After writing this, I came across the alias directive, which also seems promising. I haven't used it myself (I've always used rewrites for this kind of thing), but it's something else to explore as well.

Linux - client_body_in_file_only - how to set file permissions for the temp file?

We use the client_body_in_file_only option with nginx, to allow file upload via Ajax. The config looks like this:
location ~ ^(\/path1|\path2)$ {
limit_except POST { deny all; }
client_body_temp_path /path/to/app/tmp;
client_body_in_file_only on;
client_body_buffer_size 128K;
client_max_body_size 1000M;
#this option is a quick hack to make sure files get saved on (ie this type of request goes to) on a specific server
proxy_pass http://admin;
proxy_pass_request_headers on;
proxy_set_header X-FILE $request_body_file;
proxy_set_body off;
proxy_redirect off;
# might not need?
proxy_read_timeout 3m;
}
This works, but the web server process (Mongrel) that handles the request has to sudo the temp file that comes through in headers['X-FILE'], before it can do anything with it. This is because the temp file comes through with 600 permissions.
I'm not happy with this approach, which requires us to edit the /etc/sudoers file to allow the web server user to do sudo chmod without a password. It feels very unsecure.
Is there a way, with the nginx config, to change the permissions on the temp file that is created, eg to 775?
EDIT: I just tried changing the value of the umask option in the nginx init config, then restarting nginx, but it didn't help. It had been at 0022, I changed it to 0002. In both cases it comes through with 600 permissions.
EDIT2: I also tried adding this line under the proxy_redirect line, in the nginx config.
proxy_store_access user:rw group:rw all:r;
But, it didn't make any difference - it still just has user:rw
Looking through the nginx source, it appears that the only mechanism that would modify the permissions of the temporary file is the request_body_file_group_access property of the request, which is consulted in ngx_http_write_request_body():
if (r->request_body_file_group_access) {
tf->access = 0660;
}
But even that limits you to 0660 and it seems that it is not a user-settable property, only being utilized by the ngx_http_dav module.
The permissions are ultimately set in ngx_open_tempfile(), where they default to 0600:
fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR, access ? access : 0600);
So it seems that there is currently no configuration-based solution. If you're willing/able to build nginx from source, one possibility is to apply a simple patch to set the permissions to whatever you want in ngx_http_write_request_body():
+ tf->access = 0644;
+
if (r->request_body_file_group_access) {
tf->access = 0660;
}
rb->temp_file = tf;
I tested this and obtained the following, the first file having been uploaded without the modification, and the second file with it:
$ ls -al /tmp/upload/
total 984
drwxr-xr-x 2 nobody root 12288 Feb 18 13:42 .
drwxrwxrwt 16 root root 12288 Feb 18 14:24 ..
-rw------- 1 nobody nogroup 490667 Feb 18 13:40 0000000001
-rw-r--r-- 1 nobody nogroup 490667 Feb 18 13:42 0063184684
It seems, that it is not possible at the moment to configure the file permissions, but there is an official feature request.
The file permissions are always 0600 making the application unable to read the file at all. [...] This is currently an unsupported scenario: [Nginx] creates the temporary file with the default permissions [...] 0600 (unless request_body_file_group_access is set - but unfortunately that property is not settable).
The ticket was opened in October 2018 with minor priority.

Moving Cakephp 3.x application into nginx subfolder

System
nginx on Ubuntu
CakePhp 3.4.6
Problem
I'm trying to move a CakePhp application into a subfolder on the webserver.
Let's say the subfolder is called /project
and root webserver directory is under /var/www/html/
I tried something like this in the nginx default (without any success):
location /project {
alias /var/www/html/project/webroot;
try_files $uri $uri/ /project/webroot/index.php;
}
The subfolder url-suffix is always interpreted as a controller.
I am guessing that I need to set specific values in the app.php file, so cakephp knows that the url has a prefix but I haven't found a value combination that works yet.
Anybody has guidance here?
It is a combination of app.php and nginx rewrite rule:
config.php/app.php
'base' => '/project'
/etc/nginx/sites-available/default
`
location /project {
if (-f $request_filename) {
break;
}
# prevent recursion
if ($request_uri ~ /webroot/index.php) {
break;
}
rewrite ^/project$ /project/ permanent;
rewrite ^/project/webroot/(.*) /project/webroot/index.php last;
rewrite ^/project/(.*)$ /project/webroot/$1 last;
}
`

Configure nginx to statically cache certain URLs

I want to cache certain URLs in my app by looking up static files directly. However, the statically cached files may not exist, so I only want to redirect the request if the files exist. I imagine I can use try_files for this somehow, but so far I have not been successful in doing it right.
Here's what I tried:
location ^/(.*)/(.*)/other/stuff/(.*)/this-is-static {
try_files /static-cache/$1/$2/$3/this-is-static #app;
}
static-cache is an internal location configured in the same file. This does not appear to work as intended. What's wrong?
The closest I've gotten is this, which rewrites every request (and thus fails when the file is not cached):
location ^/(.*)/(.*)/other/stuff/(.*)/this-is-static {
rewrite ^ /static-cache/$1/$2/$3/this-is-static;
}
Did you try the ~?, I also used [^/] to avoid greedy regex
location ~ /([^/]*)/([^/]*)/other/stuff/([^/]*)/this-is-static {
try_files /static-cache/$1/$2/$3/this-is-static #app;
}
location #app {
#handle non cached files
}

Resources