Using variables in Nginx location rules

VariablesNginxWebserver

Variables Problem Overview


In Nginx, I'm trying to define a variable which allows me to configure a sub-folder for all my location blocks. I did this:

set $folder '/test';

location $folder/ {
   [...]
}

location $folder/something {
   [...]
}

Unfortunately, this doesn't seem to work. While Nginx doesn't complain about the syntax, it returns a 404 when requesting /test/. If I write the folder in explicitly, it works. So how can I use variables in location blocks?

Variables Solutions


Solution 1 - Variables

You can't. Nginx doesn't really support variables in config files, and its developers mock everyone who ask for this feature to be added:

> "[Variables] are rather costly compared to plain static configuration. [A] macro expansion and "include" directives should be used [with] e.g. sed + make or any other common template mechanism." http://nginx.org/en/docs/faq/variables_in_config.html

You should either write or download a little tool that will allow you to generate config files from placeholder config files.

Update The code below still works, but I've wrapped it all up into a small PHP program/library called Configurator also on Packagist, which allows easy generation of nginx/php-fpm etc config files, from templates and various forms of config data.

e.g. my nginx source config file looks like this:

location  / {
	try_files $uri /routing.php?$args;
    fastcgi_pass   unix:%phpfpm.socket%/php-fpm-www.sock;
    include       %mysite.root.directory%/conf/fastcgi.conf;
}

And then I have a config file with the variables defined:

phpfpm.socket=/var/run/php-fpm.socket
mysite.root.directory=/home/mysite

And then I generate the actual config file using that. It looks like you're a Python guy, so a PHP based example may not help you, but for anyone else who does use PHP:

<?php

require_once('path.php');

$filesToGenerate = array(
	'conf/nginx.conf' => 'autogen/nginx.conf',
	'conf/mysite.nginx.conf' => 'autogen/mysite.nginx.conf',
	'conf/mysite.php-fpm.conf' => 'autogen/mysite.php-fpm.conf',
	'conf/my.cnf' => 'autogen/my.cnf',
);

$environment = 'amazonec2';

if ($argc >= 2){
	$environmentRequired = $argv[1];

	$allowedVars = array(
		'amazonec2',
		'macports',
	);

	if (in_array($environmentRequired, $allowedVars) == true){
		$environment = $environmentRequired;
	}
}
else{
	echo "Defaulting to [".$environment."] environment";
}

$config = getConfigForEnvironment($environment);

foreach($filesToGenerate as $inputFilename => $outputFilename){
	generateConfigFile(PATH_TO_ROOT.$inputFilename, PATH_TO_ROOT.$outputFilename, $config);
}


function	getConfigForEnvironment($environment){
	$config = parse_ini_file(PATH_TO_ROOT."conf/deployConfig.ini", TRUE);
	$configWithMarkers = array();
	foreach($config[$environment] as $key => $value){
		$configWithMarkers['%'.$key.'%'] = $value;
	}

	return	$configWithMarkers;
}


function	generateConfigFile($inputFilename, $outputFilename, $config){

	$lines = file($inputFilename);

	if($lines === FALSE){
		echo "Failed to read [".$inputFilename."] for reading.";
		exit(-1);
	}

	$fileHandle = fopen($outputFilename, "w");

	if($fileHandle === FALSE){
		echo "Failed to read [".$outputFilename."] for writing.";
		exit(-1);
	}

	$search = array_keys($config);
	$replace = array_values($config);

	foreach($lines as $line){
		$line = str_replace($search, $replace, $line);
		fwrite($fileHandle, $line);
	}

	fclose($fileHandle);
}

?>

And then deployConfig.ini looks something like:

[global]

;global variables go here.

[amazonec2]
nginx.log.directory = /var/log/nginx
nginx.root.directory = /usr/share/nginx
nginx.conf.directory = /etc/nginx
nginx.run.directory  = /var/run
nginx.user			 = nginx

[macports]
nginx.log.directory = /opt/local/var/log/nginx
nginx.root.directory = /opt/local/share/nginx
nginx.conf.directory = /opt/local/etc/nginx
nginx.run.directory  = /opt/local/var/run
nginx.user			 = _www

Solution 2 - Variables

This is many years late but since I found the solution I'll post it here. By using maps it is possible to do what was asked:

map $http_host $variable_name {
    hostnames;

    default       /ap/;
    example.com   /api/;
    *.example.org /whatever/;
}

server {
    location $variable_name/test {
        proxy_pass $auth_proxy;
    }
}

If you need to share the same endpoint across multiple servers, you can also reduce the cost by simply defaulting the value:

map "" $variable_name {
    default       /test/;
}

Map can be used to initialise a variable based on the content of a string and can be used inside http scope allowing variables to be global and sharable across servers.

Solution 3 - Variables

You could do the opposite of what you proposed.

location (/test)/ {
   set $folder $1;
}

location (/test_/something {
   set $folder $1;
}

Solution 4 - Variables

A modified python version of @danack's PHP generate script. It generates all files & folders that live inside of build/ to the parent directory, replacing all {{placeholder}} matches. You need to cd into build/ before running the script.

File structure

build/
-- (files/folders you want to generate)
-- build.py

sites-available/...
sites-enabled/...
nginx.conf
...

build.py

import os, re

# Configurations
target = os.path.join('.', '..')
variables = {
  'placeholder': 'your replacement here'
}


# Loop files
def loop(cb, subdir=''):
  dir = os.path.join('.', subdir);

  for name in os.listdir(dir):
    file = os.path.join(dir, name)
    newsubdir = os.path.join(subdir, name)

    if name == 'build.py': continue
    if os.path.isdir(file): loop(cb, newsubdir)
    else: cb(subdir, name)


# Update file
def replacer(subdir, name):
  dir  = os.path.join(target, subdir)
  file = os.path.join(dir, name)
  oldfile = os.path.join('.', subdir, name)
  
  with open(oldfile, "r") as fin:
    data = fin.read()

  for key, replacement in variables.iteritems():
    data = re.sub(r"{{\s*" + key + "\s*}}", replacement, data)

  if not os.path.exists(dir):
    os.makedirs(dir)

  with open(file, "w") as fout:
    fout.write(data)


# Start variable replacements.
loop(replacer)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontomkaView Question on Stackoverflow
Solution 1 - VariablesDanackView Answer on Stackoverflow
Solution 2 - VariablesVarstahlView Answer on Stackoverflow
Solution 3 - VariablesrstackhouseView Answer on Stackoverflow
Solution 4 - VariablesRicky BoyceView Answer on Stackoverflow