Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for NGINX By James Joyner IV · · 9 min read Last reviewed Jul 2026

NGINX Error Guide: 'unknown directive' — fix typos, missing modules, and misplaced context

Quick answer

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
Free toolkit

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 -t fails with [emerg] unknown directive "X".
  • systemctl reload nginx fails; 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 -V on 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_module in the main context at the top of nginx.conf, before the http/events blocks 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 -t before 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:

  1. A misspelled or mis-cased directive (proxy_set_headers, limit_req_zon).
  2. A dynamic module present on disk but never load_moduled.
  3. A module not compiled into the binary at all (needs a different package/build).
  4. A directive placed in the wrong context.
  5. A load_module in 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.

Free download · 368-page PDF

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?

Free download · 368-page PDF

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.