Site icon GetPageSpeed

NGINX JSON Var Module: Build Safe JSON Variables

NGINX JSON Var Module: Build JSON Variables for Logging, APIs, and Proxying

The NGINX JSON Var module lets you compose NGINX variables into properly escaped JSON objects. When you need to return a JSON response from NGINX, pass structured metadata to an upstream, or write JSON-formatted access logs, you face a fundamental problem: NGINX has no built-in way to produce a JSON-escaped variable.

You can manually build a JSON string using set or map directives:

set $json '{"ip":"$remote_addr","agent":"$http_user_agent"}';

However, this approach is dangerous. If $http_user_agent contains a double quote or backslash — which real-world user agents regularly do — the resulting string is broken JSON. This can corrupt log pipelines, break upstream parsers, and open the door to log injection attacks.

The NGINX JSON Var module solves this problem. It lets you define variables that produce properly escaped JSON objects at request time. Every value runs through NGINX’s ngx_escape_json() function. Quotes, backslashes, and control characters are all safely escaped.

How the NGINX JSON Var Module Works

The module introduces a single block directive, json_var, in the http context. You give it a variable name and a list of key-value pairs. At runtime, when NGINX accesses that variable, the module evaluates all inner expressions, JSON-escapes each value, and assembles a complete JSON object.

Here is the internal process:

  1. At configuration time, the module parses the json_var block. It registers the variable name and compiles each value expression (which can contain NGINX variables like $remote_addr)
  2. At request time, the module evaluates every expression against the current request. It calculates the exact buffer size needed and builds the output in a single pass
  3. All values are JSON strings — they are always double-quoted. Numeric values like $status appear as "200", not 200
  4. Memory comes from the request pool, so it is freed when the request completes

The variable is non-cacheable. It is recomputed each time it is accessed. This ensures variables like $status always reflect the correct value.

Why Not Use Native escape=json?

NGINX (since version 1.11.8) supports escape=json in the log_format directive:

log_format json_native escape=json
    '{"client":"$remote_addr","agent":"$http_user_agent"}';

This works well for access logs. However, the escaping happens only at log-write time. It does not produce a reusable NGINX variable. You cannot use it in:

The NGINX JSON Var module fills this gap. It produces a first-class NGINX variable usable anywhere variables are accepted.

Capability Native escape=json JSON Var Module
JSON-escaped access logs Yes Yes (with escape=none)
JSON variable in return No Yes
JSON variable in proxy_set_header No Yes
JSON variable in add_header No Yes
JSON variable in map or if No Yes
Automatic key-value structure No (manual) Yes (declarative)

Installation

RHEL, CentOS, AlmaLinux, Rocky Linux, Fedora

Install from the GetPageSpeed RPM repository:

sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-json-var

Load the module at the top level of /etc/nginx/nginx.conf (before the http block):

load_module modules/ngx_http_json_var_module.so;

Debian and Ubuntu

First, set up the GetPageSpeed APT repository, then install:

sudo apt-get update
sudo apt-get install nginx-module-json-var

On Debian/Ubuntu, the package handles module loading automatically. No load_module directive is needed.

See the APT module page for details.

Configuration

The json_var Directive

Property Value
Syntax json_var $variable { key value; ... }
Default —
Context http

The json_var directive defines a new NGINX variable whose value is a JSON object. The first argument is the variable name (must start with $). Inside the block, each line specifies a JSON key and a value expression. Value expressions can contain any NGINX variables.

Example:

http {
    json_var $request_info {
        timestamp    $time_iso8601;
        client       $remote_addr;
        method       $request_method;
        uri          $request_uri;
        userAgent    $http_user_agent;
        status       $status;
    }

    server {
        listen 80;

        location /info {
            default_type application/json;
            return 200 $request_info;
        }
    }
}

A request to /info produces:

{"timestamp":"2026-03-10T11:08:58+08:00","client":"127.0.0.1","method":"GET","uri":"/info","userAgent":"curl/8.12.1","status":"200"}

Rules and Constraints

Use Cases

1. Structured JSON Access Logs

The most popular use for the NGINX JSON Var module is structured access logs. These logs are ready for tools like Elasticsearch, Loki, Datadog, or Splunk.

