QJS-FPM is a Rust FastCGI process manager for executing JavaScript with QuickJS-NG. nginx sends FastCGI requests to a master process, which serves them from a resident worker pool (static/dynamic/ondemand); every request executes in a fresh QuickJS-NG context inside a reused worker, so no state leaks between requests.
- FastCGI record parsing and response framing
- Request size limits (
request.params_limit/request.body_limit) and read timeouts (request.read_timeout), with typed error responses (400/408/413/500) - Timestamped logging to stderr and rotating log files (
src/log.rs) - TCP listener and Unix socket listener on Unix
- Resident worker pool (static/dynamic/ondemand) with per-request timeout handling and LRU idle reclamation
- SIGTERM/SIGINT graceful shutdown and SIGHUP configuration reload
- INI-style configuration for PHP-FPM-like options
- User/group/chroot privilege dropping (
src/privilege.rs) - Embedded QuickJS-NG runtime via rquickjs with per-request memory/stack limits and context injection; module top-level exceptions are reported as 500 rather than a silent 200 empty body
- multipart/form-data parsing with
request.forminjection - qjs-ng official native module loading (
js_init_module.so/.dll/.dylib) via rquickjsNativeLoader, so the JS extension ecosystem follows the qjs-ng project - Example JavaScript scripts and nginx configuration
- Rust toolchain (build compiles the bundled QuickJS-NG C sources)
- nginx for end-to-end FastCGI testing
cargo build --workspace
cargo run -- --check-config --config qjs-fpm.ini
cargo run -- --config qjs-fpm.ini
The default listen is unix:/tmp/qjs-cgi-10.sock; point nginx at it (a starting server block using .mjs as the dynamic entry suffix — PHP's .php analog — is in docs/nginx.conf). Static .js/.css/images are served directly by nginx, only .mjs goes to qjs-fpm. The Unix socket is chmod 0666 after bind (like php-fpm listen.mode) so nginx (running as www) can connect even when the master starts as root.
build.sh is the single build-and-deploy entry: it runs cargo build --release and copies the binary, config, and native modules into target/qjs-fpm/ (bin/qjs-fpm, etc/qjs-fpm.ini, lib/qjs/extensions/*.so). Copy the whole directory to the target host and run bin/qjs-fpm; the config is auto-discovered next to the binary. Do not hand-copy target/release/qjs-fpm and skip build.sh.
The default chdir is empty, so scripts are not confined to any directory (php-fpm behavior: the script path comes from nginx SCRIPT_FILENAME); set chdir in qjs-fpm.ini to confine scripts. Request hello.js, file-read.js, or native-module.js through nginx. Scripts receive:
request.env
request.body
request.form
response.status
response.headers
response.write(value)
response.json(value)
fs.read_file(path) // 同步读取,相对路径基于脚本所在目录
fs.read_file_async(path) // 返回 Promise 的异步读取
setTimeout(cb, delayMs) // 事件循环定时器,返回定时器 id
setInterval(cb, delayMs) // 周期定时器,返回定时器 id
clearTimeout(id)
clearInterval(id)
print(...values)
console.log(...values)
console.error(...values)Native extensions are loaded through the qjs-ng official module mechanism — scripts use import * as os from "os" to load <modules_dir>/os.so (see examples/native-module.js, examples/native-os.js and examples/http-client.js).
The qjs-ng CLI official libc ecosystem (os/std/bjson) requires the per-runtime libc thread state, so the host initializes it via js_std_init_handlers after creating each Runtime. Official releases do not ship a standalone os.so; build quickjs-libc.c from a quickjs-ng source tree that matches the version bundled with rquickjs-sys (see docs/native-modules-build.md for the exact build steps).
The current code prioritizes semantic completeness and clear replacement boundaries. Request limits, timeouts, error logging, privilege dropping/chroot, multipart parsing, and qjs-ng native module loading are in place; the native module loading path is verified end-to-end with a real js_init_module module. The next production step is adding integration tests against nginx.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Copyright 2026 心衍 vier@j7yx.com