How to Use Nginx Variables in Location Blocks for Dynamic Configuration


2 views

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;'"