64

I am trying to optimize my nginx configs, so it would be possible to set one variable, and all location paths would update automatically. I have four lines in question:

server_name php.domain.com;
root /srv/web/vhosts/php/web;
error_log /srv/web/vhosts/php/logs/error.log;
access_log /srv/web/vhosts/php/logs/access.log;

What I would like to achieve is to set one variable (in this case 'php') and include it to config.

set $variable "php";
server_name $variable.domain.com;
root /srv/web/vhosts/$variable/web;
error_log /srv/web/vhosts/$variable/logs/error.log;
access_log /srv/web/vhosts/$variable/logs/access.log;

However it seams that nginx ignores variables in this config. Am I doing something wrong or it is not possible to use variable in location paths?

3 Answers 3

94

Variables can't be declared anywhere nor be used in any directive.

As the documentation of set directive is :

Syntax:   set $variable value;
Default:  —
Context:  server, location, if

The immediate consequence is that you can't use custom variables in an http block.

Update : after a discussion and experiments with AlexeyTen in this chatroom.

  • access_log can contain variables with restrictions. Among them, the lack of buffering and the fact that the leading slash must not be declared in a variable.
  • error_log won't work with variables at all.
  • root directive can contains variables.
  • server_name directive only allows strict $hostname value as a variable-like notation.
10
  • 2
    access_log/error_log work with variables, but with some limitations. server_name can't contain variables.
    – Alexey Ten
    Nov 17, 2014 at 11:38
  • 1
    @AlexeyTen Re-read my answer. It doesn't work with custom variables. Server name can contain built-in variable like $hostname. Updated the answer to clarify that behaviour. Nov 17, 2014 at 12:10
  • 7
    Well, $hostname is the only allowed variable. github.com/nginx/nginx/blob/… Actually, it's more like a magic constant, not a real variable
    – Alexey Ten
    Nov 17, 2014 at 12:12
  • 2
    Let us continue this discussion in chat.
    – Alexey Ten
    Nov 17, 2014 at 12:18
  • 4
    This is maybe the best researched SF answer ever. Thanks to both of you /Cc @AlexeyTen
    – kaiser
    Feb 15, 2016 at 16:58
5

You can use variables declared via map. This Stackoverflow answer discusses using custom variables in the expression part of location blocks:

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.

1

You can do this using docker!

As far as I can tell, this is a feature of the docker image Nginx puts out, but it might also be a feature of docker itself. I'm sure someone will correct me. However, I know that it works with docker, so that's what I'll propose.

Nginx regularly updates a docker file on the docker image hub. The image they put out allows you to create "template" files that basically pass whatever variable you want into your conf file.

For example, let's say I want my conf file to look like this:

http {
  server {
    server {
      # Redirect http to https
      listen 80;
      listen [::]:80;
      server_name www.example.com example.com;

      root /etc/nginx/www;
      index index.html;

      location / {
          try_files $uri $uri/ =404;
      }
    }
  }

What you do is create a "template" file default.conf.template in /etc/nginx/templates (you can actually configure this if you want) that looks like this:

http {
  server {
    server {
      # Redirect http to https
      listen ${NGINX_PORT};
      listen [::]:${NGINX_PORT};
      server_name www.${NGINX_HOST} ${NGINX_HOST};

      root ${NGINX_ROOT};
      index index.html;

      location / {
          try_files $uri $uri/ =404;
      }
    }
  }

Then, you create a docker-compose.yml file (you can do this from command line as well, but it's not going to look as pretty) that looks like this:

services:
  web:
    image: nginx:latest
    volumes:
      - /etc/nginx/templates:/etc/nginx/templates:ro
      - /etc/nginx/www:/etc/nginx/www:ro
    ports:
      - "8080:80"
    environment:
      - NGINX_HOST=example.com 
      - NGINX_PORT=80
      - NGINX_ROOT=/etc/nginx/www

And run it by simply starting docker compose: docker compose up -d

Check that the container is running with docker container list

You can verify the conf file was evaluated correctly by copying the file out of the running container if you want like so (double check the container name here, from the previous command): docker cp docker-web-1:/etc/nginx/conf.d/default.conf . && cat default.conf

As far as I can tell, you can use as many environment variables as you want.

1
  • I'm working on a new project, and this is exactly what I needed.
    – synkro
    Jan 26 at 20:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .