nginx njs: Installing the Module, Setting js_set, and Applying a Reload to Create JavaScript Variables

Affiliate disclosure v3.1
Affiliate disclosure v3.2 (mobile)
Test environment RHEL familyAlmaLinux 9.8Nginx 1.28 + Apache 2.4njs 0.9.4WP Rocket 3.23.5.1Checked 2026-10-17

nginx njs is an official nginx module that handles requests or creates variable values with JavaScript functions inside the nginx configuration. On a server with an Nginx + Apache setup, to fix the letter case of request URLs, which nginx configuration alone cannot change, the njs module was installed in the same version as the installed nginx, and a variable created with js_set was put into the cache file path and applied with a reload, without a restart.

The example function is a one-liner that lowercases the percent-encoding of Korean URLs, and building variables by reading headers or argument values from the request object works the same way.

1️⃣ What Is nginx njs: A Module That Extends nginx With JavaScript

njs is not a standalone JavaScript runtime like Node.js: it has no startup script or event loop of its own, and code runs only when nginx calls a function named in its configuration, after which processing returns to nginx.

The points where nginx calls JavaScript functions are fixed: the HTTP request and response processing phases, the TCP/UDP session processing phases, variable value calculation, and periodic timers. A function reads the URL, headers and arguments from the request object nginx passes in, and returns the result as a response or a variable value.

On the HTTP side, [js_import] loads a script file, [js_set] creates a variable value, [js_content] creates the response body, and [js_header_filter] and [js_body_filter] modify the response headers and body. This setup used two directives: [js_import] and [js_set].

🧭 Where nginx Calls njs Functions
Explainer diagram
📌 Call point
📥 Request
🖥️ nginx
Processing phase · point set in the config
js_setVariable value calculation
js_contentResponse body
js_header_filter · js_body_filterModifying response headers · body
Call→
📜 njs function
Request object passed by nginx
Read URL · headers · arguments
↩️ Returned as a variable value
↩️ Returned as a response
↩️ Returned as modified headers · body
❌ Does not run on its own like Node.jsNo startup script · no event loop of its own
Return→
🖥️ nginx
Continues processing
Variable valueUsed here · put into the cache file path
Response bodyResponds with the body the function built
Response headers · bodyResponds with the modified headers and body
📤 Response
[js_set] calls the function at the point where nginx calculates a variable value; the value the function returns becomes the variable value, and processing returns to nginx.
[js_content] calls the function at the point where the response body is created, and nginx sends the response the function built from the request object.
[js_header_filter] and [js_body_filter] call the function while the response is being sent out to modify its headers and body, and once that is done, processing returns to nginx.

The njs module's default engine implements ECMAScript 5.1 (strict mode) plus some ECMAScript 6 and later features. String handling at the level of regex replacement and functions works within that scope.

2️⃣ When You Need njs: Variable Values nginx Configuration Alone Cannot Change

The WordPress plugin WP Rocket names the cache folders of posts with Korean URLs in lowercase percent-encoding, while browsers request the same URL encoded in uppercase. A WordPress site runs on this server.

If nginx builds the cache file path from the request URL as is, the letter case does not match, the file is not found, and the request goes on to Apache and PHP.

nginx configuration alone cannot lowercase this value. [map] only picks one of the values you set and cannot transform strings, and before installation neither the [nginx -V] output nor the modules folder showed a perl or lua module.

If only a few URLs need changing, a [map] lookup table can handle it, but you have to rebuild the table and reload every time a new post appears. When every URL needs its own value converted by the same rule, a single [js_set] function also covers new posts, tags and category URLs.

The perl module's [perl_set] can be used in the same place, and the repository has a package matched to nginx, but the official nginx documentation describes the perl module as experimental.

3️⃣ Installing nginx njs: In the Same Version as the Installed nginx

Installing by the package name alone looks like it would only add njs, but the latest njs in the repository requires the latest nginx, so nginx gets upgraded too. In the pre-install check, the install command without a version showed a plan to move nginx from 1.28.0 to 1.30.6.

So start by checking the nginx version that is installed now.

rpm -q nginx
nginx-1.28.0-1.el9.ngx.x86_64

The repository has a separate njs package matched to each nginx version; in the version number, the part before + is the nginx version and the part after it is the njs version.

