NGINX conditional logging and responses

2020-11-01

Here are some examples on how to configure NGINX to conditionally log requests based on certain conditions like remote IP address, URI etc by using the map module and variables.

Conditional logging

Create a variable $do_not_log using ngx_http_map_module.

This is placed in the HTTP block.

map $remote_addr $log_enable {
    "192.168.4.1" 0;
    "192.168.4.2" 0;
    "192.168.4.3" 0;
    "192.168.4.4" 0;
    default 1;
}

Regexes can also be used against the IP address.

map $remote_addr $log_enable {
    ~^192\.168\.4\.[1-4]$ 0;
    default 1;
}

In the server block, we can configure the logging. When $log_enable = 0, there is no logging to the access log. Don't forget to set the log format e.g combined, it is required.

server {
    # Other directives here...

    access_log /var/log/nginx/nginx-access.log combined if=$log_enable;
}

Redirecting based on IP address

Inside the server block, you can test on the variable $redirect. For example

map $remote_addr $redirect {
    "192.168.4.1" 1;
    "192.168.4.2" 1;
    "192.168.4.3" 1;
    "192.168.4.4" 1;
    default 0;
}

server {
    listen 80;
    root /var/www/MYDOMAIN.COM;

    # redirect some users to other domain
    if ($redirect = 1) {
        return 301 http://OTHER-DOMAIN.COM$request_uri;
    }
}

Capitalisation of domains is for clarity only, don't do this in your NGINX config.

Other conditionals

You can test against the URI directly

map $uri $do_not_log {
    default 0;
    /do/not/log-this 1;
}

Or using a regex

map $uri $do_not_log {
    default 0;
    ~^/api/v1/status/.*$ 1;
}

Multiple conditions

NGINX doesn't support multiple conditionals like:

# not supported!
if ($redirect = 1 && $log_enable = 1) {
    ...
}

# also not supported, or anything of similar meaning
access_log /var/log/nginx/nginx-access.log combined if=($a && $b);

But you can work around it my chaining map directives to derive a final desired variable. For example

map $remote_addr $not_match_ip {
    ~^192\.168\.4\.[1-4]$ 0;
    default 1;
}

map $uri $not_match_ip_and_uri {
    /do/not/log-this 0;
    default $match_ip;
}

Then we use $not_match_ip_and_uri in the server block

access_log /var/log/nginx/nginx-access.log combined if=$not_match_ip_and_uri;

Other methods

Another method for dealing with multiple conditions. Use this inside a server block, not a location block!

if ($request_uri = /) {
    set $test A;
}

if ($host ~* teambox.com) {
    set $test "${test}B";
}

if ($http_cookie !~* "auth_token") {
    set $test "${test}C";
}

if ($test = "ABC") {
    proxy_pass http://teambox-cms.heroku.com;
    break;
}

References