1
1
Fork
You've already forked gnix
2
A simple stupid reverse proxy
  • Rust 99.3%
  • HTML 0.7%
Find a file
2026年07月09日 17:06:58 +02:00
src extract http transport code from main.rs to files 2026年07月09日 17:06:58 +02:00
.gitignore
Cargo.lock experimental websocket to tcp proxy 2026年07月08日 23:53:06 +02:00
Cargo.toml experimental websocket to tcp proxy 2026年07月08日 23:53:06 +02:00
comparison.md proxy parse from string 2026年07月08日 22:00:56 +02:00
COPYING
readme.md experimental websocket to tcp proxy 2026年07月08日 23:53:06 +02:00
rust-toolchain

gnix

a simple stupid reverse proxy

Features

  • HTTP/1.1, HTTP/2 and HTTP/3 support by default
  • Simple and predictable configuration (see below)
  • Somewhat fast (about 150k req/s on a rpi5 for simple routes)
  • Composable modules
  • Handles connection upgrades correctly by default (websocket, etc.)
  • TLS support with automatic certificate selection
  • Configuration hot-reloading
  • Client authentification (http basic auth, form + cookie)

Installation

On Arch Linux use gnix from the AUR. Otherwise install rustup, then run cargo install --git https://codeberg.org/metamuffin/gnix --tag v2.4.1.

Quick Start

Run the binary with the a path to the configuration as the first argument. The configuration file is written in YAML and could look like this:

http:bind:"[::]:80"https:bind:"[::]:443"cert_path:"/etc/letsencrypt/live"# Scans directory and selects a valid certificate based on hostnamehandler:!hosts# !hosts routes requests for different hostnames.# requests for `example.org` are forwarded to 127.0.0.1:8000."example.org": !proxy { backend: "127.0.0.1:8000" }# requests for `mydomain.com` will access files from /srv/http."mydomain.com": !filesroot:"/srv/http"index:true# requests for `panel.mydomain.com` will be logged, authentificated and passed on to `otherserver`."panel.mydomain.com": !access_logfile:"/var/log/gnix/panel"next:!cookie_authusers:"/etc/panel_creds"expire:86400fail:!file { path: "/usr/share/gnix/login.html" }next:!proxy { backend: "otherserver:80" }

Command-line Option Reference

Usage: gnix [OPTIONS] <CONFIG>
Arguments:
 <CONFIG> Path to main configuration file in YAML format
Options:
 -u, --user <USER>
 Switch to specified user after loading configuration and certificates
 -s, --control-socket <CONTROL_SOCKET>
 Create/Use a control socket to reload configuration with
 -w, --watch
 Watch the configuration file and reload on changes. Can be used with --reload
 -r, --reload
 Reload configuration of another running instance. Requires --control-socket option
 -h, --help
 Print help

--reload and --user are only supported on UNIX platforms.

Configuration Reference

The configuration uses YAML formatting. When the configuration file is changed, it will automatically be loaded and applied if valid. Configuration errors are reported in stderr and are only fatal at startup.

  • section http

    • Optional section. Omit to disable unencrypted http.
    • bind: Addresses to accept http requests on (string or list of strings).
  • section https

    • Optional section. Omit to disable https.
    • bind: Addresses to accept https requests on (string or list of strings).
    • cert_path: Path to a directory structure that certificates are loaded from. The hierachy should contain directories containing corresponding fullchain.pem and privkey.pem files. The correct certificate is selected automatically by subject (CN). Pointing this directly at /etc/letsencrypt/live is possible. (string or list of strings)
    • cert_fallback: Path to a single directory containing a key-cert pair that is used when no other certificate seems appropriate. This is useful for testing locally with a self-signed subjectless certificate. (optional string)
    • disable_h1: Stops advertising HTTP/1.1 support but still continues to support it. Default: false
    • disable_h2: Stops advertising HTTP/2 support. Default: false
    • disable_h3: Disables HTTP/3 support. Default: false
  • section limits

    • Note: Make sure you do not exceed the maximum file descriptor limit on your platform.
    • max_incoming_connections: Maximum number of incoming (downstream) connections over TCP transport. Excess connections are rejected. Default: 512
    • max_incoming_connections_h3: Maximum number of connections via HTTP/3 where connections are cheaper due to reuse of a single UDP socket. Default: 4096
    • max_requests_per_connnection: Maximum number of parallel streams/requests per HTTP/3 connection. Default: 16
    • max_outgoing_connections: Maximum number of maximum outgoing (upstream) connections. Excess connections are rejected. Default: 256
  • section handler

    • A module to handle all requests. Usually an instance of hosts or paths.
  • source_ip_from_header: Uses the X-Real-IP header for source ip address, requests without it will be rejected. Useful when gnix is behing another proxy.

  • private_key: An array of 32 random 8-bit numbers used for cryptographic purposes such as encrypting cookies. If not specified these are randomly choosen at startup which results in all cookie auth session to be invalid after a restart.

  • alpn: Maps from ALPN protocols for proxying (map from string to endpoint addr)

