xin

Configuration language

Bring your nginx config. Xin reads the same block-and-directive language, including includes, variables, inheritance and nginx's location-selection rules.

Configuration file structure

A xin configuration is a tree of directives. A simple directive ends with a semicolon. A block directive contains more directives between braces. The root of the file is the main context.

worker_processes auto;

events {
    worker_connections 4096;
}

http {
    include /etc/nginx/mime.types;
    server {
        listen 8080;
        location / { return 200 "hello\n"; }
    }
}
ContextPurposeCommon children
mainProcess-wide configurationevents, http, stream
eventsConnection-worker settingsworker_connections
httpDefaults for HTTP serversserver, upstream, map, geo
serverA virtual HTTP serverlisten, server_name, location
locationURI matching and request handlingcontent, proxy, access, limit and filter directives
upstreamA named backend poolserver, load-balancing policy, keepalive
streamTCP proxyingserver, upstream, map

Tokens, comments and quoting

Spaces, tabs and newlines separate tokens. A # outside quotes starts a comment through the end of the line. The characters ;, { and } terminate directives or blocks when they are not quoted or escaped.

Single and double quotes keep whitespace inside one argument. A backslash escapes the following character. Quote an argument when it contains whitespace, a semicolon, braces, a leading #, or a literal dollar sign that must not begin a variable.

# one argument after the status code
return 200 "hello from xin
";

# a literal dollar sign
add_header X-Price "\$10";

Includes and the configuration prefix

include accepts a file path or glob. Absolute paths are used as written. Relative paths are resolved from the active configuration prefix. The Public Beta parses nginx's -p CLI option for command-line compatibility but does not yet apply it. Glob matches are read in lexical order. Includes can contain more includes; cycles are rejected with the full chain.

http {
    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
}

Contexts and inheritance

Every directive has a fixed set of valid contexts. Using one elsewhere is a configuration error. Values flow from broader contexts into narrower ones: httpserverlocation → nested location or if.

Most scalar directives inherit until a child defines its own value. Lists usually inherit as a whole: the first directive at a child level replaces the complete parent list. This matters for directives such as proxy_set_header, add_header and access rules—repeat every entry the child needs.

http {
    proxy_set_header Host $host;
    proxy_set_header X-Request-ID $request_id;

    server {
        # Defining one proxy_set_header here replaces the inherited set.
        proxy_set_header Host internal.example;
    }
}

Sizes, times and rates

Size arguments accept bytes or a case-insensitive k, m or g suffix. Time arguments accept milliseconds (ms), seconds (s), minutes (m), hours (h), days (d) and weeks (w); a bare time value is seconds where nginx defines it that way. Rate directives accept the units named on their reference page.

client_max_body_size 20m;
proxy_connect_timeout 3s;
proxy_read_timeout 1m;
limit_rate 512k;

Variables

Variables begin with $ and are evaluated for each request. Use $name or ${name} when text immediately follows the name. Built-ins expose the request, selected route, connection, TLS state and upstream result. Header variables use $http_ for request headers, $sent_http_ for response headers and $upstream_http_ for upstream response headers; header names are lowercased and dashes become underscores.

set assigns a value in rewrite processing. map, geo and split_clients declare lazily evaluated variables at http scope. Regular-expression captures are available as $1 through $9 and by named capture.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

How a virtual server is selected

Xin first selects the listening address and port. The default_server for that socket handles the request unless server_name produces a better match. Name matching uses nginx order: exact name, longest leading wildcard, longest trailing wildcard, then regular expressions in configuration order. Names are compared case-insensitively after host normalization.

How a location is selected

  1. An exact location = /path match wins immediately.
  2. Xin remembers the longest matching prefix location, descending through nested prefix locations.
  3. If the remembered prefix uses ^~, it wins without testing regular expressions.
  4. Regular-expression locations are tested in configuration order. The first match wins.
  5. If no regular expression matches, the longest remembered prefix wins.

Named locations such as @app are not selected from the request URI. They are targets for internal redirects from directives including try_files, error_page and upstream retry handling.

URIs, arguments and internal redirects