http {
    json_var $json_access_log {
        time         $time_iso8601;
        client       $remote_addr;
        method       $request_method;
        uri          $request_uri;
        status       $status;
        bodyBytes    $body_bytes_sent;
        referer      $http_referer;
        userAgent    $http_user_agent;
        requestTime  $request_time;
        upstream     $upstream_addr;
        upstreamTime $upstream_response_time;
    }

    log_format json_access escape=none $json_access_log;

    access_log /var/log/nginx/access.json json_access;
}

Important: Use escape=none in the log_format when using a json_var variable. The module handles JSON escaping internally. Without escape=none, NGINX’s default escaping hex-encodes the quotes (producing \x22), which makes the output unreadable.

Each log line is a valid JSON object:

{"time":"2026-03-10T11:08:58+08:00","client":"127.0.0.1","method":"GET","uri":"/page","status":"200","bodyBytes":"1024","referer":"","userAgent":"Mozilla/5.0","requestTime":"0.002","upstream":"127.0.0.1:8080","upstreamTime":"0.001"}

This is cleaner than hand-crafted JSON in log_format. You do not need to manage quotes, commas, or escape characters.

2. JSON API Responses Without a Backend

For health checks, status endpoints, or debug pages, serve JSON directly from NGINX:

http {
    json_var $health_response {
        status    healthy;
        server    $hostname;
        time      $time_iso8601;
    }

    server {
        listen 80;

        location /health {
            default_type application/json;
            return 200 $health_response;
        }
    }
}

This returns properly formatted JSON:

{"status":"healthy","server":"web01","time":"2026-03-10T11:08:58+08:00"}

3. Passing Structured Metadata to Upstreams

When NGINX acts as a reverse proxy, you often forward client metadata to the backend. Instead of multiple headers, pass a single JSON object:

http {
    json_var $client_context {
        clientIp     $remote_addr;
        host         $host;
        scheme       $scheme;
        requestId    $request_id;
        forwardedFor $http_x_forwarded_for;
    }

    server {
        listen 80;

        location / {
            proxy_set_header X-Client-Context $client_context;
            proxy_pass http://backend;
        }
    }
}

The backend receives one X-Client-Context header with all metadata in a JSON string. This reduces header count and makes the structure explicit.

4. Debug Headers in Responses

During troubleshooting, attach request metadata as a response header:

http {
    json_var $debug_info {
        requestId    $request_id;
        upstream     $upstream_addr;
        cacheStatus  $upstream_cache_status;
        responseTime $request_time;
    }

    server {
        listen 80;

        location / {
            add_header X-Debug-Info $debug_info always;
            proxy_pass http://backend;
        }
    }
}

5. Custom Error Responses in JSON

Combine json_var with NGINX error pages to return structured error responses:

http {
    json_var $not_found_response {
        error   true;
        message "The requested resource was not found";
        path    $request_uri;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
        }

        error_page 404 = @not_found;

        location @not_found {
            default_type application/json;
            return 404 $not_found_response;
        }
    }
}

JSON Escaping Behavior

The module uses NGINX’s built-in ngx_escape_json() function. These characters are escaped automatically:

Character Escaped Form
" (double quote) \"
\ (backslash) \\
\n (newline) \n
\r (carriage return) \r
\t (tab) \t
Control characters (0x00–0x1F) \uXXXX

