NGINX Error Guide: 'unknown directive' — fix typos, missing modules, and misplaced context
Fix NGINX 'unknown directive' errors that fail nginx -t: diagnose typos, missing dynamic modules, wrong config context, unloaded load_module, and third-party directives, then reload safely.
- #nginx
- #web-server
- #troubleshooting
- #errors
Stuck on this NGINX error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
NGINX aborts config loading when it encounters a directive its running binary does not recognize. nginx -t fails with [emerg] unknown directive "..." and the file:line, and no new config loads until it is resolved. The directive is “unknown” either because it is misspelled, or because the module that provides it is not compiled in / not loaded.
nginx: [emerg] unknown directive "limit_req_zon" in /etc/nginx/conf.d/app.conf:8
nginx: configuration file /etc/nginx/nginx.conf test failed
The quoted string is exactly what NGINX could not parse. A typo (limit_req_zon) and a real-but-unavailable directive (e.g. brotli without the module) produce the same error text, so the fix depends on which it is.
Symptoms
nginx -tfails with[emerg] unknown directive "X".systemctl reload nginxfails; the running config keeps serving but the change does not apply.- The directive is valid documentation-wise but comes from a third-party or dynamic module (
brotli,more_set_headers,geoip2,perl). - A config that worked on one host fails on another with a differently built NGINX.
sudo nginx -t
nginx: [emerg] unknown directive "brotli" in /etc/nginx/conf.d/gzip.conf:3
nginx: configuration file /etc/nginx/nginx.conf test failed
Common Root Causes
1. A simple typo
The most frequent cause — a misspelled or mis-cased directive. NGINX directives are case-sensitive and exact.
proxy_set_headers Host $host; # BUG: should be proxy_set_header (no 's')
limit_req_zon ...; # BUG: should be limit_req_zone
2. Missing dynamic module (not loaded)
The directive comes from a dynamic module that exists on disk but was never loaded with load_module in the main context.
# gzip.conf uses `brotli on;` but nginx.conf never loads the module:
# (missing) load_module modules/ngx_http_brotli_filter_module.so;
3. Module not compiled into the binary at all
The distro/package NGINX simply was not built with that module, so no load_module can help — you need a different package or a build that includes it.
nginx -V 2>&1 | tr ' ' '\n' | grep -i brotli # empty -> not built in
4. Directive in the wrong context
The directive is real and available, but placed in a context where it is not allowed — though NGINX usually says directive is not allowed here, a directive only valid inside a specific module block can read as unknown when that block/module is absent.
http {
server {
worker_connections 1024; # belongs in events{}, not here
}
}
5. load_module ordering or path wrong
load_module must appear in the main (top-level) context before the block that uses the directive, and the .so path must be correct for the installed version.
events { ... }
load_module modules/ngx_http_headers_more_filter_module.so; # too late / wrong context
Diagnostic Workflow
Step 1: Read the exact unknown directive
sudo nginx -t
Note the quoted directive and file:line. First decide: is it a typo, or a real directive from a module? Check the spelling against the official directive index before assuming a module problem.
Step 2: Check whether the binary even supports it
nginx -V 2>&1 | tr ' ' '\n' | grep -iE 'module|brotli|headers-more|geoip'
ls /etc/nginx/modules/ /usr/lib/nginx/modules/ 2>/dev/null
If the directive’s module is not in nginx -V and not present as a .so, the binary cannot support it — you need a package/build that includes it (cause 3). If the .so exists but is not loaded, it is cause 2.
Step 3: Load the module correctly (if it exists on disk)
Add load_module in the main context (top of nginx.conf, before events/http), using the exact path for your version:
# nginx.conf — main context, at the very top
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
events { worker_connections 1024; }
http {
brotli on; # now recognized
brotli_types text/plain application/javascript text/css;
}
Step 4: Fix typos and context, then validate
grep -RnE 'proxy_set_headers|limit_req_zon|brotli|more_set_headers' /etc/nginx/
sudo nginx -t && sudo systemctl reload nginx
Example Root Cause Analysis
A config that enables Brotli compression fails to reload after being copied from a host that had an NGINX Plus / custom build:
nginx: [emerg] unknown directive "brotli" in /etc/nginx/conf.d/gzip.conf:3
First, confirm whether the directive is a typo or a module directive — brotli is spelled correctly, so it is a module. Check what the binary supports:
nginx -V 2>&1 | tr ' ' '\n' | grep -i brotli
ls /etc/nginx/modules/ | grep -i brotli
# nginx -V: (no brotli output)
ngx_http_brotli_filter_module.so
ngx_http_brotli_static_module.so
The module .so files are present on disk, but nginx -V shows they are dynamic and were never loaded — the config uses brotli without a load_module. Add the loads in the main context:
# top of /etc/nginx/nginx.conf, before events{}
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
sudo nginx -t && sudo systemctl reload nginx # test passes, brotli active
Had the .so files been absent and nginx -V shown no brotli support, the fix would instead be installing an NGINX package built with the module — no load_module can enable a directive the binary does not ship.
Prevention Best Practices
- Verify a directive’s spelling and required module against the official directive reference before deploying — a typo and a missing module look identical in the log.
- Run
nginx -Von each target host to know exactly which modules are compiled in; do not assume configs are portable across differently built NGINX packages. - Load dynamic modules with
load_modulein the main context at the top ofnginx.conf, before thehttp/eventsblocks that use their directives. - Keep third-party-module directives (
brotli,more_set_headers,geoip2) in clearly named include files so their module dependency is obvious to the next editor. - Always
nginx -tbefore reload; the emerg names the exact directive and file:line, making the fix fast.
Quick Command Reference
# Identify the unknown directive and location
sudo nginx -t
# What modules is this binary built with / does it have on disk?
nginx -V 2>&1 | tr ' ' '\n' | grep -iE 'module|brotli|headers-more|geoip'
ls /etc/nginx/modules/ /usr/lib/nginx/modules/ 2>/dev/null
# Find where the directive is used
grep -RnE 'brotli|more_set_headers|proxy_set_headers|limit_req_zon' /etc/nginx/
# After adding load_module or fixing the typo
sudo nginx -t && sudo systemctl reload nginx
Conclusion
unknown directive means the running NGINX binary cannot parse that word — either a typo or a directive whose module is unavailable. The usual root causes:
- A misspelled or mis-cased directive (
proxy_set_headers,limit_req_zon). - A dynamic module present on disk but never
load_moduled. - A module not compiled into the binary at all (needs a different package/build).
- A directive placed in the wrong context.
- A
load_modulein the wrong context or with a bad path.
Read the quoted directive, decide typo vs module with the directive reference and nginx -V, then either fix the spelling, add load_module in the main context, or install an NGINX build that ships the module — and always nginx -t before reloading.
Fixed it? Get 500 NGINX & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.