Location matching uses the normalized URI path, not the query string. $request_uri keeps the original path and arguments; $uri is the current normalized URI and can change during internal redirects. $args contains the current query string.

rewrite ... last, try_files, index, error_page and X-Accel-Redirect can restart URI and location processing internally. Xin bounds redirect loops and reports the failure instead of recursing indefinitely.

Filesystem paths: root, alias and try_files

root appends the URI to a directory. alias replaces the matched location prefix. try_files checks candidates using the active root or alias; only its final argument is a URI, named location or status fallback.

location /assets/ {
    root /srv/www;      # /assets/app.css -> /srv/www/assets/app.css
}

location /downloads/ {
    alias /srv/files/;  # /downloads/a.zip -> /srv/files/a.zip
}

location / {
    try_files $uri $uri/ @application;
}

Reverse proxying and URI replacement

When proxy_pass names an upstream without a URI, the current request URI is forwarded. When it includes a URI, the part matched by the normalized location is replaced. A variable in proxy_pass changes these rules; consult the directive page before using a dynamic target.

location /api/ {
    proxy_pass http://backend/;   # /api/users -> /users
}

location /raw/ {
    proxy_pass http://backend;    # /raw/users -> /raw/users
}

Request headers are controlled with proxy_set_header; an empty value suppresses a header. Buffering, request buffering, connect/send/read timeouts, retry policy, upstream TLS verification, response-header handling and cookie rewriting are independently configurable.

Upstream groups

An upstream block gives a backend pool a name. Servers can have weights, passive failure thresholds, failure windows, backup status and administrative down status. The default policy is weighted round robin; least_conn, ip_hash, hash and random select alternatives. keepalive retains idle upstream connections per worker.

upstream application {
    least_conn;
    server 10.0.0.8:8080 weight=2 max_fails=3 fail_timeout=10s;
    server 10.0.0.9:8080;
    server 10.0.0.10:8080 backup;
    keepalive 32;
}

TLS

Enable TLS with listen ... ssl, then provide ssl_certificate and ssl_certificate_key. Multiple certificate pairs support RSA and ECDSA selection. ssl_protocols, explicit cipher suites, client certificates, session caches, tickets, OCSP stapling and ALPN are configurable.

The default package uses rustls with AWS-LC, a cryptographic library derived from BoringSSL. Xin does not claim that its entire TLS stack is written in memory-safe Rust. The request and configuration engines enforce their own safe-code boundaries; the memory-safety page documents the boundary precisely.

Content handlers and filters

A location normally chooses one primary content handler: static files, return, HTTP proxy, FastCGI, uwsgi, SCGI, gRPC, autoindex, empty GIF or another compiled feature. Response filters then apply headers, ranges, conditional requests, substitutions, addition bodies and gzip according to their status and content-type rules.

Access, authentication and limits

allow/deny, HTTP Basic auth, subrequest auth, secure links and referer checks run in the access phase. satisfy any or satisfy all combines access modules. Request-rate and connection limits use shared zones declared at http scope and applied in a server or location. Body size, header/body timeouts, response rate and keepalive limits are separate controls.

Logging

log_format declares a format at http scope. access_log selects the path and format and can be disabled or made conditional. Variables are rendered after request processing so status, bytes and upstream timing are available. error_log selects the destination and minimum severity.

Stream TCP configuration

The stream context configures TCP listeners and upstream pools independently from HTTP. It supports address-based access control, proxying, load balancing, timeouts, logging, PROXY protocol and TLS ClientHello inspection with ssl_preread. UDP and the nginx mail module are not part of the Public Beta.

Validation and unsupported directives

Unknown directives, invalid contexts, wrong argument counts, invalid values and feature-gated directives fail configuration loading with a source file and line number. Xin does not silently accept a directive it cannot apply. See configuration compatibility for the current supported families, refused modules and known beta differences.

Testing before running

xin -t -c /etc/nginx/nginx.conf
xin -T -c /etc/nginx/nginx.conf

-t parses and validates without starting listeners. -T also prints every resolved configuration file. Configuration validation cannot exercise every request-time branch, so run representative traffic on a spare port before switching a production listener.