Multi-tenant nginx server
Matching and capturing multiple domains in one immutable nginx config - how to and examples!
Context
In a multi-teanent scenario it is desireable to have different domains for different customers.
Often, nginx is the entry to your SaaS or PaaS application. In such scneario one nginx server needs to service all customers.
It is hard to create a new config per customer, in particular from an automated fashion; not only is it potentially insecure (an automated api needs to edit nginx config files!), but also not scaleable (the nginx server needs to reload the config after each edit).
Solution
It is therefore much better to have a generic nginx config that resolves for all customers. The magic ingredient is called capture group and hgas the following syntax: (?<variable>.+)
. Here variable
will become a variable available in the current context that holds the contents of the matched regex (regex here being .+
).
In the following there are different example configuration that for exactly above scenario.
server {
server_name ~^(www\.)?(?<tenant>[a-zA-Z0-9]+)\.mysaas\.com$;
root /var/$tenant/www;
.....
}
server {
server_name ~^(www\.)?(?<tenant>[a-zA-Z0-9]+)\.mysaas\.com$;
location / {
proxy_pass http://127.0.0.1:8080/$tenant;
....
}
....
}
server {
server_name ~^(www\.)?(?<tenant>[a-zA-Z0-9]+)\.mysaas\.com$;
location / {
proxy_set_header mysaas-tenant $tenant;
proxy_pass http://127.0.0.1:8080/;
....
}
....
}
server {
server_name ~^(www\.)?(?<tenant>[a-zA-Z0-9]+)\.mysaas\.com$;
location / {
fastcgi_pass unix:/var/run/php-fpm-www.sock;
fastcgi_param SCRIPT_FILENAME $document_root/$tenant/mysaas.php;
.....
}
....
}
All above scenarios match www.<anything>.mysaas.com
(www
is optional) where <anything>
matches any number of alphanumeric characters. (If you wanted it to match any character, you could use (?.+)
but be warned about path traversal attacks, dns wildcards not match subdomains and similar!)
Notes
If you are worried about the regex capturing too much, e.g. because you only want to match domains you have not explicit specified, don't worry. In such "catch-all" scenario, nginx will smartly choose "the right one". In more formal terms, if more than one server-name matches, the following rules apply in precedence:
- exact name
- wildcard match starting with star (longest match wins)
- wildcard match ending with star (longest match wins)
- regular expression match (in order of appearance in a config)
This means, it is perfectly fine to have a config as below.
server {
server_name ~^(www\.)?admin\.mysaas\.com$;
.... serve the admin app ....
}
server {
server_name ~^(www\.)?(?<tenant>[a-zA-Z0-9]+)\.mysaas\.com$;
root /var/$tenant/www;
..... catch all .... everything that is not admin ....
}