Modules

Modules handle requests. Some of them have arguments which are modules themselves; in that case the request is passed on.

  • module hosts

    • Hands over the requests to different modules depending on the host header. Special keys are :fallback which handles hosts that are not found in the map and :none to handle requests without a host header.
    • Takes a map from hostname (string) to handler (module)
  • module paths

    • Routes requests by matching the path against regexes. The first matching regex determines the module to handle this request. If the regex has an unnamed capture group, its value will become the downstream request URI.
    • Takes a map from regex (string) to handler (module) OR a sequence of such maps. The priority is assigned first by position in the sequence then alphabetically in the map.
  • module proxy

    • Forwards the request as-is to some other server. the x-real-ip header is added to the request. Connection upgrades are handled by direct forwarding of network traffic. Can either be specified as string directly if only backend field is required.
    • backend: IP socket address or absolute path to unix socket of the backend server. (string, endpoint address)
    • set_real_ip: Sets the X-Real-IP header. (boolean)
    • set_forwarded_for: Sets the X-Forwarded-For, X-Forwarded-Host, X-Forwaded-Proto, X-Forwarded-Scheme and X-Forwarded-Port headers. (boolean)
  • module files

    • Provides a simple built-in fileserver. The server handles accept-ranges. The content-type header is inferred from the file extension and falls back to application/octet-stream. If a directory is requested index.html will be served or else when indexing is enabled, index.banner.html will be prepended to the response.
    • root: root directory to be served (string)
    • index: enables directory indexing (boolean)
    • localize: sends a localized version if possible. for localized content the ISO 639-1 language code is injected in front of the first file name extension (boolean) semi-experimental
  • module file

    • Basic HTTP response with custom body.
    • path: File path to the content.
    • preload: Load file into memory at startup. (boolean)
    • content: inline declaration of the content. (string)
    • type: type of content reflected in content-type header. (string)
    • status: returned HTTP status code (int)
  • module access_log

    • Logs requests to a file.
    • file: file path to log (string)
    • reject_on_fail: rejects requests if log could not be written (boolean)
    • flush: flushes log on every request (boolean)
    • next: module for further handling of the request (module)
  • module error

    • Rejects every request with a custom error message.
    • Takes an error message (string)
  • module http_basic_auth

    • Filters requests via HTTP Basic Authentification. Unauthorized clients will be challenged.
    • realm: describes what the user is logging into (most modern browsers dont show this anymore -_-)
    • users: list of valid logins (credentials)
    • next: a module to handle this request on successfully authentificated. (module)
  • module cookie_auth

    • Authentificates a client based on cookies. The cookies are set on login by a POST request to /_gnix_login with form fields password and username (optional, default: "user") in x-www-form-urlencoded format. In any case the users submitting this request will be directed back to the page they come from. For successful logins two cookies are set: gnix_username containing the username and gnix_auth containing an opaque authentification token. The gnix_username cookie is authentificated by gnix and can therefore be used by applications. Alternatively a login may be implemented by returning the gnix-auth-success header with a username as the value from the fail handler, which is handled like a sucessful login for that user. If gnix-auth-no-redirect is set the response of the fail handler is passed instead of a redirect being sent. This method can be useful for implementing custom login logic like OTP login or a CAPTCHA.
    • users: list of valid logins (credentials)
    • expire: seconds before logins expire; not setting this option keeps the login valid forever on the server but cleared after the session on the client (optional number)
    • secure: makes the cookies accessable from secure contexts exclusively i.e. HTTPS (boolean)
    • next: a module to handle this request on successfully authentification. (module)
    • fail: a module to handle the request when a user is not authorized. This could show an HTML form prompting the user to log in. An implementation of such a form is provided with the distribution of this software, usually in /usr/share/gnix/login.html. It can return the gnix-auth-success header, see above. (module)
  • module switch

    • Decides between two possible routes based on a condition.
    • condition:
      • !is_websocket_upgrade: Checks if a websocket was requested.
      • !is_{get,post,put,patch,options}: Checks if this is a GET/... request
      • !path_starts_with <prefix>: Checks if the URI path starts with some prefix
      • !path_is <path>: Checks if the URI path is exactly what you specified
      • !has_header <name>: Checks if the request includes a certain header.
      • !http_version [n]: Checks for HTTP version n where HTTP/0.9 is considered 1 aswell.
      • !any [conditions]: Checks if any of a set of conditions are satisfied.
      • !all [conditions]: Checks if all conditions are satisfied.
    • case_true Handler with matched requests (module)
    • case_false Handler for all other requests (module)
  • module headers

    • Appends multiple headers to the response.
    • request: A map of all header name-value pairs added before the request is passed to next. (object string:string)
    • response: A map headers added to responsed from next. (object string:string)
    • next: The handler to add the headers to. (module)
  • module redirect

    • Responds with a permanent redirect.
    • Takes the location to redirect to. (string)
  • module cgi

    • Runs a CGI script on the request.
    • bin: Path to the CGI binary (string)
    • user: User that the script is executed as. Requires to run gnix as root. (optional string) (unix only)
    • args: List of arguments. (list of string)
    • env: Environment variables. (map from string to string)
    • pwd: Working directory path. (optional string)
  • module cache

    • Caches requests. This is experimental! Don't use this.
    • Not documented yet
  • module loadbalance

    • Balances load across multiple modules (presumably proxied backends). The current implementation selects the least busy handler for each request and does not scale well to many modules. This is experimental! Don't use this.
    • Takes a set of handlers. (sequence of module)
  • module fallback

    • Runs a series of handlers until one returns a success, informational or redirect response and the returns that. The last handler's response will unconditionally be returned. Note that to run multiple handlers the request body will first be completely received to memory before any handler is run. There is currently no size limit for request bodies, so be careful using this. TODO request size limit
    • Takes a sequence of handlers.
  • module limits

    • Limits size and transmission rate of request and response bodies. The limit is enforced on an internal data frame level and does therefore not exactly reach the specified limits, but never exceeds them.
    • {request,response}.rate Maximum transmission rate in bytes per second. (number)
    • {request,response}.rate_buffer How much transmission time can be accumulated by not reading for some time in milliseconds. (number)
    • {request,response}.rate_buffer_filled If the rate buffer is filled up initially. (boolean)
    • {request,response}.size Maximum total body size. The body is cut off before the frame that exceeds this limit. Therefore the body is up to one frame size smaller than allowed.
    • next: Inner handler. (module)
  • module ratelimit

    • Limits the rate at which requests can be processed. For this every identity (see below) has a request counter. The counter is reset after a fixed time delay.
    • reference_duration: Duration in seconds after which request the counter are reset.
    • identity: Requests are counted per identity. Default is source address.
      • !global: Use a central counter
      • !source_address: Count per source ip address
      • !source_address_trunc: Same but truncate them before. Requires keys v4 and v6 which control how many trailing bits are discarded respectively.
      • !path: Count per path (excluding query)
      • !path_query: Count per path (including query)
    • max_identities: Always rejects requests if there are already more than this many identites tracked.
    • thresholds: A list of [threshold, mode] pairs that are checked and conditionally executed in order.
      • !too_many_requests: Responds with a empty request with 429 status code and Retry-After header set. Later thresholds are not checked.
      • !exec <path>: Invokes a script like CGI would but expects no output.
    • next: Inner handler. (module)
  • module semaphore

    • Limits the number of requests processed in parallel
    • permits: Permit count (number)
    • next: Inner handler. (module)
  • module debug

    • Replies with information about the request to debug. Includes source address, HTTP version, method, URI and headers.
  • module upgrade_insecure

    • Redirects requests from HTTP to HTTPS when Upgrade-Insecure-Requests is present in the request. The redirect URI's authority will be the Host header of the request. Do not use behind another proxy because requests will always be HTTP and result in an endless redirect loop.
    • next: Inner handler. (module)

Credentials config format

Login credentials for cookie_auth and http_basic_auth are supplied as either an object mapping usernames to PHC strings or a file path pointing to a file that contains that map in YALM format. Currently only argon2id is supported.

Additional Notes

Internally gnix processes requests as they would be sent in HTTP/1.1. HTTP/2 and HTTP/3 is translated on arrival.

Paths matching /_gnix* might be used internally in gnix for purposes like OpenID callback or login action endpoints. I hope your application doesn't rely on using them for itself.

License

AGPL-3.0-only; see COPYING