I’m trying to write an nginx configuration for a change in our application directory structure. I want to be able to support developers who are running both the old version and the new version for the next few years.
In the new version of the application the only real change is that the web accessible code has been moved to a public
directory.
We have a range of PHP files which are accessed directly, and some which are accessed through a router, r.php
. We also have some which are accessed via a javascript endpoint which minifies (and caches) but the files are accessed using URLs in the form http://example.com/oldsite/javascript.php/some/js/file.
Each instance of the application is within a directory, for example:
├── oldsite
│ ├── index.php
│ ├── javascript.php
│ ├── mod
│ │ └── book
│ │ └── index.php
│ └── r.php
└── newsite
└── public
├── index.php
├── javascript.php
├── mod
│ └── book
│ └── index.php
└── r.php
The oldsite is access at http://example.com/oldsite/
and the newsite at http://example.com/newsite/
.
I’m trying (and failing) to write an nginx configuration that will automatically handle both the old and new sites correctly.
This is what I have so far:
set $root /srv/sites
index index.php;
set $site "";
if ($uri ~ /(?<site>[^/]+)(?<relpath>/?.*)) {
set $site $site;
}
set $docroot "$root/$site";
if (-d "/$docroot/public") {
set $docroot "$root/$site/public";
}
location ~ /(?<site>[^/]+)(?<relpath>/.*.php)(/?|$) {
root $docroot;
include /opt/homebrew/etc/nginx/blocks/php.conf;
}
location ~ ^/(?<site>[^/]+)/?$ {
root $docroot;
rewrite ^(.*)/?$ $1/index.php last;
location ~ .php$ {
include /opt/homebrew/etc/nginx/blocks/php.conf;
}
}
location ~ ^/(?<site>[^/]+)/?(?<relpath>.*)$ {
root $docroot;
location ~ .php$ {
include /opt/homebrew/etc/nginx/blocks/php.conf;
}
try_files /$relpath /$relpath/ /r.php;
}
My php.conf looks like this:
fastcgi_split_path_info ^(.+.php)(/.*)$;
# Save path_info before try_files resets it
set $path_info $fastcgi_path_info;
include fastcgi_params;
fastcgi_param PATH_INFO $path_info;
fastcgi_param SCRIPT_FILENAME $docroot$relpath;
fastcgi_param DOCUMENT_ROOT $docroot;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
What is working:
http://example.com/oldsite
(returns /index.php)
http://example.com/oldsite/
(returns /index.php)
http://example.com/oldsite/index.php
http://example.com/oldsite/javascript.php/some/file
http://example.com/oldsite/mod/book/index.php
http://example.com/newsite
(returns /index.php)
http://example.com/newsite/
(returns /index.php)
http://example.com/newsite/index.php
http://example.com/newsite/mod/book/index.php
http://example.com/newsite/javascript.php/some/file
What is not working:
http://example.com/oldsite/notfound
(returns 404) — should use /r.php
http://example.com/oldsite/mod/book
(returns 404) — should use /mod/book/index.php
http://example.com/oldsite/mod/book/
(returns 404) — should use /mod/book/index.php
Digging into the logs, the final location match is used (correctly) but the try_files
directive is not working as I expect.
2025/06/05 09:00:22 [debug] 80884#0: *31 generic phase: 13
2025/06/05 09:00:22 [debug] 80884#0:*31 try files handler
2025/06/05 09:00:22 [debug] 80884#0: *31 http script var: "/srv/sites/newsite/public"
2025/06/05 09:00:22 [debug] 80884#0:*31 http script copy: "/"
2025/06/05 09:00:22 [debug] 80884#0: *31 http script var: "mod/book/"
2025/06/05 09:00:22 [debug] 80884#0:*31 trying to use file: "/mod/book/" "/srv/sites/newsite/public/mod/book/"
2025/06/05 09:00:22 [debug] 80884#0: *31 http script copy: "/"
2025/06/05 09:00:22 [debug] 80884#0:*31 http script var: "mod/book/"
2025/06/05 09:00:22 [debug] 80884#0: *31 trying to use dir: "/mod/book/" "/srv/sites/newsite/public/mod/book/"
2025/06/05 09:00:22 [debug] 80884#0:*31 try file uri: "/mod/book/"
2025/06/05 09:00:22 [debug] 80884#0: *31 generic phase: 14
2025/06/05 09:00:22 [debug] 80884#0:*31 content phase: 15
2025/06/05 09:00:22 [debug] 80884#0: *31 content phase: 16
2025/06/05 09:00:22 [debug] 80884#0:*31 http script var: "/srv/sites/newsite/public"
2025/06/05 09:00:22 [debug] 80884#0: *31 open index "/srv/sites/newsite/public/mod/book/index.php"
2025/06/05 09:00:22 [debug] 80884#0:*31 internal redirect: "/mod/book/index.php?"
I can see that it’s trying to use the directory, which it finds successfully, but then that turns into an internal redirect, which then gets processed from the top and the $site var is turned into mod
, which doesn’t then exist.
Likewise with a fallback URL:
2025/06/05 07:00:29 [debug] 80884#0: *11 try files handler
2025/06/05 07:00:29 [debug] 80884#0: *11 http script var: "/srv/sites/newsite/public"
2025/06/05 07:00:29 [debug] 80884#0: *11 http script copy: "/"
2025/06/05 07:00:29 [debug] 80884#0: *11 http script var: "mod/book/notfound"
2025/06/05 07:00:29 [debug] 80884#0: *11 trying to use file: "/mod/book/notfound" "/srv/sites/newsite/public/mod/book/notfound"
2025/06/05 07:00:29 [debug] 80884#0: *11 http script copy: "/"
2025/06/05 07:00:29 [debug] 80884#0: *11 http script var: "mod/book/notfound"
2025/06/05 07:00:29 [debug] 80884#0: *11 trying to use dir: "/mod/book/notfound" "/srv/sites/newsite/public/mod/book/notfound"
2025/06/05 07:00:29 [debug] 80884#0: *11 trying to use file: "/r.php" "/srv/sites/newsite/public/r.php"
2025/06/05 07:00:29 [debug] 80884#0: *11 internal redirect: "/r.php?"
It’s trying and (correctly) failing to find the file, and then it (correctly) falls back to /r.php
, which it does find… only to redirect in the same way.
I feel like there’s something really obvious that I’m missing. I have thought about using a series of if tests instead of if
tests, but this feels wrong. Another option would be some kind of proxy to an internal server directive I guess?
Thanks