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)
Links
- AUR Package
- Matrix Room for questions, announcements and general discussion around the software.
Installation
On arch use gnix
from the AUR; Otherwise install rustup
, then run
cargo install --git https://codeberg.org/metamuffin/gnix --tag v2.x.x
.
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 hostname
handler: !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": !files
root: "/srv/http"
index: true
# requests for `panel.mydomain.com` will be logged, authentificated and passed on to `otherserver`.
"panel.mydomain.com": !access_log
file: "/var/log/gnix/panel"
next: !cookie_auth
users: "/etc/panel_creds"
expire: 86400
fail: !file { path: "/usr/share/gnix/login.html" }
next: !proxy { backend: "otherserver:80" }
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 correspondingfullchain.pem
andprivkey.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: falsedisable_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
number of maximum incoming (downstream) connections over TCP transport. excess connections are rejected. Default: 512max_incoming_connections_h3
same but for HTTP/3 where connections are cheaper due to reuse of a single UDP socket. Default: 4096-
max_outgoing_connections
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
orpaths
. -
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. watch_config
: boolean if to watch the configuration file for changes and apply them accordingly. Default: true (Note: This will watch the entire parent directory of the config since most editors first move the file. Currently any change will trigger a reload. TODO)
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. backend
: IP socket address or absolute path to unix socket of the backend server. (string)set_real_ip
: Sets theX-Real-IP
header. (boolean)-
set_forwarded_for
: Sets theX-Forwarded-For
,X-Forwarded-Host
,X-Forwaded-Proto
,X-Forwarded-Scheme
andX-Forwarded-Port
headers. (boolean) -
module
files
- Provides a simple built-in fileserver. The server handles
accept-ranges
. Thecontent-type
header is inferred from the file extension and falls back toapplication/octet-stream
. If a directory is requestedindex.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
- Replies with static content.
path
: file path to the content. This will be loaded into memory at startup! (string)content
: inline declaration of the content. (string)-
type
: type of content reflected incontent-type
header. (string) -
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 fieldspassword
andusername
(optional, default: "user") inx-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 andgnix_auth
containing an opaque authentification token. Thegnix_username
cookie is authentificated by gnix and can therefore be used by applications. Alternatively a login may be implemented by returning thegnix-auth-success
header with a username as the value from thefail
handler, which is handled like a sucessful login for that user. Ifgnix-auth-no-redirect
is set the response of thefail
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 thegnix-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 tonext
. (object string:string)response
: A map headers added to responsed fromnext
. (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)args
: List of arguments. (list of string)-
env
: Environment variables. (map from string to 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. -
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 keysv4
andv6
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 andRetry-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 theHost
header of the request. Do not use behind another proxy because requests will always be HTTP and result in an endless redirect loop. - Takes a single module (as a single-element array because of a parser limitation)
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