đź“… Updated: January 27, 2026 (Originally published: September 1, 2022)
This guide covers everything you need to know about NGINX maps hash tuning. When your map directives grow to hundreds or thousands of entries, NGINX may emit warnings about hash table sizing. Understanding how to tune these hash tables is essential for optimal performance.
This comprehensive NGINX maps hash tuning guide explains the underlying algorithm. It shows you when tuning is actually needed. Finally, it provides tested formulas for calculating optimal map_hash_max_size and map_hash_bucket_size values.
Why Hash Table Tuning Matters for Large Maps
When NGINX loads a map directive, it builds an in-memory hash table for fast lookups. Two directives control this hash table:
map_hash_bucket_size– Maximum size of each hash bucket (default: CPU cache line size, typically 64 bytes)map_hash_max_size– Maximum number of hash buckets (default: 2048)
These defaults work perfectly for most configurations. However, you need to tune them when NGINX emits warnings or errors. Understanding the mechanics of NGINX maps hash tuning helps you make informed decisions. You avoid blindly increasing values without understanding why.
Critical: Directive Placement Order
Important: If you need non-default values for map_hash_max_size or map_hash_bucket_size, these directives must appear before any map blocks. NGINX processes the configuration sequentially. Placing hash directives after a map block causes a “directive is duplicate” error.
http {
# CORRECT: Hash directives BEFORE map blocks
map_hash_max_size 8192;
map_hash_bucket_size 128;
map $uri $redirect_target {
default "";
# ... entries ...
}
}
http {
# WRONG: Hash directives AFTER map block - causes error!
map $uri $redirect_target {
default "";
}
map_hash_max_size 8192; # Error: directive is duplicate
}
This ordering requirement applies to all hash tuning directives in NGINX. It is not limited to map-related directives.
The Hash Element Size Formula Explained
Understanding how NGINX calculates storage is the foundation of effective NGINX maps hash tuning. From the NGINX source code (src/core/ngx_hash.c):
#define NGX_HASH_ELT_SIZE(name) \
(sizeof(void *) + ngx_align((name)->key.len + 2, sizeof(void *)))
On 64-bit systems, each map entry requires storage based on key length:
| Key Length | Aligned Size | Total per Entry |
|---|---|---|
| 10 chars | align(12, 8) = 16 | 8 + 16 = 24 bytes |
| 30 chars | align(32, 8) = 32 | 8 + 32 = 40 bytes |
| 46 chars | align(48, 8) = 48 | 8 + 48 = 56 bytes |
| 100 chars | align(102, 8) = 104 | 8 + 104 = 112 bytes |
Important insight: Only the KEY length affects bucket sizing. Values are stored as pointers (8 bytes) regardless of their actual length. A map with 2-character values uses the same bucket space as one with 2000-character values.
Maximum Key Lengths by Bucket Size
Each entry must fit within a bucket along with a terminating pointer. Testing on Rocky Linux 10 with NGINX 1.28 confirms these exact limits:
| bucket_size | Maximum Key Length |
|---|---|
| 64 (default) | 46 characters |
| 128 | 110 characters |
| 256 | 238 characters |
If ANY single key exceeds the bucket capacity, NGINX emits an EMERG error. The server refuses to start:
nginx: [emerg] could not build map_hash, you should increase map_hash_bucket_size: 64
This error requires increasing bucket_size. No amount of max_size adjustment helps. The problem is a single key that cannot fit in any bucket.
Entry Count and Hash Distribution Guidelines
The map_hash_max_size directive controls available buckets for distributing entries. When NGINX cannot find an optimal distribution, it emits a WARNING:
nginx: [warn] could not build optimal map_hash, you should increase
either map_hash_max_size: 2048 or map_hash_bucket_size: 64;
ignoring map_hash_bucket_size
This is a warning, not an error. NGINX will still load and function correctly. However, the hash table may be suboptimal for performance.
Tested Entry Count Thresholds
With default bucket_size=64 and short keys, here are the tested thresholds:
| Entry Count | Default max_size (2048) | Required max_size |
|---|---|---|
| 2000 | OK | 2048 |
| 2500 | WARNING | ~4096 |
| 5000 | WARNING | 8192 |
| 10000 | WARNING | 16384 |
Practical guideline: Set max_size to approximately 1.5-2x your entry count. This ensures optimal distribution.
Two Paths to Fix the Warning Message
The warning message suggests increasing “either” parameter. Both approaches work. They solve the problem differently. Understanding this distinction is key to effective configuration.
Option 1: Increase max_size for More Buckets
More buckets means better hash distribution. You get fewer collisions per bucket:
http {
map_hash_max_size 8192; # Place BEFORE map blocks
map $uri $redirect_target {
default "";
# ... thousands of redirect entries ...
}
}
Best when: You have many entries with short-to-medium length keys.
Option 2: Increase bucket_size for Larger Buckets
Larger buckets can hold more entries each. This reduces the number of buckets needed:
http {
map_hash_bucket_size 128; # Place BEFORE map blocks
map $http_referer_host $bad_referer {
hostnames;
default 0;
# ... thousands of spam domains ...
}
}
Best when: Keys are moderately long or you want to minimize total bucket count.
Which Approach Should You Choose?
For the same 4000-entry hostnames map, both configurations eliminate the warning:
map_hash_max_size 8192with default bucket_size → Worksmap_hash_bucket_size 128with default max_size → Works
The choice depends on your priorities:
– Minimize memory usage: Use larger bucket_size (fewer buckets allocated)
– Minimize lookup time: Use larger max_size (fewer entries per bucket to scan)
In practice, the performance difference is negligible for most configurations.
Error vs Warning: Understanding the Difference
Distinguishing between error types is crucial for correct configuration:
| Message Type | Example | Cause | Solution |
|---|---|---|---|
| EMERG | “increase map_hash_bucket_size: 64” | Single key too long | MUST increase bucket_size |
| WARN | “increase either…or…” | Cannot distribute optimally | Increase max_size OR bucket_size |
The EMERG error mentions ONLY bucket_size. It prevents NGINX from starting. The WARNING mentions BOTH parameters. It allows NGINX to proceed normally.
Step-by-Step Tuning Workflow
When You See the EMERG Error
nginx: [emerg] could not build map_hash, you should increase map_hash_bucket_size: 64
Follow these steps:
- You have at least one key longer than 46 characters
- Add
map_hash_bucket_size 128;BEFORE your map blocks - If still failing, increase to 256 or higher
- Use the formula:
bucket_size >= key_length + 18(rounded up to cache line)
When You See the WARNING
nginx: [warn] could not build optimal map_hash, you should increase
either map_hash_max_size: 2048 or map_hash_bucket_size: 64
Follow these steps:
- Try adding
map_hash_max_size 4096;BEFORE your map blocks - Keep doubling until the warning disappears
- If
max_sizealone does not help even at high values, trybucket_sizeinstead - Important: After the warning disappears, decrease values to find optimal minimum
The Going Backwards Optimization
After achieving a working configuration, you may have over-provisioned. Test lower values:
# If this works without warning:
map_hash_max_size 16384;
map_hash_bucket_size 128;
# Try removing max_size entirely:
map_hash_bucket_size 128;
# Still works? You did not need max_size!
# Try lower bucket_size with adjusted max_size:
map_hash_bucket_size 64;
map_hash_max_size 8192;
# Still works? This is more memory-efficient!
This backward optimization is often overlooked. It is essential for optimal resource usage.
Getting Your CPU Cache Line Size
The bucket_size must be a multiple of your CPU cache line size. Find yours with:
getconf LEVEL1_DCACHE_LINESIZE
# Typical output: 64
Valid bucket_size values are multiples of this result: 64, 128, 192, 256, and so on.
Real-World Example: Referrer Spam Blocking
A map blocking 4000+ spam referrer domains is a common scenario. It typically requires tuning:
http {
# Hash directives MUST come first
map_hash_max_size 8192; # OR use map_hash_bucket_size 128;
map $http_referer_host $bad_referer {
hostnames;
default 0;
.spam-site-1.com 1;
.spam-site-2.com 1;
# ... 4000+ more entries
}
server {
# Use $bad_referer in your configuration
}
}
With defaults, this produces the warning. Either solution works:
Option A – More buckets:
map_hash_max_size 8192;
Option B – Larger buckets:
map_hash_bucket_size 128;
Choose based on your memory versus lookup-time priorities.
Common Mistakes to Avoid
When working with NGINX maps hash tuning, watch out for these pitfalls:
- Placing hash directives after map blocks – This causes cryptic “duplicate” errors
- Increasing only max_size for long keys – Long keys need larger bucket_size
- Over-provisioning without testing – Always optimize backwards after fixing errors
- Forgetting the hostnames parameter – Domain wildcards need this for proper hashing
- Using non-aligned bucket sizes – Values must be multiples of cache line size
Performance Considerations
Hash table performance depends on several factors. The algorithm NGINX uses is efficient for most use cases. Lookups are O(1) for well-distributed hash tables.
Memory usage scales with the number of buckets multiplied by bucket size. A configuration with max_size 8192 and bucket_size 128 allocates up to 1MB for the hash table. This is typically negligible compared to other NGINX memory usage.
Startup time increases with larger maps. NGINX builds the hash table during configuration loading. Very large maps (100,000+ entries) may add a few seconds to startup time.
Quick Reference Tables
Maximum Key Lengths
| bucket_size | Max Key Length |
|---|---|
| 64 | 46 chars |
| 128 | 110 chars |
| 256 | 238 chars |
Suggested max_size by Entry Count
| Entries | Suggested max_size |
|---|---|
| < 2000 | Default (2048) |
| 2000-5000 | 4096-8192 |
| 5000-10000 | 8192-16384 |
| 10000+ | 16384+ |
Configuration Decision Tree
- EMERG error mentioning only bucket_size? → Increase
bucket_size(long key issue) - WARNING mentioning both parameters? → Increase
max_sizefirst (entry count issue) - max_size increase not helping? → Increase
bucket_sizeinstead - Working now? → Decrease values to find optimal minimum
- Remember: Place hash directives BEFORE map blocks!
Summary
Effective NGINX maps hash tuning requires understanding three key concepts:
- Directive order matters – Hash directives must appear before map blocks
- Key length determines bucket_size needs – Long keys require larger buckets
- Entry count determines max_size needs – Many entries require more buckets
For most configurations, the defaults work perfectly. Only tune when NGINX tells you to. Always test both increasing and decreasing values to find the optimal configuration.
Further Reading
- NGINX Map Directive Guide – Complete guide to using maps
- NGINX Hash Sizing Documentation – Official documentation
- Block Referrer Spam – Practical example with large maps

