Experimental nginx-compatible HTTP server and reverse proxy written in Rust
See the codeAn experimental nginx-compatible HTTP server and reverse proxy written from scratch in Rust.
ruxen explores how nginx's architecture and configuration semantics can be reimplemented in idiomatic Rust — without translating the C code line by line.
It is Linux-only, built around a thread-per-core model using monoio and io_uring, and currently targets HTTP/1.1.
[!WARNING] ruxen is an active prototype. It is not production-ready yet.
nginx has decades of engineering behind its event loop, HTTP state machine, configuration system, request routing, and upstream handling.
The goal of ruxen is to study those ideas and reproduce their behaviour using Rust's ownership model and type system rather than copying nginx's C implementation.
Three things guide the project:
nginx-tests suite, not only project-specific tests;See DESIGN.md for the architecture notes and the reasoning behind individual design decisions.
The v0.1 scope is deliberately limited:
io_uring via monoioThe upstream nginx-tests suite is used as a compatibility test. 63 of the 105 test files that ruxen currently opts into pass end-to-end (v0.1.1).
See NGINX_TEST_PROGRESS.md for the per-file status.
io_uring available. ruxen is developed and tested on kernels 6.x–7.0. io_uring must not be turned off with the kernel.io_uring_disabled sysctl.cargo install): Rust 1.88 or newer, plus cmake and a C compiler for the aws-lc-rs crypto backend that rustls uses. aws-lc refuses GCC 9 (Your compiler (cc) is not supported due to a memcmp related bug); on Ubuntu 20.04, install gcc-10 g++-10 and build with CC=gcc-10 CXX=g++-10.curl to try the server.Prebuilt binary from GitHub Releases:
| tarball | runs on |
|---|---|
ruxen-x86_64-linux-gnu.tar.gz | x86_64 with glibc 2.35 or newer (Ubuntu 22.04, Debian 12 and later) |
ruxen-aarch64-linux-gnu.tar.gz | ARM64 with glibc 2.35 or newer (Graviton, Ampere, Raspberry Pi 5 on a 64-bit OS) |
ruxen-x86_64-linux-musl.tar.gz | any x86_64 Linux, static (Alpine, older glibc). See the release notes for its performance. |
curl -LO https://github.com/gvozdetsky/ruxen/releases/latest/download/ruxen-x86_64-linux-gnu.tar.gz
tar xzf ruxen-x86_64-linux-gnu.tar.gz
cd ruxen-v*-x86_64-linux-gnu
./ruxen -V
On a glibc older than 2.35 the gnu builds fail with version `GLIBC_2.34' not found: use the musl build, or build from source. Each release also has the tarballs under versioned names (ruxen-v<version>-<target>.tar.gz) and a SHA256SUMS file.
From crates.io:
cargo install ruxen --locked
From source:
git clone https://github.com/gvozdetsky/ruxen.git
cd ruxen
cargo build --release
# the binary is target/release/ruxen
In Docker, the default seccomp profile blocks io_uring, so ruxen cannot create its io_uring runtime. Run the container with --security-opt seccomp=unconfined (or a profile that allows the io_uring_* syscalls).
ruxen does not switch to an unprivileged user the way nginx's user directive does, so it refuses to start as root unless the configuration says user root;. Containers usually run as root: either add user root; or run the container with --user.
examples/minimal.conf is the smallest useful configuration:
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
server_name _;
location / {
return 200 "hello from ruxen\n";
}
}
}
Check the configuration, then start the server in the foreground:
ruxen -t -c examples/minimal.conf
ruxen -c examples/minimal.conf
As with nginx, a valid configuration prints … syntax is ok and … test is successful (on stderr; -q silences them) and exits with status 0.
Then, from another terminal:
curl -i http://127.0.0.1:8080/
You should get a 200 response with:
hello from ruxen
examples/static.conf serves files from a directory and examples/proxy.conf proxies to two backends with keep-alive upstream connections.
You are now serving an nginx-style configuration with ruxen.
listen addressesserver_name exact, wildcard and regex matchinglocation matching:
=)^~~, ~*)internal locationslisten port ranges and listen … proxy_protocol (PROXY protocol v1 and v2, $proxy_protocol_*)worker_connections, client_header_timeout, client_body_timeout, send_timeoutclient_max_body_size, spilled to private temp files (client_body_temp_path), with an early 413limit_rate, limit_rate_after and $limit_rateSupported functionality includes:
rootaliasindextry_filesLast-Modifiedsendfile on|off (plain TCP)autoindexruxen supports a growing subset of nginx's proxy functionality, including:
proxy_passupstream blocksleast_connmax_fails / fail_timeoutproxy_http_versionproxy_set_headerproxy_set_bodyproxy_next_upstream (including _tries / _timeout)proxy_intercept_errorsproxy_redirect, including the implicit defaultproxy_hide_header / proxy_pass_headerX-Accel-Redirect, X-Accel-Limit-Rate and proxy_ignore_headers$upstream_* per-attempt variables, $proxy_host, $proxy_portTLS termination uses rustls.
Currently supported:
ssl_certificatessl_certificate_keyssl_session_cache (in memory, shared by the workers), ssl_session_tickets, ssl_session_timeout$ssl_* variablesALPN currently advertises HTTP/1.1 only.
A growing nginx-compatible configuration surface is implemented, including:
returnadd_headeradd_trailerexpireserror_page and recursive_error_pagespost_actionsetifrewritemap (including hostnames)split_clientsauth_basicauth_basic_user_filelog_formataccess_log and error_log, including syslog: targetsMany common nginx variables are also available, including request, response, upstream, TLS, cookie, header and rewrite variables.
The currently implemented nginx-style command-line surface includes:
-c
-p
-e
-g
-t
-T
-q
-V
-s stop | quit | reopen
-V starts with nginx version: nginx/1.29.2, followed by ruxen version: ruxen/<version>. The nginx line and the configure arguments list are what the nginx-tests harness reads to decide which tests apply, so they describe ruxen as an nginx build: the arguments list only the modules ruxen actually implements.
Responses identify ruxen itself: Server: ruxen/<version> (plain ruxen with server_tokens off), and the same name in the footer of built-in error pages. scripts/run_nginx_tests.sh sets RUXEN_NGINX_IDENTITY=1, which makes them say nginx/1.29.2 instead, because the upstream tests assert nginx's header.
ruxen also supports pid files, graceful shutdown via SIGQUIT (waits for in-flight requests), fast shutdown via SIGTERM or SIGINT (does not wait; the pid file is removed either way) and log reopening via SIGUSR1. -s stop|quit|reopen send SIGTERM/SIGQUIT/SIGUSR1 to the PID in the config's pid file, as nginx does. -s reload is refused: ruxen can't re-read its configuration yet, so restart it instead.
ruxen is not a line-by-line Rust port of nginx.
The approach is:
understand why nginx does something, then implement that behaviour in a way that makes sense in Rust.
For example, nginx uses memory pools, pointer-based configuration structures and arrays of function pointers because those are natural solutions in C.
ruxen does not reproduce those mechanisms where Rust already provides safer or simpler alternatives.
The aim is behavioural and architectural compatibility where it matters — not source-code similarity.
More detail is in DESIGN.md.
Run the Rust unit and integration tests:
cargo test --release
The HTTPS integration tests use the system curl binary and generate temporary certificates at runtime.
ruxen can also run the upstream nginx Perl test suite.
From the repository root:
scripts/run_nginx_tests.sh
Run only selected groups:
scripts/run_nginx_tests.sh --no-build 'http_*.t' 'proxy_*.t'
By default the script expects an nginx-tests checkout at:
../nginx-tests
A different location can be supplied with:
scripts/run_nginx_tests.sh --tests-dir /path/to/nginx-tests
Passing tests, failing tests and skipped tests are tracked in NGINX_TEST_PROGRESS.md.
Performance is treated as part of the design, not as a final optimization pass.
The benchmark suite runs equivalent scenarios against nginx and ruxen and stores the nginx baseline and ruxen history for each workload.
See bench/README.md for the benchmark methodology and individual scenarios.
The current v0.1 performance contract for the minimal serving path is to reach at least 95% of nginx's throughput on the same hardware, while keeping p99 latency within the project's target and producing zero errors.
See DESIGN.md for the current baseline and performance notes.
The following are intentionally outside the current v0.1 scope:
proxy_ssl_*)userAccess restrictions that ruxen can't enforce yet — limit_except, ssl_verify_client on, ssl_reject_handshake on, disable_symlinks on|if_not_owner — are rejected when the configuration is loaded instead of being ignored. Accepted with a warning: ssl_ciphers and ssl_ecdh_curve (rustls's defaults — AEAD suites, modern groups — are used) and ssl_verify_client optional|optional_no_ca (no client certificate is requested, and $ssl_client_verify is always NONE).
Missing functionality is expected at this stage. ruxen should not yet be treated as a drop-in production replacement for nginx.
ruxen is still early enough that small experiments and compatibility reports can be genuinely useful.
Good ways to contribute include:
nginx-tests case;If you find something interesting, open an issue — even if you are not planning to implement it yourself.
See CONTRIBUTING.md for setup (tests, nginx-tests, benchmarks) and how changes are reviewed.
Licensed under the Apache License, Version 2.0.
Experimental nginx-compatible HTTP server and reverse proxy written in Rust
See the codeAn experimental nginx-compatible HTTP server and reverse proxy written from scratch in Rust.
ruxen explores how nginx's architecture and configuration semantics can be reimplemented in idiomatic Rust — without translating the C code line by line.
It is Linux-only, built around a thread-per-core model using monoio and io_uring, and currently targets HTTP/1.1.
[!WARNING] ruxen is an active prototype. It is not production-ready yet.
nginx has decades of engineering behind its event loop, HTTP state machine, configuration system, request routing, and upstream handling.
The goal of ruxen is to study those ideas and reproduce their behaviour using Rust's ownership model and type system rather than copying nginx's C implementation.
Three things guide the project:
nginx-tests suite, not only project-specific tests;See DESIGN.md for the architecture notes and the reasoning behind individual design decisions.
The v0.1 scope is deliberately limited:
io_uring via monoioThe upstream nginx-tests suite is used as a compatibility test. 63 of the 105 test files that ruxen currently opts into pass end-to-end (v0.1.1).
See NGINX_TEST_PROGRESS.md for the per-file status.
io_uring available. ruxen is developed and tested on kernels 6.x–7.0. io_uring must not be turned off with the kernel.io_uring_disabled sysctl.cargo install): Rust 1.88 or newer, plus cmake and a C compiler for the aws-lc-rs crypto backend that rustls uses. aws-lc refuses GCC 9 (Your compiler (cc) is not supported due to a memcmp related bug); on Ubuntu 20.04, install gcc-10 g++-10 and build with CC=gcc-10 CXX=g++-10.curl to try the server.Prebuilt binary from GitHub Releases:
| tarball | runs on |
|---|---|
ruxen-x86_64-linux-gnu.tar.gz | x86_64 with glibc 2.35 or newer (Ubuntu 22.04, Debian 12 and later) |
ruxen-aarch64-linux-gnu.tar.gz | ARM64 with glibc 2.35 or newer (Graviton, Ampere, Raspberry Pi 5 on a 64-bit OS) |
ruxen-x86_64-linux-musl.tar.gz | any x86_64 Linux, static (Alpine, older glibc). See the release notes for its performance. |
curl -LO https://github.com/gvozdetsky/ruxen/releases/latest/download/ruxen-x86_64-linux-gnu.tar.gz
tar xzf ruxen-x86_64-linux-gnu.tar.gz
cd ruxen-v*-x86_64-linux-gnu
./ruxen -V
On a glibc older than 2.35 the gnu builds fail with version `GLIBC_2.34' not found: use the musl build, or build from source. Each release also has the tarballs under versioned names (ruxen-v<version>-<target>.tar.gz) and a SHA256SUMS file.
From crates.io:
cargo install ruxen --locked
From source:
git clone https://github.com/gvozdetsky/ruxen.git
cd ruxen
cargo build --release
# the binary is target/release/ruxen
In Docker, the default seccomp profile blocks io_uring, so ruxen cannot create its io_uring runtime. Run the container with --security-opt seccomp=unconfined (or a profile that allows the io_uring_* syscalls).
ruxen does not switch to an unprivileged user the way nginx's user directive does, so it refuses to start as root unless the configuration says user root;. Containers usually run as root: either add user root; or run the container with --user.
examples/minimal.conf is the smallest useful configuration:
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
server_name _;
location / {
return 200 "hello from ruxen\n";
}
}
}
Check the configuration, then start the server in the foreground:
ruxen -t -c examples/minimal.conf
ruxen -c examples/minimal.conf
As with nginx, a valid configuration prints … syntax is ok and … test is successful (on stderr; -q silences them) and exits with status 0.
Then, from another terminal:
curl -i http://127.0.0.1:8080/
You should get a 200 response with:
hello from ruxen
examples/static.conf serves files from a directory and examples/proxy.conf proxies to two backends with keep-alive upstream connections.
You are now serving an nginx-style configuration with ruxen.
listen addressesserver_name exact, wildcard and regex matchinglocation matching:
=)^~~, ~*)internal locationslisten port ranges and listen … proxy_protocol (PROXY protocol v1 and v2, $proxy_protocol_*)worker_connections, client_header_timeout, client_body_timeout, send_timeoutclient_max_body_size, spilled to private temp files (client_body_temp_path), with an early 413limit_rate, limit_rate_after and $limit_rateSupported functionality includes:
rootaliasindextry_filesLast-Modifiedsendfile on|off (plain TCP)autoindexruxen supports a growing subset of nginx's proxy functionality, including:
proxy_passupstream blocksleast_connmax_fails / fail_timeoutproxy_http_versionproxy_set_headerproxy_set_bodyproxy_next_upstream (including _tries / _timeout)proxy_intercept_errorsproxy_redirect, including the implicit defaultproxy_hide_header / proxy_pass_headerX-Accel-Redirect, X-Accel-Limit-Rate and proxy_ignore_headers$upstream_* per-attempt variables, $proxy_host, $proxy_portTLS termination uses rustls.
Currently supported:
ssl_certificatessl_certificate_keyssl_session_cache (in memory, shared by the workers), ssl_session_tickets, ssl_session_timeout$ssl_* variablesALPN currently advertises HTTP/1.1 only.
A growing nginx-compatible configuration surface is implemented, including:
returnadd_headeradd_trailerexpireserror_page and recursive_error_pagespost_actionsetifrewritemap (including hostnames)split_clientsauth_basicauth_basic_user_filelog_formataccess_log and error_log, including syslog: targetsMany common nginx variables are also available, including request, response, upstream, TLS, cookie, header and rewrite variables.
The currently implemented nginx-style command-line surface includes:
-c
-p
-e
-g
-t
-T
-q
-V
-s stop | quit | reopen
-V starts with nginx version: nginx/1.29.2, followed by ruxen version: ruxen/<version>. The nginx line and the configure arguments list are what the nginx-tests harness reads to decide which tests apply, so they describe ruxen as an nginx build: the arguments list only the modules ruxen actually implements.
Responses identify ruxen itself: Server: ruxen/<version> (plain ruxen with server_tokens off), and the same name in the footer of built-in error pages. scripts/run_nginx_tests.sh sets RUXEN_NGINX_IDENTITY=1, which makes them say nginx/1.29.2 instead, because the upstream tests assert nginx's header.
ruxen also supports pid files, graceful shutdown via SIGQUIT (waits for in-flight requests), fast shutdown via SIGTERM or SIGINT (does not wait; the pid file is removed either way) and log reopening via SIGUSR1. -s stop|quit|reopen send SIGTERM/SIGQUIT/SIGUSR1 to the PID in the config's pid file, as nginx does. -s reload is refused: ruxen can't re-read its configuration yet, so restart it instead.
ruxen is not a line-by-line Rust port of nginx.
The approach is:
understand why nginx does something, then implement that behaviour in a way that makes sense in Rust.
For example, nginx uses memory pools, pointer-based configuration structures and arrays of function pointers because those are natural solutions in C.
ruxen does not reproduce those mechanisms where Rust already provides safer or simpler alternatives.
The aim is behavioural and architectural compatibility where it matters — not source-code similarity.
More detail is in DESIGN.md.
Run the Rust unit and integration tests:
cargo test --release
The HTTPS integration tests use the system curl binary and generate temporary certificates at runtime.
ruxen can also run the upstream nginx Perl test suite.
From the repository root:
scripts/run_nginx_tests.sh
Run only selected groups:
scripts/run_nginx_tests.sh --no-build 'http_*.t' 'proxy_*.t'
By default the script expects an nginx-tests checkout at:
../nginx-tests
A different location can be supplied with:
scripts/run_nginx_tests.sh --tests-dir /path/to/nginx-tests
Passing tests, failing tests and skipped tests are tracked in NGINX_TEST_PROGRESS.md.
Performance is treated as part of the design, not as a final optimization pass.
The benchmark suite runs equivalent scenarios against nginx and ruxen and stores the nginx baseline and ruxen history for each workload.
See bench/README.md for the benchmark methodology and individual scenarios.
The current v0.1 performance contract for the minimal serving path is to reach at least 95% of nginx's throughput on the same hardware, while keeping p99 latency within the project's target and producing zero errors.
See DESIGN.md for the current baseline and performance notes.
The following are intentionally outside the current v0.1 scope:
proxy_ssl_*)userAccess restrictions that ruxen can't enforce yet — limit_except, ssl_verify_client on, ssl_reject_handshake on, disable_symlinks on|if_not_owner — are rejected when the configuration is loaded instead of being ignored. Accepted with a warning: ssl_ciphers and ssl_ecdh_curve (rustls's defaults — AEAD suites, modern groups — are used) and ssl_verify_client optional|optional_no_ca (no client certificate is requested, and $ssl_client_verify is always NONE).
Missing functionality is expected at this stage. ruxen should not yet be treated as a drop-in production replacement for nginx.
ruxen is still early enough that small experiments and compatibility reports can be genuinely useful.
Good ways to contribute include:
nginx-tests case;If you find something interesting, open an issue — even if you are not planning to implement it yourself.
See CONTRIBUTING.md for setup (tests, nginx-tests, benchmarks) and how changes are reviewed.
Licensed under the Apache License, Version 2.0.