I have a query about the way nginx handles locations when try_files is invoked. I have a docker stack which serves Wordpress and phpmyadmin applications. My config is below:
server {
listen 80;
index index.php;
server_name test.com; # Just a placeholder
root /code;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location /pma {
alias /var/www/html;
index index.php;
try_files $uri $uri/ /index.php;
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_pass phpmyadmin:9000;
}
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
My question is this: Why does the above config work when according to the nginx documentation here it should not work.
When a request for 'http://test.com/pma/' is made I understand the following should happen:
Match /pma location
try_files $uri = FALSE
try_files $uri/ = FALSE
Fallback to /index.php
$uri now equal to: 'http://test.com/pma/index.php'
Processing is now restarted.
Match '.php$' location at the bottom.
It breaks because this is the wrong location and the 'php' container doesn't have access to the phpmyadmin files inside the 'phpmyadmin' container.
However, instead what appears to happen is this:
Match /pma location
try_files $uri = FALSE
try_files $uri/ = FALSE
Fallback to /index.php
$uri now equal to: 'http://test.com/pma/index.php'
Processing continues inside the '/pma' context, which now matches the nested '.php$' location block, which passes the request to FPM listening inside the 'phpmyadmin' container.
I know I should be happy that it works, but it's bugging me when everything I understand about nginx says this shouldn't work.
Obviously I have some fundamental misunderstanding about the way this works, and I'd appreciate it if someone could point me in the right direction.
Thank you.
Solution was found here: https://artfulrobot.uk/blog/untangling-nginx-location-block-matching-algorithm
Specifically this part:
Exact string matches location = /foo
The longest of any location ^~ ... matches
The first regex match that is nested within the single longest matching prefix match! See discussion below.
The first other regex match location ~ regex
The longest prefix match location /foo
Point number '3' is the answer here.
Related
My structure project
- index.php
- abc.php
- folder/
---- def.php
My nginx.conf
server {
listen 80 default_server;
root /var/www/public;
index index.html index.htm index.php;
server_name _;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location /index.php {
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
}
How can I change nginx.conf to use domain/abc for href instead of domain/abc.php
Thanks!
This is commonly called "extensionless PHP", there are many solutions, of which this is just one:
location / {
try_files $uri $uri/ #php;
}
location #php {
try_files $uri.php $uri/index.php /index.php =404;
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
If you want URIs ending in .php to work too, add:
location ~* ^(.*)\.php$ { return 301 $1$is_args$args; }
The high-performance solution is simply specifying the desired location, and map it to the corresponding PHP script.
location = /abc {
include snippets/fastcgi-php.conf;
fastcgi_param SCRIPT_FILENAME $document_root/abc.php;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
This will ensure that /abc is processed by script /abc.php.
If you want to also "hide" access to /abc.php, you can add:
location = /abc.php {
return 404;
}
Why this is fast, is because the exact matching (with equals sign) involves no prefix matching and no regular expression processing.
Moreover we don't need to use try_files (it has performance issues). Specifically, if using the config from the answer by #RichardSmith, it may yield up to 5 unnecessary file existence checks for an arbitrary request, and 3 file existence checks for every request to /abc.
I have an online web server where I have deployed multiple applications and they all have to share the domain, so my nginx config is a bit messy. The last time I changed it, the PHP files didn't load getting the error ("access denied to file") so I had to do some changes, now the config for one of the applications is like this:
location ^~ /vuelos_baratos {
root /home/gonzalo;
try_files $uri $uri/ /$uri/index.php /index.php$is_args$args $uri/index.php =404;
#root /usr/share/nginx/html;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
include /etc/nginx/mime.types;
autoindex on;
}
and the urls of this application are like this:
mydomain.com/vuelos_baratos/index.php
mydomain.com/vuelos_baratos/style.css
...
and so on.
And the PHP files and everything is working fine, except that If I try to acces:
mydomain.com/vuelos_baratos/image.png
I get the binary data of the image, I discovered that that is because the headers for all files under "vuelos_baratos" are set to type: text/html
How can I fix this?
I don't think that nginx is responsible for determining the content type in your current configuration, as you pass everything to php-fpm.
You could divide the static and dynamic sections into separate locations, for example:
location ^~ /vuelos_baratos {
include /etc/nginx/mime.types;
root /home/gonzalo;
index index.php;
try_files $uri $uri/ /index.php$is_args$args;
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
}
Which should mean that /vuelos_baratos/image.png is processed by nginx and that /etc/nginx/mime.types is used to determine the Content Type.
I have my nginx home at /opt/nginx, inside there are site1 and mail folders, site1 has html folder that is a wordpress installation and mail is a webmail site, both of them must be proxied to php-fpm, site1/html works like a charm no problem at all.
I have the domain1.com and my server delivers site1/html content when domain1.com is requested.
What I want to do is, when domain1.com/mail is requested, serve the content of mail folder (sibling of site1). If I left a index.html file inside mail, when domail1.com/mail is requested, index.html is served to the client without problem but if I try to deliver mail/index.php, 404 error rise instead, what am I doing wrong? below my config:
/etc/nginx/conf.d/domain1.com.conf
server {
.
.
.
root /opt/nginx/site1/html;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location /mail {
root /opt/nginx/;
try_files $uri $uri/mail mail/index.php;
}
location ~ [^/]\.php(/|$) {
# SECURITY : Zero day Exploit Protection
try_files $uri =404;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
}
You can use nested location blocks to invoke PHP scripts which are in a different document root.
Like this:
root /opt/nginx/site1/html;
index index.html index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ^~ /mail {
root /opt/nginx;
try_files $uri $uri/ /mail/index.php;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php-fpm.sock;
include fastcgi_params;
}
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php-fpm.sock;
include fastcgi_params;
}
Notice the ^~ modifier which allows the nested PHP block to take precedence over the outer PHP block. I also removed the path info code which was not being used.
I am trying to achieve the following result with an nginx configuration:
A PHP app is running in a subdirectory of a server, lets say
server.com/app/. Files in images/ and styles/ (for example) should be accessible, php files in api/ should be executed, and in all other cases nginx should pass the whole string after app/ to PHP as a GET variable, say path.
I really have no clue what I am doing here, and I can not seem to find anything useful for this on the web, so if you can chip in, thank you.
I am running php5-fpm currently like this:
location /app {
index index.html index.php;
access_log /{...}/access.log;
error_log /{...}/error.log;
location ~ \.php {
try_files $uri = 404;
fastcgi_pass php5-fpm-sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Please ask if you need any more details.
EDIT
For now I found that this works
location /{path}/ {
index index.php;
access_log /{path}/access.log;
error_log /{path}/error.log;
location ~\.php {
try_files $uri = 404;
fastcgi_pass php5-fpm-sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ {
try_files $uri $uri/ /{path}/index.php?path=$uri;
}
}
However I am worried that this might allow unwanted file access. Any comments?
You can probably simplify it by moving the try_files directive out of the location sub-block so that your config file ends up looking like:
location /app {
index index.php;
try_files $uri $uri/ /app/index.php?path=$uri;
access_log /{path}/access.log;
error_log /{path}/error.log;
location ~\.php {
try_files $uri =404;
fastcgi_pass php5-fpm-sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
The key thing is the try_files directive - nginx will try each location in the order specified. $uri looks for a file matching the exact path specified (so /api/random.php loads correctly because it's a file), $uri/ looks for a folder matching the path, and attempts to load the index from the folder, and finally /app/index.php?path=$uri loads the page /app/index.php. This is then picked up by the location ~\.php block and passed to php-fpm.
The main thing I'd be concerned about is that your access and error.log files would be publicly accessible by virtue of being stored in the web directory. If possible, shift them somewhere else (like /var/log maybe?)
I'm using nginx with PHP-FPM on a ISPConfig3 server.
I put the following rewrite-rule in my nginx-directives (to make prettier links in Pydio):
location ~ \.php$ {
try_files #php;
}
location #php {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass 127.0.0.1:9026;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
}
proxy_set_header X-Accel-Mapping /var/www/XXXYYY.com/pydio/data/=/data/;
location /conf/ { deny all; }
location /data/ { internal; }
location /data/public/ {
try_files $uri $uri.php =404 last;
}
I want URLs in pydio.XXXYYY.com/data/public/* to have a .php-extension added.
This rule finds the files without the .php in the address bar but now they are downloaded instead of executed.
Since I use ISPConfig3, the rewrites for .php-files (to have them executed by PHP-FPM) is above the stated part. But I thought adding "last" should take care of that.
What else could I try?
Thank you!
First of all, you misunderstand the try_files directive. There's no "last" argument and it doesn't work like you think. Please, check the documentation: http://nginx.org/r/try_files. It's technical documentation, read it literally, every word has meaning.
To solve your problem you have to remove two last arguments from try_files:
try_files $uri $uri.php =404 last;
should be replaced with:
try_files $uri $uri.php;