When migrating monolithic applications to microservices while maintaining legacy URL structures, Nginx location priority becomes critical. The specific pain point emerges when you need:
- Explicit paths (/ and /community) handled by Perl scripts
- /news route dedicated to WordPress
- CakePHP as universal fallback
The current approach using location ~ .*
as catch-all in the CakePHP config causes it to hijack all requests, including /news. This happens because:
# Problematic pattern - too greedy
location ~ .* {
# This will match EVERY request
}
Here's the corrected configuration structure that respects proper matching order:
# /etc/nginx/sites-enabled/example.org
server {
listen 80;
server_name example.org;
# Process most specific matches first
location = / {
include /etc/nginx/subsites-enabled/example.org/home;
}
location ^~ /community {
include /etc/nginx/subsites-enabled/example.org/home;
}
location ^~ /news {
include /etc/nginx/subsites-enabled/example.org/news;
}
# Fallback (must come LAST)
location / {
include /etc/nginx/subsites-enabled/example.org/app;
}
}
1. Match priority rules:
=
(exact match) has highest priority^~
(prefix match) comes next- Regular expressions (
~
) in order of declaration
2. Static assets protection:
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 365d;
add_header Cache-Control "public";
try_files $uri =404;
}
Add this to your server block to see matching behavior:
location /nginx-match-test {
default_type text/plain;
return 200 "Matched: $uri\nLocation: $request_uri\n";
}
Test patterns using:
curl -I http://example.org/nginx-match-test/community/subpage
Here's the complete optimized configuration for the home subsystem:
# /etc/nginx/subsites-enabled/example.org/home
location ~ ^/(|community(/.*)?)$ {
access_log /var/log/nginx/home/access.log;
try_files $uri @perlhandler;
location ~ \.pl$ {
internal;
include fastcgi_params;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
fastcgi_param SCRIPT_FILENAME /var/www/vhosts/home$fastcgi_script_name;
}
}
location @perlhandler {
rewrite ^ /index.pl last;
}
Always place static asset handling before dynamic content rules:
location ~* \.(?:css|js|map|jpe?g|png|gif|ico)$ {
root /var/www/vhosts/app/app/webroot;
access_log off;
expires 30d;
}
When migrating from a monolithic repository to microservices while maintaining URL structures, Nginx location priority becomes critical. The specific requirement where one application must serve as a fallback for unmatched routes introduces complex routing logic.
The existing setup has three key location blocks across separate files:
# Home application (Perl)
location = / { ... }
location ~* /community(.*) { ... }
# News application (WordPress)
location /news { ... }
# Fallback application (CakePHP)
location ~ .* { ... }
The greedy regex pattern ~ .*
in the fallback application catches all requests, including those meant for /news
. Nginx evaluates locations in this order:
- Exact matches (=)
- Prefix matches (^~)
- Regular expressions (~ and ~*) in config file order
- Generic prefix matches
We need to:
# 1. Make home locations explicit
location = / { ... }
location ^~ /community { ... }
# 2. Prioritize news before fallback
location ^~ /news {
# WordPress specific rules
try_files $uri $uri/ /news/index.php?$args;
}
# 3. Revised fallback with negative lookahead
location / {
# Exclude already handled paths
if ($request_uri ~ ^/(news|community)) {
return 404;
}
# CakePHP rewrite rules
try_files $uri $uri/ /index.php?$args;
}
For more complex scenarios, consider:
# Option 1: Map-based routing
map $request_uri $app_root {
default "/var/www/vhosts/app";
~^/news "/var/www/vhosts/news";
~^/community "/var/www/vhosts/home";
}
# Option 2: Named locations
location @fallback {
# CakePHP processing
}
location / {
try_files $uri @fallback;
}
Add these directives to monitor routing decisions:
log_format routing_debug '$remote_addr - $request [$location]';
set $location "default";
access_log /var/log/nginx/routing.log routing_debug;
location /news {
set $location "news";
...
}