Correct way in new versions of nginx
Turn out my first answer to this question was correct at certain time, but it turned into another pitfall - to stay up to date please check Taxing rewrite pitfalls
I have been corrected by many SE users, so the credit goes to them, but more importantly, here is the correct code:
server {
listen 80;
server_name my.domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name my.domain.com;
# add Strict-Transport-Security to prevent man in the middle attacks
add_header Strict-Transport-Security "max-age=31536000" always;
[....]
}
There are a few things wrong with your config, the two relevant ones being:
- Paths within a location block still include the matched path.
- Rewrites with 'last' continue by looking through all available locations for a match (they break out of the current location block).
For instance, take the URL example.org/news/test.htm
- The
location /news
block will match it
- The path used is then
/news/test.htm
- this does not change, just because it is in the location block
- Adding the path to the document_root, you get:
/var/www/vhosts/news/news/test.htm
- Your
if (!-e $request_filename)
statement should capture this non-existent file
- You rewrite the path to
/index.php
- Since you are using
last
the processes starts over (breaking out of the location block)
/index.php
is now captured by the location /app block
.
The problem mentioned above, with the root directive, is compounded when you go to your app location block. Unlike with the 'news' block, where you could conceivably just remove 'news' from the path (since it will be added back in), you cannot do this for the app path, which ends in 'webroot'.
The solution lies in the alias
directive. This does not change the document_root, but it does change the file path that is used to serve the request. Unfortunately, rewrite
and try_files
tend to behave a bit unexpectedly with alias
.
Let's start with a simple example - no PHP - just HTML and your Perl block - but with a folder structure matching yours (tested on Nginx 1.0.12, CentOS 6):
server {
server_name example.org;
error_log /var/log/nginx/example.org.error.log notice;
access_log /var/log/nginx/example.org.access.log;
rewrite_log on;
location = / {
rewrite ^ /index.pl last;
}
location ^~ /community {
rewrite ^ /index.pl last;
}
location ~ \.pl {
root /var/www/vhosts/home;
[fastcgi_stuff...]
}
location ^~ /news {
alias /var/www/vhosts/news;
index index.htm;
try_files $uri $uri/ /news/index.htm;
}
location ^~ /app {
alias /var/www/vhosts/app/app/webroot;
index index.htm;
try_files $uri $uri/ /app/index.htm;
}
location / {
rewrite ^/(.*) /app/$1 last;
}
}
location = /
- will only match the root path
location ^~ /community
- will match every path starting with /community
location ~ \.pl
- will match all files that contain .pl
location ^~ /news
- will match every path starting with /news
location ^~ /app
- will match every path starting with /app
location /
- will match all paths not matched above
You should be able to remove the ^~
- but it may offer a slight performance improvement, since it stops searching once a match is found.
While it should be a simple matter to add the PHP blocks back in, there is, unfortunately, a slight difficulty - try_files
(and your rewrite) do not end up passing the desired path to the nested location block - and using alias
when only the extension is specified in the location block doesn't work.
One solution is to use separate location blocks that perform a capture together with the alias directive - it isn't quite elegant, but as far as I can tell, it does work (again, tested on Nginx 1.0.12, CentOS 6 - of course, I didn't setup CakePHP, Wordpress, and Perl - I just used a couple of PHP and HTML files in each folder)
server {
server_name example.org;
error_log /var/log/nginx/example.org.error.log notice;
access_log /var/log/nginx/example.org.access.log;
rewrite_log on;
location = / {
rewrite ^ /index.pl last;
}
location ^~ /community {
rewrite ^ /index.pl last;
}
location ~ \.pl {
root /var/www/vhosts/home;
access_log /var/log/nginx/home.access.log;
error_log /var/log/nginx/home.error.log;
include /etc/nginx/fastcgi_params;
fastcgi_index index.pl;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
location /news {
access_log /var/log/nginx/news.access.log;
error_log /var/log/nginx/news.error.log notice;
alias /var/www/vhosts/news;
index index.php;
try_files $uri $uri/ /news/index.php;
}
location ~* ^/news/(.*\.php)$ {
access_log /var/log/nginx/news.php.access.log;
error_log /var/log/nginx/news.php.error.log notice;
alias /var/www/vhosts/news/$1;
try_files "" /news/index.php;
include /etc/nginx/fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_NAME $1;
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/news/$1;
fastcgi_pass 127.0.0.1:9000;
}
location /app {
alias /var/www/vhosts/app/app/webroot;
access_log /var/log/nginx/app.access.log;
error_log /var/log/nginx/app.error.log notice;
index index.php;
try_files $uri $uri/ /app/index.php;
}
location ~* ^/app/(.*\.php)$ {
access_log /var/log/nginx/news.access.log;
error_log /var/log/nginx/news.error.log notice;
alias /var/www/vhosts/app/app/webroot/$1;
try_files "" /app/index.php;
include /etc/nginx/fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_NAME $1;
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/app/app/webroot/$1;
fastcgi_pass 127.0.0.1:9000;
}
location / {
rewrite ^/(.*) /app/$1 last;
}
}
The above config, takes the simple one above, and makes two changes:
- Add two location blocks:
location ~* ^/news/(.*\.php)$
- will match all files ending in .php, with paths starting with /news/
location ~* ^/app/(.*\.php)$
- will match all files ending in .php, with paths starting with /app/
- Remove the
^~
matching - this is required so that the two added location blocks can match against paths (otherwise matching would stop on the /news or /app blocks).
It should be noted that the order for location matching is very important here:
- Exact matches first (using
=
)
- Matches with
^~
second
- Matching regex blocks
- Conventional strings - only if no matching regex is found
A matching regex will supersede a straight string!
An important point of mention is that when captures are used with alias, the entire URL is replaced - not just the leading folder. Unfortunately, this means that $fastcgi_script_name
is left empty - so, I have used $1
above instead.
I am sure you will need to make a few changes, but the basic premise should be functional. You should be able to separate the blocks into multiple files as needed - ordering shouldn't affect the config.
Best Answer
Replaced
*
with\S+
was all I needed. (found what I needed in another exchange)