Skip to content
NewBun 1.4.5 is out. Release notes
AphrodyBun

Runtime · HTTP server

NGINX

Validate, start, reload and stop an NGINX process from Bun with bun:nginx.

bun:nginx controls an installed NGINX executable from JavaScript. Bun manages the NGINX master process; NGINX itself handles HTTP, TLS, proxying, caching and its configured modules. bun:Nginixx resolves to the same module.

import Nginx from "bun:nginx";
import { homedir } from "node:os";
import { join } from "node:path";

const provider = await Nginx.probe({ binary: "/usr/sbin/nginx" });
if (!provider.available) throw new Error(provider.message);

await using nginx = await Nginx.create({
  binary: "/usr/sbin/nginx",
  prefix: join(homedir(), "apps", "my-nginx"),
  configFile: "nginx.conf",
  timeoutMs: 10_000,
});
await nginx.validate();
const started = await nginx.start();
console.log(started.pid);
await nginx.reload();

Bun does not download NGINX. binary defaults to nginx on PATH.

Prefix and configuration

The prefix directory must already exist and configFile must resolve inside it. Omit the daemon and pid directives: Bun runs NGINX in the foreground and writes the master PID to <prefix>/bun-nginx.pid. Create NGINX's log and temporary directories inside the prefix, or set their paths in the configuration.

Only one controller can own a prefix at a time; ownership is recorded in an exclusive .bun-nginx-owner file. start() refuses a prefix that already contains a PID file, and reload() and quit() check that the PID belongs to the master Bun started. The controller never adopts, reloads or stops an NGINX process it did not start. On Unix the master runs in its own process group; on Windows it runs in a job object that terminates it when the controller closes.

validate() runs nginx -t with a separate temporary PID file, so it never touches the PID file of a running master.

API

MethodDescription
Nginx.probe(options?)Runs nginx -V and reports version, configure arguments and compiled capabilities.
Nginx.create(options)Claims the prefix and returns a controller.
nginx.probe(options?)Same as Nginx.probe for this controller's executable.
nginx.validate()Tests the configuration.
nginx.start()Starts the master and resolves with its status once the PID is written.
nginx.reload()Reloads the configuration in the same master.
nginx.quit()Stops the master gracefully. The controller can be started again.
nginx.status()Returns { running, pid, prefix, configFile, pidFile, closed, lastExitCode }.
nginx.close()Quits, releases the prefix and resolves with { closed: true, forced }.

All methods return promises. create() and probe() accept:

OptionDescription
binaryExecutable path or name on PATH. Default nginx.
binaryArgumentsLauncher arguments placed before NGINX flags (at most 16).
maxOutputBytesLimit for each of stdout and stderr, 1–1048576 bytes. Default 262144.
timeoutMsDeadline in milliseconds, integer 1–30000.
signalAbortSignal that cancels the operation.
prefix(create only) Existing directory owned by this controller.
configFile(create only) Configuration file inside prefix.

Every operation also accepts its own timeoutMs and signal. Running two operations on the same controller at once rejects with ERR_NGINX_BUSY.

Lifetime

A running master keeps Bun's event loop alive. When the master exits, Bun stops its remaining workers. A failed or aborted start() terminates the child. close() attempts a graceful quit and terminates the process if that exceeds the deadline (forced: true); calling it again succeeds. If the controller is garbage-collected or the process exits, Bun terminates the NGINX processes it started. Use await using or a finally block to close the controller deterministically.

Errors

probe() resolves with available: false and code ERR_NGINX_PROVIDER_UNAVAILABLE when the executable cannot be run. Other failures reject with one of these codes:

CodeMeaning
ERR_NGINX_PROVIDER_UNAVAILABLEThe NGINX executable cannot be run.
ERR_NGINX_CONFIGNGINX rejected the configuration.
ERR_NGINX_OWNERThe prefix or PID file belongs to another controller.
ERR_NGINX_BUSYAnother operation is in progress on this controller.
ERR_NGINX_TIMEOUTThe operation exceeded timeoutMs.
ERR_NGINX_OUTPUT_LIMITNGINX output exceeded maxOutputBytes.
ERR_NGINX_NATIVEA process or system call failed.
ABORT_ERRThe operation was cancelled with its AbortSignal.

Invalid options, such as a configFile that resolves outside prefix, reject with ERR_INVALID_ARG_VALUE.

The capabilities reported by probe() (http, httpTLS, http2, http3, stream, mail, udp, threads, ...) come from the build flags printed by nginx -V. platform and arch describe the host running Bun, not the executable.

Windows

The official NGINX for Windows is a beta release: it uses a single effective worker process (effectiveWorkersMaximum: 1) and does not support UDP or QUIC. See NGINX for Windows.

The repository script packages/bun-nginx/scripts/prepare-provider.ts downloads the official NGINX 1.31.6 Windows ZIP, verifies its signature with the official NGINX signing keys in an isolated keyring, and extracts it into a directory you choose:

bun packages/bun-nginx/scripts/prepare-provider.ts C:\path\to\nginx-sdk "C:\Program Files\Git\usr\bin\gpg.exe"

Rust HTTP module

packages/bun-nginx/native contains an NGINX HTTP module written in Rust with ngx-rust. It is compiled into an NGINX executable built from source with --add-module (see packages/bun-nginx/scripts/build-provider.ts); a stock NGINX binary does not include it. It adds two location directives:

  • bun_nginx; returns a small JSON metadata response.
  • bun_nginx_delay <ms>; (1–1000) returns that response after an asynchronous NGINX timer.
events { worker_connections 32; }
http {
    server {
        listen 127.0.0.1:8080;
        location = /info { bun_nginx; }
        location = /delay { bun_nginx_delay 500; }
    }
}

The optional @aphrody/bun-nginx package re-exports bun:nginx and adds readModuleInfo(nginx, url, options?), which reads that response over loopback HTTP and checks that it comes from the master owned by the controller:

import { readModuleInfo } from "@aphrody/bun-nginx";

const info = await readModuleInfo(nginx, "http://127.0.0.1:8080/info");
console.log(info.nginxVersion, info.masterPID, info.pendingTimers);

options accepts timeoutMs (default 2000), signal and expectedConfigurationSha256.

References