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?

masegaloeh
  • 18,376
  • 10
  • 58
  • 110

3 Answers3

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.
Xavier Lucas
  • 13,225
  • 2
    access_log/error_log work with variables, but with some limitations. server_name can't contain variables. – Alexey Ten Nov 17 '14 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. – Xavier Lucas Nov 17 '14 at 12:10
  • 7
    Well, $hostname is the only allowed variable. https://github.com/nginx/nginx/blob/bb8c0683da773566e09797ef8a4ee00ad1e0f956/src/http/ngx_http_core_module.c#L4370 Actually, it's more like a magic constant, not a real variable – Alexey Ten Nov 17 '14 at 12:12
  • @AlexeyTen Good point, updated the answer this way. – Xavier Lucas Nov 17 '14 at 12:16
  • 1
    And still, *_logs work with variables, but nginx's worker should have permissions to create resulted filename. In particular that means all previous directories must exist. – Alexey Ten Nov 17 '14 at 12:17
  • 1
    @AlexeyTen No it doesn't. – Xavier Lucas Nov 17 '14 at 12:18
  • 2
  • 4
    This is maybe the best researched SF answer ever. Thanks to both of you /Cc @AlexeyTen – kaiser Feb 15 '16 at 16:58
  • 1
    Thanks for the confirmation that error_log won't work with variables---I thought I was crazy. For anyone wanting to rotate their error logs, I'd recommend the "Manual Log Rotation" or "Log Rotation with logrotate" sections here: https://www.digitalocean.com/community/tutorials/how-to-configure-logging-and-log-rotation-in-nginx-on-an-ubuntu-vps – kevinmicke Feb 28 '18 at 17:46
  • Whereas you can set a custom variable in an http.server block and use it in the proxy_pass you cannot set a custom variable in a stream.server block. – Jesse Chisholm Sep 18 '20 at 02:45
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.

toraritte
  • 260
gruentee
  • 151
  • 1
  • 4
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.

Dave M
  • 4,514