Many developers encounter a common frustration when trying to use variables in Nginx configuration files. While variables work perfectly in some contexts, they behave unexpectedly in others, particularly in directives like root
, error_log
, and access_log
.
The fundamental issue lies in Nginx's processing phases. Variables are evaluated at the request processing stage, while certain directives (like root
) are evaluated during configuration parsing. This temporal mismatch means the variable values aren't available when these directives need them.
# This WON'T work as expected
set $vhost "php";
root /srv/web/vhosts/$vhost/web;
Option 1: Using include files with variables
Create a template config file with variables, then generate the final config:
# template.conf
server_name {VHOST}.domain.com;
root /srv/web/vhosts/{VHOST}/web;
error_log /srv/web/vhosts/{VHOST}/logs/error.log;
access_log /srv/web/vhosts/{VHOST}/logs/access.log;
# Generate with sed (or your preferred method)
sed "s/{VHOST}/php/g" template.conf > live.conf
Option 2: Using map directive
For more complex scenarios, the map directive can help:
map $http_host $vhost_root {
default /srv/web/default;
"~^(?.+)\.domain\.com$" /srv/web/vhosts/$sub/web;
}
server {
server_name ~^(?.+)\.domain\.com$;
root $vhost_root;
}
Option 3: Using environment variables
For Docker or modern deployment scenarios:
env VHOST;
http {
server {
server_name ${VHOST}.domain.com;
root /srv/web/vhosts/${VHOST}/web;
}
}
While dynamic configurations are convenient, they come with performance costs. The map directive adds processing overhead per request. For high-traffic sites, pre-generated configurations with include files often provide better performance.
Always test your nginx configuration after changes:
nginx -t
systemctl reload nginx
Many developers face the challenge of maintaining multiple Nginx configurations where only certain path segments change between environments. Hardcoding these values leads to:
- Error-prone manual updates
- Configuration duplication
- Maintenance headaches
The initial approach of using set $variable
fails because:
# This WON'T work as expected:
set $app "php";
server_name $app.domain.com;
root /srv/web/vhosts/$app/web;
Nginx processes these directives at different configuration phases. Variables set with set
are only available during request processing, not during configuration parsing.
1. Using include with Environment Variables
Create a template file and use environment variables:
# /etc/nginx/conf.d/app_template.conf
server_name ${APP_NAME}.domain.com;
root /srv/web/vhosts/${APP_NAME}/web;
# Then in your main nginx.conf
include /etc/nginx/conf.d/*.conf;
Start Nginx with:
envsubst < /etc/nginx/conf.d/app_template.conf > /etc/nginx/conf.d/app.conf && nginx
2. Map Directive for Multiple Environments
map $host $app_root {
default "/srv/web/vhosts/default/web";
"~^php\." "/srv/web/vhosts/php/web";
"~^staging\." "/srv/web/vhosts/staging/web";
}
server {
server_name ~^(?.+)\.domain\.com$;
root $app_root;
error_log /srv/web/vhosts/$app/logs/error.log;
}
3. Lua Scripting for Advanced Cases
For OpenResty or Nginx with Lua module:
server {
access_by_lua_block {
ngx.var.document_root = "/srv/web/vhosts/" .. ngx.var.host .. "/web"
}
}
While these solutions add flexibility:
- Regex-based maps add slight overhead
- Lua scripting impacts performance more significantly
- Environment variable approach has zero runtime cost
For most use cases, the environment variable approach provides the best balance:
# docker-compose.yml example
services:
nginx:
environment:
- APP_NAME=php
command: >
bash -c "envsubst < /etc/nginx/templates/default.conf.template
> /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"