dnf list --showduplicates nginx-module-njs | grep '1.28.0+'
nginx-module-njs.x86_64         1:1.28.0+0.9.4-1.el9.ngx           @nginx-stable
nginx-module-njs.x86_64         1:1.28.0+0.8.10-1.el9.ngx          nginx-stable
nginx-module-njs.x86_64         1:1.28.0+0.9.0-1.el9.ngx           nginx-stable
nginx-module-njs.x86_64         1:1.28.0+0.9.1-1.el9.ngx           nginx-stable
nginx-module-njs.x86_64         1:1.28.0+0.9.2-1.el9.ngx           nginx-stable
nginx-module-njs.x86_64         1:1.28.0+0.9.3-1.el9.ngx           nginx-stable
nginx-module-njs.x86_64         1:1.28.0+0.9.4-1.el9.ngx           nginx-stable

Six versions, 0.8.10 through 0.9.4, are matched to nginx 1.28.0, and the top @nginx-stable line is the one already installed. Install the newest of them, 0.9.4, spelling out the full version number.

dnf install nginx-module-njs-1:1.28.0+0.9.4-1.el9.ngx

You can check with rpm which nginx version the installed package requires.

rpm -q --requires nginx-module-njs | grep nginx
nginx-r1.28.0
rpm -ql nginx-module-njs | grep -v build-id
/usr/bin/njs
/usr/lib64/nginx/modules/ngx_http_js_module-debug.so
/usr/lib64/nginx/modules/ngx_http_js_module.so
/usr/lib64/nginx/modules/ngx_stream_js_module-debug.so
/usr/lib64/nginx/modules/ngx_stream_js_module.so
/usr/share/doc/nginx-module-njs
/usr/share/doc/nginx-module-njs/CHANGES
/usr/share/doc/nginx-module-njs/COPYRIGHT

The installed files are the HTTP module, the stream module that handles TCP/UDP, a debug build of each, and the njs command-line tool. This setup loads only one file, the HTTP module [ngx_http_js_module.so].

4️⃣ Setting Up js_set: From Loading the Module to the Cache File Path

Installing the package does not make nginx load the module automatically; nginx.conf needs a [load_module] line at the very top. According to the official documentation, [load_module] is a main-context directive, so it goes outside the events and http blocks.

📂 /etc/nginx/Config folder
nginx.confload_module at the top — loads the module
njs/rocket.jsFunction lc — lowercases only the percent-encoding
conf.d/
rocket-njs.confhttp block — js_import · js_set
example.com.confserver block — $rocket_uri in the cache file path
# top of /etc/nginx/nginx.conf (outside the events and http blocks)
load_module modules/ngx_http_js_module.so;

The conversion function lives in a .js file separate from the config files, and the nginx configuration imports the function exported with export default on the last line.

// /etc/nginx/njs/rocket.js — lowercase only the percent-encoding %XX
function lc(r) {
    return r.variables.request_uri.replace(/%[0-9A-F]{2}/g, function (m) { return m.toLowerCase(); });
}
export default { lc };

The function reads the [request_uri] variable from the request object r and lowercases only the two characters after each percent sign where they are uppercase, then returns the result. Latin letters are left as they are, so English URLs with uppercase letters do not change; this matches the rule WP Rocket uses to name cache folders.

# inside the http block (a .conf file in conf.d)
js_import rkt from /etc/nginx/njs/rocket.js;
js_set $rocket_uri rkt.lc;

In [js_import], rkt is the name used to call functions inside the module, and [js_set] turns that function's result into the [$rocket_uri] variable. According to the official documentation, the function runs when the variable is first referenced in a request and supports synchronous processing only.

# server block — $request_uri in the cache file path becomes $rocket_uri
set $rocket_file '/wp-content/cache/wp-rocket/example.com${rocket_uri}index$rocket_mobile-https.html';

The only change is this one line that builds the cache file path; the checks that exclude login cookies, queries and POST requests from the cache stay as they are.

5️⃣ Applying and Checking: Error Log and Response Headers After a Reload

Run the configuration test first, and reload if it passes.

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx

The new [load_module] line took effect with a reload, without a restart. The error log recorded a line for initializing the njs virtual machine, and there were no JavaScript errors.

grep 'js vm init' /var/log/nginx/error.log | head -1
2026/10/17 18:21:35 [notice] 27992#27992: js vm init njs: 0000559FDF6D0E00

The njs at the end of the line is the name of the engine in use. The same line is also logged when you test the configuration with [nginx -t], so match it against the time of the reload.

curl -sI 'https://example.com/-/' | grep -i 'x-rocket\|content-type'
content-type: text/html
x-rocket-nginx-serving-file: HIT

Even for a Korean URL encoded in uppercase, the header showing that nginx served the cache file was present. Requests with queries or login cookies and POST requests still went to PHP without that header after the change.

6️⃣ Rolling Back njs and Updating nginx

To roll back, change [$rocket_uri] in the cache file path back to [$request_uri], remove the [js_import]/[js_set] config file and the [load_module] line, then run the configuration test and reload.

