nginx Config Parser
bext includes a complete nginx configuration parser written in Rust. It tokenizes, parses, and builds an abstract syntax tree (AST) from any valid nginx.conf.
Grammar
The nginx config format is a simple recursive structure:
config ::= directive*
directive ::= word+ (';' | '{' directive* '}')
A directive is one or more words followed by either a semicolon (simple directive) or a brace-delimited block containing child directives.
Lexer
The hand-written tokenizer produces five token types:
| Token | Description |
|---|---|
Word |
Unquoted or quoted string value |
OpenBrace |
{ |
CloseBrace |
} |
Semicolon |
; |
Eof |
End of input |
Quoting Rules
- Double quotes — escape sequences supported:
\",\\ - Single quotes — literal content, no escaping
- Unquoted — delimited by whitespace, braces, or semicolons
- Comments —
#to end of line, with line number tracking
# Both of these are equivalent:
server_name "my-site.example.com";
server_name my-site.example.com;
# Quotes needed for special characters:
rewrite "^/old/(.*)$" "/new/$1" permanent;
Include Expansion
The parser resolves include directives inline during parsing:
http {
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Behavior:
- Relative paths are resolved against the including file's directory
- Glob patterns (
*.conf,sites-enabled/*) are expanded - Max depth — 16 levels of nesting to prevent circular includes
- No-match includes — non-fatal; logged as a warning
- Missing files — return an
IoErrorunless the include uses a glob pattern
AST Structure
The parser produces a tree of Directive nodes:
pub struct Directive {
pub name: String, // e.g., "server", "listen", "proxy_pass"
pub args: Vec<String>, // e.g., ["443", "ssl", "http2"]
pub children: Vec<Directive>, // block contents (empty for simple directives)
pub file: String, // source file path
pub line: usize, // line number in source
pub column: usize, // column number
}
The top-level NginxConfig wrapper provides convenience methods:
config.http_block() // -> Option<&Directive> — the http {} block
config.server_blocks() // -> Vec<&Directive> — all server {} blocks
config.upstream_blocks() // -> Vec<&Directive> — all upstream {} blocks
config.get_directive("worker_processes") // -> Option<&Directive>
Error Handling
The parser reports six error types with file and line information:
| Error | Description |
|---|---|
UnexpectedToken |
Syntax error — unexpected token in stream |
UnterminatedString |
Quote opened but never closed |
EmptyDirective |
Block or simple directive with no name |
IncludeDepthExceeded |
More than 16 levels of include nesting |
MissingIncludePath |
include with no path argument |
IoError |
File read failure |
Usage
use bext_nginx_compat::{parse_and_convert, parse_and_convert_string};
// Parse from a file path (resolves includes)
let config = parse_and_convert("/etc/nginx/nginx.conf")?;
// Parse from a string (no include resolution)
let config = parse_and_convert_string(raw_config)?;
// Auto-detect nginx installation and parse
let config = bext_nginx_compat::detect_and_convert()?;
Detection
bext automatically searches for nginx configs at:
/etc/nginx/nginx.conf(Linux default)/usr/local/etc/nginx/nginx.conf(FreeBSD, Homebrew Intel)/opt/homebrew/etc/nginx/nginx.conf(Homebrew Apple Silicon)$NGINX_CONFenvironment variable (custom path)
It also detects whether nginx is currently running by checking PID files at /run/nginx.pid and /var/run/nginx.pid.
Non-matching glob include directives (e.g. include sites-enabled/* on an empty directory) are treated as warnings, not errors. Missing non-glob files are fatal — check the error output if a vhost goes missing after parse.
Related
- Directive Coverage — every directive the converter understands
- nginx Compatibility Overview — the three-stage parse → convert → serve pipeline
- Preflight Dashboard — visual view of what the parser detected
- Migration Guide — step-by-step from nginx to bext
- Architecture Overview — where the parser fits in the wider request lifecycle