ngx_wasm is an out-of-tree NGINX HTTP module for running request handlers in
WebAssembly.
The current focus is an OpenResty-style programming model for standard,
unmodified NGINX, starting with content_by_wasm, server_rewrite_by_wasm,
and rewrite_by_wasm.
Project references:
- NGINX: https://github.com/nginx/nginx
- OpenResty /
lua-nginx-module: https://github.com/openresty/lua-nginx-module - Wasmtime: https://github.com/bytecodealliance/wasmtime
Today, ngx_wasm provides an initial vertical slice of:
content_by_wasm <module-path> <export>server_rewrite_by_wasm <module-path> <export>rewrite_by_wasm <module-path> <export>- OpenResty-style configuration placement in
http,server, andlocation - Wasmtime C API embedding as the current WebAssembly runtime
- one guest export invocation per request
- basic host functions for logging and writing a response
This is still early-stage software, but the direction is clear:
- keep NGINX as the server
- keep the module out-of-tree and usable with stock NGINX
- use WebAssembly for safer, language-flexible guest logic
- learn from OpenResty semantics without requiring OpenResty as the runtime
NGINX was chosen because it already provides the core request-processing model this project wants to extend:
- mature HTTP phase and filter pipeline
- production-proven event-driven architecture
- familiar configuration and inheritance model
- strong operational footprint and ecosystem
The goal is not to replace NGINX. The goal is to make NGINX programmable in a way that borrows the best ideas from OpenResty while staying compatible with standard unmodified NGINX.
WebAssembly was chosen as the guest execution layer because it offers:
- stronger isolation than embedding application logic directly in-process
- a path to multi-language support
- explicit execution budgeting and control
- a better fit for typed SDKs than a dynamic scripting-only model
The current runtime choice is Wasmtime via its C API.
| Feature | ngx_wasm |
OpenResty / lua-nginx-module |
Proxy-Wasm style systems |
|---|---|---|---|
| Core server model | Standard NGINX module | NGINX/OpenResty | Varies by host proxy |
| Primary inspiration | OpenResty semantics | Native project model | Generalized proxy ABI |
| Guest runtime | WASM via Wasmtime | Lua / LuaJIT | WASM |
| Main goal | Programmable NGINX callbacks | Programmable NGINX callbacks | Portable proxy extensions |
| Config model | NGINX config directives | NGINX config directives | Usually plugin/filter configuration |
| Language model | Multi-language via WASM | Lua-first | Multi-language via WASM |
| Execution budgeting | Planned first-class fuel/gas model | Not the primary model | Often host/runtime dependent |
| Current project state | Early prototype | Mature | Mature in some ecosystems |
OpenResty is the main source of semantic inspiration for this project. The goal is not to dismiss it, but to carry forward the programmable request-handling model with a WASM guest runtime.
Build/runtime dependencies for the current implementation:
- standard NGINX source tree for
--add-module - Wasmtime C API
- a C compiler for the nginx module build
Optional example guest build dependencies:
- Rust with the
wasm32-unknown-unknowntarget for Rust guest fixtures - Perl +
provefor theTest::Nginxtest suite cpanmorcpanfor installing Perl test dependencies viamake deps
Performance numbers are not published yet.
The long-term goal is to publish comparative measurements covering:
- request latency
- host boundary overhead
- guest startup and invocation overhead
- the cost of execution budgeting and future yield/resume behavior
Fetch the pinned Wasmtime C API release, a local test-nginx checkout, the
Rust stable toolchain with wasm32-unknown-unknown, and Perl test
dependencies:
make depsBuild the example guest:
make wasmStart a simple local nginx instance for load testing:
make start
curl http://127.0.0.1:18080/hello
curl http://127.0.0.1:18080/health
make stopRun a repeatable ApacheBench sweep against both wasm endpoints:
make bench-abOverride the benchmark shape when needed:
make bench-ab BENCH_AB_REQUESTS=50000 BENCH_AB_CONCURRENCIES="100 500 1000 2000"The benchmark target uses 127.0.0.1:18081 by default so it does not collide
with make start on 127.0.0.1:18080.
make wasm builds all current guest modules required by the example and test
suite.
Build NGINX with the module:
cd ../nginx
auto/configure --add-module=../ngx_wasm
make -j"$(sysctl -n hw.ncpu)"Example nginx configuration:
http {
server {
listen 8080;
server_rewrite_by_wasm wasm/http-guests/build/hello_world.wasm on_content;
location /rewrite {
rewrite_by_wasm wasm/http-guests/build/hello_world.wasm on_content;
}
location /wasm {
content_by_wasm wasm/http-guests/build/hello_world.wasm on_content;
}
}
}Current expected behavior:
- the request hits the configured
ngx_wasmserver rewrite, rewrite, or content handler - the guest export
on_contentis invoked - the guest writes the response via the host ABI
The low-level example guest lives in
wasm/http-guests.
Build it with:
make wasmmake wasm builds the Rust guest fixtures with rustc --target wasm32-unknown-unknown.
If needed, override the Rust toolchain explicitly:
make RUSTC=/path/to/rustc wasm
make RUSTC=/path/to/rustc RUSTUP=/path/to/rustup wasmIf the target is missing:
rustup target add wasm32-unknown-unknownmake deps also installs rustup when needed and ensures the Rust stable
toolchain has the wasm32-unknown-unknown target available for Rust-based
guest fixtures.
- Project spec: ngx_wasm_openresty_style_spec.md
- Design notes: design.md
- Testing plan: testing.md
- Guest ABI: abi.md
- Guest ABI header: ngx_wasm_guest_abi.h
This repository is an active prototype. The current code is intentionally narrow and is being built phase by phase.
Install development dependencies and run the current test suite:
make deps
make testTo rebuild nginx with ASan+UBSan and run the same suite against the
sanitized binary:
make build BUILD_SANITIZE=1
make testIf your NGINX checkout is not at ../nginx, override it explicitly:
make smoke NGINX_DIR=/path/to/nginx
make test NGINX_DIR=/path/to/nginx
make build BUILD_SANITIZE=1 NGINX_DIR=/path/to/nginx
make test NGINX_DIR=/path/to/nginxFor debugging, keep the Test::Nginx server root and logs on disk:
TEST_NGINX_NO_CLEAN=1 make testUse a fixed port only when you explicitly want one:
TEST_NGINX_RANDOMIZE=0 TEST_NGINX_PORT=1984 make testSanitized builds currently assume a clang-compatible toolchain. Override
SANITIZER_CC, SANITIZER_FLAGS, ASAN_OPTIONS, or UBSAN_OPTIONS if you
need different sanitizer settings.
The default sanitizer flags disable UBSan's nonnull-attribute check and use
an ignorelist for known nginx-core sanitizer findings outside ngx_wasm
itself.
On Linux, leak detection stays enabled, but the default LSAN_OPTIONS points
at sanitizers/lsan.supp
to suppress known nginx-core process-lifetime allocations from event startup.