# server block — back to the original variable
set $rocket_file '/wp-content/cache/wp-rocket/example.com${request_uri}index$rocket_mobile-https.html';

You can leave the package installed: without the [load_module] line nginx does not load it, so remove the package only when you want it gone completely.

dnf remove nginx-module-njs

When you update nginx, the njs package must move up with it to the version matched to the new nginx. Before updating, check with [dnf list --showduplicates] that the repository has the njs match for the new nginx version.

🔢 FAQ & Recommended Content

The official nginx documentation marks the default njs engine as deprecated from version 1.0.0 and recommends the QuickJS engine, which supports ES2023, for new setups. You choose the engine with the [js_engine qjs] directive, available since version 0.8.6. The 0.9.4 install in this article ran on the default njs engine, and switching to QuickJS was not tested.

It is a standalone command-line tool for developing and debugging njs code without nginx (according to the official documentation). It comes with the nginx-module-njs package, and checking its version with [njs -v] shows 0.9.4, the same as the module.

If you only handle HTTP requests, [ngx_http_js_module] alone is enough. The package also includes [ngx_stream_js_module] for TCP/UDP sessions, and the official install documentation says to load whichever of the two you need with [load_module].

🧭 WP Rocket and Perfmatters related posts 15 posts · 3 tools
🗂️ What is WP?
📋 WP Rocket settings guide 📄 Perfmatters guide 📄 WP Rocket With Perfmatters 📄 WP Rocket MCP Server 📄 WP Rocket Korean URLs 📄 nginx njs 📄 WP Rocket Multilingual Cache 📄 gclid Cache
📄 WP Rocket speed test 📝 Cache and optimization plugins 📝 Speed improvement case
📋 Cache: definition and all fields 📄 Website cache & server-side cache
📋 BEST hosting TOP 7 📄 Fast hosting 2026
🛠️ WordPress Detection 🛠️ Whois Domain Lookup 🛠️ Check My IP Address
WP Rocket Settings Guide: Default Setup for Each Menu and a 20% Promo Code
WordPress · WP Rocket
WP Rocket Settings Guide: Default Setup for Each Menu and a 20% Promo Code
WP Rocket's basic settings for each menu and its pricing.
View Post →Current article
Perfmatters Plugin Review & Guide: WordPress Performance Optimization Settings After 5 Years of Use
WordPress · Perfmatters
Perfmatters Plugin Review & Guide: WordPress Performance Optimization Settings After 5 Years of Use
Recommended Perfmatters settings for each menu and how to use Script Manager.
View Post →Current article
WP Rocket With Perfmatters: How to Set Up Optimization Features
WordPress · Perfmatters
WP Rocket With Perfmatters: How to Set Up Optimization Features
How to set things up when using WP Rocket and Perfmatters together.
View Post →Current article
WP Rocket MCP Server: Handling Cache and Settings with AI Tools
WordPress · MCP
WP Rocket MCP Server: Handling Cache and Settings with AI Tools
How to handle WP Rocket cache and settings from AI tools.
View Post →Current article
WP Rocket Korean URL request flow diagram
WordPress · WP Rocket
WP Rocket Korean URLs
Why nginx can't find the cache file because of the letter case of the encoding in Korean URL requests, and how to fix it with njs.
View Post →Current article
Diagram of nginx njs variables and cache file paths
Server · nginx
nginx njs: Installing the Module, Setting js_set, and Applying a Reload to Create JavaScript Variables
How to install the njs module, which creates variables with JavaScript functions inside the nginx configuration, and apply js_set variables with a reload.
View Post →Current article
WP Rocket multilingual cache post-save flow diagram
WordPress · WP Rocket
WP Rocket Multilingual Cache
Why saving a draft deletes the translated page cache along with the whole language folder, and the code that prevents it.
View Post →Current article
Flow in which a request with an ad query goes to the origin server when the CDN has no copy, passes from nginx to Apache, and WP Rocket drops the ad query and answers with the same cache file
Server · Cache
gclid Cache: Ad Click Query Strings, Page Cache Keys, and Measured Time to First Byte
Measuring, by time to first byte, how the gclid query added to the end of ad-click URLs is ignored and the request is served from the cache file of the URL without the query.
View Post →Current article

ℹ️ Affiliate Disclosure
This site's content contains affiliate links. When a visitor buys a product or service through one of them, the site receives a commission from the seller. The amount the buyer pays(it goes down during event discounts ↓)does not go up. Posted prices, discounts, and stock reflect the time of writing and may differ, so confirm with the seller before buying. Products are chosen and reviewed by our own standards, and commissions do not affect the order or content of reviews.

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your humanity: 9   +   5   =