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 IoError unless 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, // e.g., ["443", "ssl", "http2"]
pub children: Vec, // 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:
1. /etc/nginx/nginx.conf (Linux default)
2. /usr/local/etc/nginx/nginx.conf (FreeBSD, Homebrew Intel)
3. /opt/homebrew/etc/nginx/nginx.conf (Homebrew Apple Silicon)
4. $NGINX_CONF environment variable (custom path)
It also detects whether nginx is currently running by checking PID files at /run/nginx.pid and /var/run/nginx.pid.