User-controlled input like $http_user_agent or $args will never break the JSON structure. A User-Agent like Mozilla/5.0 ("test") \slash becomes Mozilla/5.0 (\"test\") \\slash in the output.

JSON Var Module vs. JSON Module

GetPageSpeed offers two complementary JSON modules for NGINX. They serve opposite purposes:

The JSON module provides json_loads and json_dumps directives. It takes a JSON string (from a header, a query parameter, or a set directive) and extracts individual fields into NGINX variables. The JSON Var module does the reverse: it takes individual NGINX variables and assembles them into a JSON object.

Feature JSON Var Module JSON Module
Direction Variables → JSON object JSON string → Variables
Directives json_var json_loads, json_dumps
Dependencies None NDK, jansson library
Context http only http, server, location
JSON escaping Automatic (built-in) Not applicable (reads JSON)
Use case Build JSON for output Extract fields from input

When to Use Which

Use the JSON Var module when you need to produce JSON output — logging, API responses, proxy headers, or debug information. It handles escaping automatically.

Use the JSON module when you receive JSON input and need to extract fields — routing based on a JSON header, extracting a tenant ID from a JSON payload, or logging individual JSON fields.

Using Both Together

The two modules combine naturally in API gateway scenarios. The JSON module parses incoming JSON metadata, and the JSON Var module constructs outgoing JSON with enriched data:

load_module modules/ndk_http_module.so;
load_module modules/ngx_http_json_module.so;
load_module modules/ngx_http_json_var_module.so;

http {
    # Construct enriched JSON for the upstream
    json_var $enriched_context {
        clientIp  $remote_addr;
        tenant    $tenant_name;
        tier      $tenant_tier;
        requestId $request_id;
    }

    server {
        listen 80;

        location /api/ {
            # Parse incoming JSON header
            json_loads $tenant_json $http_x_tenant_config;
            json_dumps $tenant_name $tenant_json name;
            json_dumps $tenant_tier $tenant_json tier;

            # Forward enriched context to backend
            proxy_set_header X-Context $enriched_context;
            proxy_pass http://backend;
        }
    }
}

In this example, a client sends X-Tenant-Config: {"name":"acme","tier":"premium"}. NGINX extracts the name and tier fields with the JSON module, then the JSON Var module packages them alongside $remote_addr and $request_id into a new JSON object for the backend.

Performance Considerations

The NGINX JSON Var module adds minimal overhead:

The variable is non-cacheable, so it is recomputed on each access. In practice, this cost is negligible. The computation involves one pass over field values with no system calls.

For high-traffic logging, performance matches native escape=json. Both use the same ngx_escape_json() function internally. The only extra cost is assembling braces, commas, and key names.

Security Best Practices

Automatic JSON escaping protects against several attack classes:

Log injection prevention. Without proper escaping, an attacker could craft headers containing JSON syntax that corrupts log structure or injects false entries. The module escapes all special characters before they reach the log file.

Header injection prevention. When passing JSON via proxy_set_header, unescaped quotes could let an attacker break out of a JSON value. The module prevents this.

Recommendations:

Troubleshooting

“unknown directive json_var”

The module is not loaded. Add the load_module directive at the top level of nginx.conf:

load_module modules/ngx_http_json_var_module.so;

Confirm the module file exists on RHEL-based systems:

ls /usr/lib64/nginx/modules/ngx_http_json_var_module.so

“json_var directive is not allowed here”

The directive can only appear in the http context. Move it outside any server or location block:

http {
    json_var $my_var {
        key value;
    }

    server {
        # use $my_var here
    }
}

“no fields defined in json_var block”

The block needs at least one key-value pair:

# Wrong: empty block
json_var $empty {
}

# Correct: at least one field
json_var $minimal {
    status ok;
}

Log output shows \x22 instead of quotes

Add escape=none to the log_format directive:

# Wrong: default escaping double-encodes the JSON
log_format broken $my_json_var;

# Correct: module handles escaping already
log_format correct escape=none $my_json_var;

Variable shows empty string

Ensure the referenced inner variables exist in your context. If a variable like $upstream_addr is empty (no proxy), the JSON value is an empty string. This is expected behavior.

Conclusion

The NGINX JSON Var module fills a real gap in NGINX’s variable system. Native escape=json handles log escaping, but it cannot produce reusable variables. This module gives you properly escaped JSON objects that work everywhere — in responses, headers, proxy configs, and log formats.

For DevOps engineers building API gateways or structuring log pipelines, this module provides a clean, declarative approach. It eliminates fragile hand-crafted JSON strings from your configuration.

The module is available from the GetPageSpeed RPM repository and the APT repository. Source code is on GitHub.

For the complementary module that parses JSON input, see the NGINX JSON module. For other data manipulation, see set-misc for encoding and hashing, array variables for lists, or njs for JavaScript scripting.

D

Danila Vershinin

Founder & Lead Engineer

NGINX configuration and optimizationLinux system administrationWeb performance engineering

10+ years NGINX experience • Maintainer of GetPageSpeed RPM repository • Contributor to open-source NGINX modules

Exit mobile version