The NGINX label module lets you define global key-value pairs directly in your NGINX configuration. These labels act as server-level metadata — environment names, datacenter regions, application versions, cluster identifiers — that you can access anywhere as NGINX variables.
In modern infrastructure, servers need identity. Load balancers, log aggregators, and monitoring systems all need to know which server handled a request. Without the NGINX label module, administrators typically resort to workarounds:
# The map hack — abuses map to define a constant
map "" $env {
default "production";
}
# The set approach — only works in server/location context
server {
set $region "us-east-1";
}
The map hack works, but it is semantically wrong. The map directive is designed for conditional variable mapping, not for defining constants. The set approach is worse — it only works inside server or location blocks, so the value is not truly global. You must repeat it in every server block.
The NGINX label module provides a clean, purpose-built solution. You define labels once in the http context, and they are available everywhere — in log formats, response headers, map directives, proxy headers, and return statements.
How the NGINX Label Module Works
The module operates entirely at configuration time. When NGINX starts, it reads all label directives, validates the keys and values, and stores them in an optimized hash table. At request time, accessing a label variable is a fast hash lookup with zero overhead.
Here is the internal process:
- At configuration time, each
labeldirective registers a key-value pair. Keys are normalized to lowercase. The module validates that keys contain only letters, numbers, and underscores, and that values do not contain&or=characters - The hash table is built once during configuration initialization. The
labels_hash_max_sizeandlabels_hash_bucket_sizedirectives control hash table sizing - At request time, accessing
$label_envtriggers a hash lookup by key name. Accessing$labelsiterates all pairs and concatenates them askey=valuepairs separated by& - Memory is efficient — label values are stored in the configuration pool and shared across all worker processes. No per-request allocation occurs for individual label lookups
Because labels are read-only constants defined at configuration time, they are inherently thread-safe and add no locking overhead to request processing.
Why Not Use Native NGINX Alternatives?
NGINX offers several built-in mechanisms for defining variables. However, none of them are designed for global metadata:
| Approach | Limitation |
|---|---|
set $var value; |
Only works in server and location context — not truly global |
map "" $var { default "value"; } |
Semantic misuse — map is for conditional mapping, not constants |
geo $var { default value; } |
Designed for IP-based mapping — wrong tool for the job |
env MY_VAR; |
Passes OS environment variables, but they are not accessible as $variable in most contexts |
The NGINX label module fills this gap. It provides a dedicated, clean directive for declaring global constants that are accessible as first-class NGINX variables throughout the entire configuration.
| Capability | Native Workarounds | Label Module |
|---|---|---|
| Global scope (all server blocks) | map hack only |
Yes |
| Semantic clarity | Poor — repurposes other directives | Clear — purpose-built |
| Key validation | None | Enforced (alphanumeric + underscore) |
| Duplicate detection | None — silently overwrites | Error on duplicates |
| Bulk access (all labels at once) | Not possible | $labels variable |
| Hash-optimized lookup | Depends on directive | Always |
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-label
Load the module at the top level of /etc/nginx/nginx.conf (before the http block):
load_module modules/ngx_http_label_module.so;
Debian and Ubuntu
First, set up the GetPageSpeed APT repository, then install:
sudo apt-get update
sudo apt-get install nginx-module-label
On Debian/Ubuntu, the package handles module loading automatically. No
load_moduledirective is needed.
Configuration
The label Directive
| Property | Value |
|---|---|
| Syntax | label key value; |
| Default | — |
| Context | http |
The label directive defines a single key-value pair. The key becomes accessible as $label_key throughout the configuration. You can define multiple labels by using the directive multiple times.
Key rules:
– Keys may contain only letters, numbers, and underscores ([a-zA-Z0-9_])
– Keys are normalized to lowercase — label MyEnv production; creates $label_myenv
– Duplicate keys cause a configuration error
– Keys cannot be empty
Value rules:
– Values cannot contain & or = characters (these are reserved for the $labels serialization format)
– Values can contain spaces if quoted: label greeting "hello world";
Example:
http {
label env production;
label region us_east_1;
label cluster_id web_frontend;
label app_version 2.5.1;
server {
listen 80;
location / {
add_header X-Environment $label_env always;
add_header X-Region $label_region always;
proxy_pass http://backend;
}
}
}
The $label_name Variable
Access any individual label by its key using the $label_ prefix followed by the key name. For example, label env production; creates the variable $label_env with the value production.
If you reference a label that does not exist (e.g., $label_nonexistent), the variable evaluates to an empty string. This does not cause an error.
The $labels Variable
The $labels variable returns all defined labels concatenated in key=value format, separated by &:
env=production®ion=us_east_1&cluster_id=web_frontend&app_version=2.5.1
This format is useful for passing all server metadata in a single header or log field. It follows the same encoding as URL query strings, making it easy to parse in most programming languages.
The labels_hash_max_size Directive
| Property | Value |
|---|---|
| Syntax | labels_hash_max_size size; |
| Default | 512 |
| Context | http |
Sets the maximum size of the labels hash table. Increase this value if you define a large number of labels and NGINX reports hash-related errors during startup.
The labels_hash_bucket_size Directive
| Property | Value |
|---|---|
| Syntax | labels_hash_bucket_size size; |
| Default | processor cache line size |
| Context | http |
Sets the bucket size for the labels hash table. Increase this if you have labels with very long key names.
Use Cases
1. Server Identification in Response Headers
The most straightforward use of the NGINX label module is adding server metadata to response headers. This lets operations teams identify which server, environment, and version handled a request:
http {
label env production;
label region us_east_1;
label app_version 2.5.1;
server {
listen 80;
location / {
add_header X-Server-Env $label_env always;
add_header X-Server-Region $label_region always;
add_header X-App-Version $label_app_version always;
proxy_pass http://backend;
}
}
}
A response from this server includes:
X-Server-Env: production
X-Server-Region: us_east_1
X-App-Version: 2.5.1
This is invaluable for debugging in multi-server environments. When a user reports an issue, the response headers immediately tell you which server and version were involved.
2. Enriched Access Logs for Observability
Labels shine in access logs. By including labels in log output, every log line carries the server context. Log aggregation tools like Elasticsearch, Loki, Splunk, and Datadog can then filter and group by environment, region, or version:
http {
label env production;
label region us_east_1;
label cluster_id web_frontend;
log_format enriched '$remote_addr [$time_local] "$request" $status '
'env=$label_env region=$label_region '
'cluster=$label_cluster_id';
server {
listen 80;
access_log /var/log/nginx/access.log enriched;
location / {
proxy_pass http://backend;
}
}
}
Each log line now includes server metadata:
192.168.1.100 [10/Mar/2026:12:00:15 +0000] "GET /api/users HTTP/1.1" 200 env=production region=us_east_1 cluster=web_frontend
Alternatively, pass all labels at once using the $labels variable:
log_format labeled '$remote_addr [$time_local] "$request" $status labels=$labels';
This produces:
192.168.1.100 [10/Mar/2026:12:00:15 +0000] "GET /api/users HTTP/1.1" 200 labels=env=production®ion=us_east_1&cluster_id=web_frontend
3. Conditional Routing with map
Combine labels with the map directive for environment-aware routing. This lets a single NGINX configuration file behave differently based on the label values:
http {
label env production;
map $label_env $upstream_backend {
production backend_prod;
staging backend_staging;
default backend_dev;
}
upstream backend_prod {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
upstream backend_staging {
server 10.0.2.10:8080;
}
upstream backend_dev {
server 127.0.0.1:8080;
}
server {
listen 80;
location / {
proxy_pass http://$upstream_backend;
}
}
}
By changing a single label env value, the same configuration routes traffic to different upstream backends. This is powerful for infrastructure-as-code workflows where you deploy the same NGINX config to production, staging, and development servers — only the label differs.
4. Forwarding Metadata to Upstream Applications
When NGINX acts as a reverse proxy, labels provide a clean way to pass server context to backend applications:
http {
label env production;
label region us_east_1;
label cluster_id web_frontend;
server {
listen 80;
location / {
proxy_set_header X-Server-Labels $labels;
proxy_set_header X-Server-Env $label_env;
proxy_set_header X-Server-Region $label_region;
proxy_pass http://backend;
}
}
}
The backend receives X-Server-Labels: env=production®ion=us_east_1&cluster_id=web_frontend in a single header. This is cleaner than hardcoding metadata values in multiple proxy_set_header directives, because updating a label automatically updates all references.
5. Health Check Endpoints with Server Context
Create health check endpoints that report server identity:
http {
label env production;
label region us_east_1;
label app_version 2.5.1;
server {
listen 80;
location /health {
default_type application/json;
return 200 '{"status":"ok","env":"$label_env","region":"$label_region","version":"$label_app_version"}';
}
}
}
This returns:
{"status":"ok","env":"production","region":"us_east_1","version":"2.5.1"}
Load balancers and monitoring systems can use this endpoint to verify server identity alongside health status.
Performance Considerations
The NGINX label module adds virtually zero runtime overhead:
- Configuration-time storage — labels are parsed and hashed once when NGINX starts. No work happens at request time beyond hash lookups
- O(1) hash lookup — individual label access via
$label_nameis a constant-time hash table lookup - Shared memory — label values live in the configuration pool, shared across all worker processes without duplication
- No per-request allocation — accessing
$label_namereturns a pointer to the pre-stored value. The$labelsvariable does allocate a buffer per request to concatenate all pairs, but this is freed when the request completes - Cacheable variables — label variables are marked as cacheable, so NGINX evaluates them only once per request even if referenced multiple times
For typical deployments with 5–20 labels, the overhead is unmeasurable. The hash table settings (labels_hash_max_size and labels_hash_bucket_size) only need adjustment if you define hundreds of labels.
Security Best Practices
Labels are server-side constants defined by the administrator. They are not influenced by client input, which makes them inherently safe. However, consider these practices:
Avoid exposing sensitive metadata to clients. Labels like internal IP addresses, database hostnames, or secret tokens should not appear in response headers visible to end users:
# Avoid this in production-facing servers
add_header X-Database-Host $label_db_host; # exposes internal infrastructure
Use labels for internal headers only. When forwarding metadata to trusted upstreams via proxy_set_header, internal labels are safe. However, avoid adding them to responses sent to untrusted clients.
Labels do not accept NGINX variables. Label values are static strings set at configuration time. There is no risk of variable injection — label env "$remote_addr"; stores the literal string $remote_addr, not the client’s IP address.
Troubleshooting
“unknown directive label”
The module is not loaded. Add the load_module directive at the top level of nginx.conf:
load_module modules/ngx_http_label_module.so;
Confirm the module file exists on RHEL-based systems:
ls /usr/lib64/nginx/modules/ngx_http_label_module.so
“label directive is not allowed here”
The label directive can only appear in the http context. Move it outside any server or location block:
http {
label env production;
server {
# use $label_env here
}
}
Note: if you use include inside the http block (e.g., include /etc/nginx/conf.d/*.conf;), you can place label directives in included files.
“duplicate label key”
Each label key must be unique. NGINX rejects configurations with duplicate keys:
nginx: [emerg] duplicate label key "env" in /etc/nginx/conf.d/labels.conf:2
Remove the duplicate or rename one of the keys.
“label key only allows numbers, letters, and underscores”
Label keys cannot contain hyphens, dots, or other special characters:
# Wrong — hyphens are not allowed
label my-env production;
# Correct — use underscores
label my_env production;
“label value contains invalid characters”
Label values cannot contain & or = characters because these are used as delimiters in the $labels serialization format:
# Wrong — contains &
label query "foo&bar";
# Correct — avoid & and = in values
label query "foo_bar";
Label variable returns empty string
If $label_something returns an empty string, the label with key something is not defined. Check your configuration for typos. Remember that keys are normalized to lowercase — label MyKey value; creates $label_mykey, not $label_MyKey. However, since NGINX variable names are case-insensitive, both $label_mykey and $label_MyKey resolve to the same variable.
Conclusion
The NGINX label module provides a clean, purpose-built way to define global key-value metadata in your NGINX configuration. Instead of abusing map, set, or geo directives to define constants, you use a dedicated label directive that validates keys, prevents duplicates, and stores values in an optimized hash table.
For system administrators managing fleets of NGINX servers, labels solve a common operational need: identifying servers in logs, headers, and health checks. By tagging each server with its environment, region, version, and cluster, you make every request traceable and every log entry contextual.
The module is available from the GetPageSpeed RPM repository. Source code is on GitHub.
For related modules, see JSON Var for building JSON objects from variables, set-misc for variable manipulation, or njs for JavaScript scripting in NGINX.

