yum upgrades for production use, this is the repository for you.
Active subscription is required.
You need to add custom logic to your NGINX reverse proxy — an authentication check that calls an external API, a rate limiter that shares state across workers, or a request transformer that rewrites headers. The problem is that NGINX was not designed to be easily extensible. Your options have been painful: write a C module (complex, unsafe, requires recompilation) or use Lua scripting through OpenResty (no sandboxing, vendor lock-in). NGINX WebAssembly support finally changes this.
With the NGINX WebAssembly module (ngx_wasm_module), you write filters in Rust, Go, C++, or AssemblyScript. These compile to WebAssembly bytecode and run at near-native speed inside a sandboxed runtime. A bug in your filter cannot crash the server. Better still, filters follow the Proxy-Wasm standard — the same compiled binary works across NGINX, Envoy, and other compatible proxies without modification.
What is Proxy-Wasm?
Proxy-Wasm is a set of binary specifications that standardize how WebAssembly extensions interact with network proxies. Originally developed for Envoy, Proxy-Wasm ensures that filters are “proxy-agnostic” — a filter compiled once can run on any compliant host, whether that is Envoy, NGINX, or Kong Gateway.
The specification defines two complementary parts:
- Host ABI (Application Binary Interface): The low-level interface that the proxy exposes to WebAssembly modules. It provides functions for reading request headers, modifying responses, making HTTP calls, and accessing shared memory.
- SDK Libraries: High-level frameworks that wrap the Host ABI into idiomatic language constructs. SDKs are available for Rust, Go (TinyGo), C++, and AssemblyScript.
You write your filter using the SDK in your preferred language, compile it to a .wasm binary, and load it into NGINX. No C code, no recompilation, no vendor lock-in.
How the NGINX WebAssembly Module Works
The NGINX WebAssembly module embeds the Wasmtime runtime directly into NGINX worker processes. Wasmtime, developed by the Bytecode Alliance, is one of the most mature WebAssembly runtimes available. The module integrates WebAssembly into the NGINX request pipeline through two mechanisms:
Direct Wasm Function Calls
The wasm_call directive invokes exported functions from a WebAssembly module at designated NGINX phases (rewrite, access, content, header_filter, body_filter, log). This is the simplest way to run WebAssembly code in NGINX.
Proxy-Wasm Filter Chains
The proxy_wasm directive attaches a Proxy-Wasm filter to the request processing chain. Filters implement callbacks like on_request_headers, on_response_body, and on_http_call_response. Multiple filters can be chained together, executing sequentially — similar to middleware in modern web frameworks.
Architecturally, the NGINX WebAssembly module:
- Loads WebAssembly modules during master process initialization for validation
- Re-loads them in each worker process after
fork() - Creates Wasm instances per worker, with configurable isolation levels
- Routes NGINX processing phases through the Proxy-Wasm filter chain
- Provides shared memory zones for inter-worker communication
Installation
The NGINX WebAssembly module is installed as a dynamic module package. It follows the same pattern as other NGINX dynamic modules — like the Zstd decompression module or the WAF module — from the GetPageSpeed repository.
RHEL, CentOS, AlmaLinux, Rocky Linux
sudo dnf install https://extras.getpagespeed.com/release-latest.rpm
sudo dnf install nginx-module-wasm-wasmtime
After installation, load the module by adding the following to the top of /etc/nginx/nginx.conf:
load_module modules/ngx_wasmx_module.so;
Alternatively, include all installed module configuration files automatically:
include /usr/share/nginx/modules/*.conf;
Verify the module is loaded by testing the configuration:
nginx -t
For more details and available versions, see the NGINX WebAssembly module page.
Debian and Ubuntu
First, set up the GetPageSpeed APT repository, then install:
sudo apt-get update
sudo apt-get install nginx-module-wasm-wasmtime
On Debian/Ubuntu, the package handles module loading automatically. No
load_moduledirective is needed.Note: The NGINX WebAssembly module may have limited availability in APT repositories depending on your platform. Check the APT module page for current status.
Configuration
The NGINX WebAssembly module introduces directives across two main contexts: the top-level wasm{} block and the standard http{}, server{}, location{} hierarchy.
The wasm{} Block
The wasm{} block is a top-level directive (same level as events{} and http{}). It configures the WebAssembly runtime and loads Wasm modules.
Loading Modules
wasm {
module my_filter /path/to/filter.wasm;
module another_filter /path/to/another.wasm 'config=value';
}
The module directive loads a WebAssembly file and accepts three arguments:
- name: A unique identifier for referring to this module elsewhere
- path: Path to a
.wasm(binary) or.wat(text format) file - config (optional): A configuration string passed to the filter’s
on_vm_startcallback
Runtime Configuration
The wasm{} block supports nested runtime-specific settings via the wasmtime{} sub-block:
wasm {
compiler auto;
backtraces on;
wasmtime {
flag epoch_interruption on;
cache_config /etc/nginx/wasmtime-cache.toml;
}
}
| Directive | Default | Description |
|---|---|---|
compiler |
auto |
WebAssembly compiler: auto or cranelift |
backtraces |
off |
Enable detailed backtraces in error logs on Wasm traps |
flag |
— | Runtime-specific configuration flags (context: wasmtime{}) |
cache_config |
— | Wasmtime compilation cache config in TOML format (context: wasmtime{}) |
DNS Resolution
Wasm filters that make HTTP dispatches need DNS resolution. Configure the global resolver in the wasm{} block:
wasm {
resolver 1.1.1.1 ipv6=off;
resolver_timeout 10s;
}
| Directive | Default | Description |
|---|---|---|
resolver |
8.8.8.8 |
DNS resolver for Wasm HTTP dispatches |
resolver_timeout |
30s |
DNS resolution timeout |
The resolver_add directive adds static host entries (similar to /etc/hosts). It works in both wasm{} and http{}/server{}/location{} contexts:
resolver_add 127.0.0.1 my-backend.local;
Socket Configuration
These directives control timeouts and buffers for TCP connections made by Wasm filters:
wasm {
socket_connect_timeout 30s;
socket_send_timeout 30s;
socket_read_timeout 30s;
socket_buffer_size 4k;
socket_buffer_reuse on;
socket_large_buffers 4 16k;
}
| Directive | Default | Description |
|---|---|---|
socket_connect_timeout |
60s |
TCP connection timeout |
socket_send_timeout |
60s |
TCP send timeout |
socket_read_timeout |
60s |
TCP read timeout |
socket_buffer_size |
1024 |
Response payload buffer size |
socket_buffer_reuse |
on |
Reuse buffers across connections |
socket_large_buffers |
4 8192 |
Number and size of large buffers for response headers |
TLS Configuration
Configure certificate verification for outgoing TLS connections from Wasm filters:
wasm {
tls_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;
tls_verify_cert on;
tls_verify_host on;
tls_no_verify_warn off;
}
| Directive | Default | Description |
|---|---|---|
tls_trusted_certificate |
— | CA certificate bundle path in PEM format |
tls_verify_cert |
off |
Verify server certificates against the trusted CA store |
tls_verify_host |
off |
Verify certificate hostnames match the target |
tls_no_verify_warn |
off |
Suppress TLS verification warning logs |
Shared Memory
Shared memory zones let Wasm filters across different worker processes share data:
wasm {
shm_kv my_cache 256k eviction=slru;
shm_queue my_events 128k;
metrics {
slab_size 5m;
max_metric_name_length 256;
}
}
| Directive | Default | Description |
|---|---|---|
shm_kv |
— | Shared key/value store. Eviction: slru (default), lru, or none |
shm_queue |
— | Shared queue for inter-process messaging |
slab_size |
5m |
Metrics storage memory (min: 3 × page size, context: metrics{}) |
max_metric_name_length |
256 |
Max metric name length, min: 6 (context: metrics{}) |
HTTP Context Directives
These directives are available in http{}, server{}, and location{} blocks.
Proxy-Wasm Filters
The proxy_wasm directive attaches a filter to the request processing chain:
location /api {
proxy_wasm my_auth_filter 'realm=api';
proxy_wasm my_rate_limiter;
proxy_wasm_isolation stream;
proxy_pass http://backend;
}
| Directive | Default | Description |
|---|---|---|
proxy_wasm |
— | Attach a Proxy-Wasm filter with optional config string |
proxy_wasm_isolation |
none |
Instance isolation: none, stream, or filter |
proxy_wasm_request_headers_in_access |
off |
Run on_request_headers in access phase instead of rewrite |
Isolation modes control how WebAssembly instances are shared:
none: All filters of the same module share one instance per worker. Lowest memory usage, best for stateless filters.stream: Filters of the same module within one request share an instance. Good for request-scoped state.filter: Each filter gets its own instance. Strongest isolation, highest memory usage.
Direct Wasm Calls
The wasm_call directive invokes a specific exported function at a given NGINX phase:
location / {
wasm_call rewrite my_module my_rewrite_func;
wasm_call log my_module my_log_func;
proxy_pass http://backend;
}
It takes three arguments:
- phase: One of
rewrite,access,content,header_filter,body_filter, orlog - module: Name of the loaded Wasm module (from the
wasm{}block) - function: Name of the exported function to call
HTTP Socket Overrides
Per-location socket settings override the global wasm{} defaults:
| Directive | Default | Description |
|---|---|---|
wasm_socket_connect_timeout |
60s |
Per-context connect timeout |
wasm_socket_send_timeout |
60s |
Per-context send timeout |
wasm_socket_read_timeout |
60s |
Per-context read timeout |
wasm_socket_buffer_size |
1024 |
Per-context buffer size |
wasm_socket_buffer_reuse |
on |
Per-context buffer reuse toggle |
wasm_socket_large_buffers |
4 8192 |
Per-context large buffers for response headers |
wasm_response_body_buffers |
4 4096 |
Response body buffering for filter chains |
Phase Postponement
These directives control when NGINX WebAssembly phase handlers run relative to other modules. Primarily useful in OpenResty builds:
| Directive | Default | Description |
|---|---|---|
wasm_postpone_rewrite |
off |
Defer Wasm rewrite handler (auto-enabled in OpenResty) |
wasm_postpone_access |
off |
Defer Wasm access handler (auto-enabled in OpenResty) |
Getting Started: Your First WebAssembly Filter
Here is a complete walkthrough to verify the NGINX WebAssembly module works.
Step 1: Create a Simple Wasm Module
Create a WebAssembly text file at /etc/nginx/hello.wat:
(module
(func (export "say_hello"))
)
This defines a minimal Wasm module with one exported function. The module accepts both .wat (text) and .wasm (binary) formats.
Step 2: Configure NGINX
load_module modules/ngx_wasmx_module.so;
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events { worker_connections 1024; }
wasm {
module hello /etc/nginx/hello.wat;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
location / {
wasm_call log hello say_hello;
return 200 "Hello from NGINX with WebAssembly!\n";
}
}
}
Step 3: Test and Reload
nginx -t
systemctl reload nginx
curl http://localhost/
You should see:
Hello from NGINX with WebAssembly!
This confirms the WebAssembly runtime is initialized and running inside NGINX.
Writing a Proxy-Wasm Filter in Rust
For production use, Proxy-Wasm filters are the preferred approach. Here is a Rust filter that adds a custom response header:
use proxy_wasm::traits::*;
use proxy_wasm::types::*;
proxy_wasm::main! {{
proxy_wasm::set_log_level(LogLevel::Info);
proxy_wasm::set_http_context(|_, _| -> Box<dyn HttpContext> {
Box::new(AddHeader)
});
}}
struct AddHeader;
impl Context for AddHeader {}
impl HttpContext for AddHeader {
fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action {
self.add_http_response_header("X-Powered-By", "WasmX");
Action::Continue
}
}
Compile it to WebAssembly:
cargo build --target wasm32-wasip1 --release
Then load the resulting .wasm binary in your NGINX configuration:
wasm {
module add_header /etc/nginx/filters/add_header.wasm;
}
http {
server {
listen 80;
location / {
proxy_wasm add_header;
proxy_pass http://backend;
}
}
}
Every response through this location will include X-Powered-By: WasmX — added by a sandboxed WebAssembly filter written in Rust.
Performance Considerations
Compilation Overhead
Wasmtime compiles WebAssembly to native code at load time using the Cranelift compiler. For large filters, this adds time to startup. Enable the compilation cache to persist artifacts across restarts:
wasm {
wasmtime {
cache_config /etc/nginx/wasmtime-cache.toml;
}
}
Create a basic cache configuration at /etc/nginx/wasmtime-cache.toml:
[cache]
enabled = true
directory = "/var/cache/nginx/wasmtime"
cleanup-interval = "1h"
files-total-size-soft-limit = "512Mi"
Then create the cache directory:
mkdir -p /var/cache/nginx/wasmtime
chown nginx:nginx /var/cache/nginx/wasmtime
Memory Usage
Each WebAssembly instance consumes memory for its linear memory, stack, and compiled code. The isolation mode directly affects instance count:
none: One instance per module per worker. Minimal overhead — the best default.stream: One instance per request. Use when filters store per-request state in Wasm globals.filter: One instance per filter per request. Only needed when filters must not share any state.
Start with proxy_wasm_isolation none and increase only if your filter logic requires it.
Socket Buffer Tuning
If your filters make HTTP dispatches to external services, tune socket buffers for expected response sizes:
wasm {
socket_buffer_size 8k;
socket_large_buffers 8 32k;
}
Increase socket_large_buffers for responses with large headers.
Security Best Practices
Enable TLS Verification for Outgoing Connections
By default, TLS verification is disabled for Wasm filter connections. In production, always enable it:
wasm {
tls_trusted_certificate /etc/pki/tls/certs/ca-bundle.crt;
tls_verify_cert on;
tls_verify_host on;
}
Size Shared Memory Appropriately
Oversized zones waste RAM. Undersized zones cause write failures:
wasm {
shm_kv rate_data 64k eviction=lru;
}
The lru policy automatically evicts old entries when full, preventing hard failures.
The WebAssembly Sandboxing Advantage
WebAssembly provides inherent sandboxing. Wasm modules cannot access the filesystem, network, or host memory beyond their allocated linear memory. All external interactions go through the Proxy-Wasm ABI.
This contrasts with native C modules (full process access) and Lua scripts (unrestricted os and io access). If you already use NGINX security modules like the HTML Sanitize module for edge protection, the NGINX WebAssembly module provides an even stronger boundary for third-party code.
Enable Backtraces During Development
Enable Wasm backtraces for detailed error output when a filter traps:
wasm {
backtraces on;
}
Wasmtime backtraces show function names, filenames, and line numbers. Disable in production to reduce log noise.
Troubleshooting
“unknown directive wasm”
The module is not loaded. Add load_module modules/ngx_wasmx_module.so; to the very top of nginx.conf, before all other blocks.
“module not loaded” or Failed to Load Module
The Wasm file is missing or unreadable. Verify the path and permissions:
ls -la /path/to/module.wasm
namei -l /path/to/module.wasm
Check validity with the wasmtime CLI (available from GetPageSpeed):
dnf install wasmtime
wasmtime compile /path/to/module.wasm
Filter Initialization Fails but NGINX Exits with Code 0
Proxy-Wasm filters start in worker processes after the master has already exited. Check the error log:
grep -i "wasm\|proxy_wasm" /var/log/nginx/error.log
Socket Timeouts in Proxy-Wasm Dispatches
Increase socket timeouts at the location level:
location /api {
wasm_socket_connect_timeout 30s;
wasm_socket_read_timeout 30s;
}
The dispatch_http_call() SDK method also accepts a per-call timeout.
Shared Memory Full
Increase the zone size or add an eviction policy:
wasm {
shm_kv my_data 512k eviction=lru;
}
The minimum zone size is 15k. The default metrics slab_size of 5m holds about 20,000 counters with 64-char names and 4 workers.
Conclusion
The NGINX WebAssembly module brings a new extension model to NGINX. Write filters in Rust or Go, benefit from sandboxing, and deploy the same binaries across proxy platforms.
Whether you need authentication, rate limiting, request transformation, or observability — the NGINX WebAssembly module lets you build it without C code or recompilation.
The module is available as nginx-module-wasm-wasmtime from the GetPageSpeed repository. For source code and issues, visit the ngx_wasm_module GitHub repository.
