
Зачем njs в 2025 году и что это такое
njs — это лёгкий JavaScript‑движок, встроенный в Nginx через динамические модули ngx_http_js_module и ngx_stream_js_module. Он позволяет внедрять в конфигурацию немного прикладной логики: вычислять переменные на лету, условно проксировать запросы в разные backend‑ы, править заголовки и даже формировать ответ без участия внешнего приложения. Это не замена полноценному бэкенду, а инструмент «на краю», там, где требуется компактное и быстрое решение.
Когда njs уместен:
- Микро‑обработчики на периметре: A/B‑маршрутизация, canary‑трафик, проброс специальных заголовков.
- Условные редиректы и каноникализация URL/хостов с учётом множества факторов.
- Быстрые ответы для health‑check, ping, метаданных, JSON‑заглушек.
- Тонкая настройка headers на ответ и запрос, где стандартных директив не хватает.
- Предобработка для API: простая авторизация по ключу, верификация формата аргументов.
Главная идея njs — не заменить приложение, а разгрузить его от рутинной логики, которую дешевле и быстрее выполнить в самом Nginx.
Установка и включение модулей
В большинстве дистрибутивов njs доступен как динамический модуль. На практике понадобятся два шага: установить пакет и явно подгрузить модуль в nginx.conf. На типовом проде удобнее управлять этим на VDS, где есть root и контроль над версиями Nginx. На классическом shared‑хостинге модуль может быть недоступен.
Примеры установки по семействам ОС
Debian/Ubuntu:
apt update
apt install libnginx-mod-http-js libnginx-mod-stream-jsАльтернативные сборки Nginx (mainline) могут называть пакет иначе, например nginx-module-njs. Суть та же: модуль для HTTP и, при необходимости, для Stream.
RHEL/AlmaLinux/Rocky:
dnf install nginx-module-njsПодключение динамических модулей
Проверьте путь к модулям в вашей системе. Часто это /usr/lib/nginx/modules. В начале nginx.conf пропишите:
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;Если нужен только HTTP, вторую строку можно опустить.
Быстрый тур по примитивам njs
Ключевые директивы:
js_import— подключает JS‑модуль (файл) и даёт ему псевдоним.js_set— вызывает JS‑функцию для вычисления значения nginx‑переменной.js_content— отдаёт контент из JS‑функции (контент‑хендлер).js_header_filter— фильтр ответных заголовков.js_access— проверка доступа в фазе access (например, авторизация по ключу).
Объект запроса r внутри функций предоставляет:
r.method,r.uri,r.args,r.variables(Nginx‑переменные по имени),r.headersInиr.headersOut(входящие/исходящие заголовки),r.return(status, body)для ответа,r.log(msg)иr.warn(msg)для логов,- полезны также
r.internalRedirect,r.subrequest()в более сложных схемах.

Структура проекта и подключение файла с кодом
Рекомендуемая структура:
- Хранить njs‑файлы отдельно, например
/etc/nginx/njs/http.js. - Импортировать модуль на уровне
http { ... }один раз. - Функции экспортировать именованно или через
export default.
# /etc/nginx/nginx.conf (фрагмент)
load_module modules/ngx_http_js_module.so;
http {
js_import http from /etc/nginx/njs/http.js;
server {
listen 80;
server_name example.local;
location = /healthz {
js_content http.hello;
}
}
}// /etc/nginx/njs/http.js
function hello(r) {
r.headersOut['Content-Type'] = 'application/json; charset=utf-8';
r.return(200, JSON.stringify({ status: 'ok', ts: Date.now() }));
}
export default { hello };После правок JS‑файла делайте перезагрузку конфигурации:
nginx -t
nginx -s reloadПример 1. Динамический выбор апстрима по cookie/заголовку
Задача: размазывать трафик между двумя версиями приложения на основании cookie exp или заголовка. В njs вычислим переменную $backend, а proxy_pass подставит её в runtime.
# /etc/nginx/nginx.conf (фрагмент)
http {
js_import http from /etc/nginx/njs/http.js;
upstream app_v1 { server 127.0.0.1:8081; }
upstream app_v2 { server 127.0.0.1:8082; }
server {
listen 80;
server_name example.local;
js_set $backend http.choose_upstream;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://$backend;
}
}
}// /etc/nginx/njs/http.js
function getCookie(r, name) {
const c = r.headersIn['Cookie'];
if (!c) return null;
const m = c.match(new RegExp('(?:^|; )' + name + '=([^;]+)'));
return m ? decodeURIComponent(m[1]) : null;
}
function choose_upstream(r) {
const header = r.headersIn['X-Exp'] || '';
const cookie = getCookie(r, 'exp') || '';
// приоритет заголовка, потом cookie
const mark = (header || cookie).toLowerCase();
if (mark === 'v2' || mark === 'beta') return 'app_v2';
// простая псевдослучайная раскатка 10% на v2
const h = (r.variables.request_id || r.remoteAddress || Date.now()).toString();
const bucket = Math.abs(hashCode(h)) % 10;
return bucket === 0 ? 'app_v2' : 'app_v1';
}
function hashCode(s) {
let h = 0;
for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
return h;
}
export default { choose_upstream };Такой подход позволяет точно контролировать трафик без внешней зависимости. При этом всё прозрачно в конфигурации и логах Nginx.
Пример 2. Каноникализация хоста и схемы одной функцией
Стандартные return/rewrite покрывают 90% кейсов, но как только правил несколько и они зависят друг от друга, удобнее собрать их в одну JS‑функцию, чтобы не городить каскад директив.
# nginx.conf (фрагмент)
server {
listen 80;
listen 443 ssl;
# ssl_* директивы опущены ради краткости
location / {
js_content http.canonical_redirect;
}
}// http.js (фрагмент)
function canonical_redirect(r) {
const host = (r.headersIn['Host'] || '').toLowerCase();
const scheme = r.variables.scheme;
let targetHost = host.replace(/^www\./, '');
// Принудительно HTTPS и без www
const needHttps = scheme !== 'https';
const needHost = host !== targetHost;
if (needHttps || needHost) {
const url = 'https://' + targetHost + r.uri + (r.args ? '?' + r.args : '');
r.return(301, url);
return;
}
// Если всё уже канонично, отдаём контент бэкенда
r.internalRedirect('@upstream');
}
export default { canonical_redirect };# в server {} добавьте внутренний location для проксирования
location @upstream {
proxy_set_header Host $host;
proxy_pass http://app_v1;
}Если вы как раз готовите принудительный HTTPS и HSTS, посмотрите материал «миграция на HTTPS и HSTS» — это поможет не допустить лишних редиректов и кэш‑проблем, а также подобрать правильные сроки политики HSTS: миграция на HTTPS и HSTS. Для продакшен‑домена заранее оформите актуальные SSL-сертификаты.
Пример 3. Единый фильтр ответных заголовков
Когда нужно гибко добавлять/менять заголовки ответа в зависимости от пути, метода или исхода обработки, удобен js_header_filter. Например, добавим ряд безопасных заголовков только для HTML‑страниц.
# nginx.conf (фрагмент)
http {
js_import http from /etc/nginx/njs/http.js;
js_header_filter http.add_security_headers;
server { ... }
}// http.js (фрагмент)
function isHtml(r) {
const ct = (r.headersOut['Content-Type'] || '').toLowerCase();
return ct.includes('text/html');
}
function add_security_headers(r) {
if (!isHtml(r)) return;
if (!r.headersOut['X-Frame-Options']) r.headersOut['X-Frame-Options'] = 'SAMEORIGIN';
if (!r.headersOut['X-Content-Type-Options']) r.headersOut['X-Content-Type-Options'] = 'nosniff';
if (!r.headersOut['Referrer-Policy']) r.headersOut['Referrer-Policy'] = 'strict-origin-when-cross-origin';
}
export default { add_security_headers };Про CORS заголовки и кросс‑доменные шрифты/скрипты — отдельный разбор: настройка CORS в Nginx и Apache.

Пример 4. Простая авторизация API‑ключом на уровне access
Для внутренних API иногда достаточно валидировать заголовок X-API-Key. Сделаем это в фазе access и пустим запрос дальше только при успехе.
# nginx.conf (фрагмент)
location /api/ {
js_access http.check_api_key;
proxy_pass http://api_backend;
}// http.js (фрагмент)
const ALLOWED_KEYS = new Set(['key123', 'key456']);
function check_api_key(r) {
const key = r.headersIn['X-API-Key'];
if (!key || !ALLOWED_KEYS.has(key)) {
r.return(403, 'Forbidden');
return;
}
// Ничего не возвращаем — запрос продолжит обработку
}
export default { check_api_key };Важно: в реальных сценариях ключи храните вне репозитория конфигурации, используйте переменные окружения или внешние секреты, а также аудит попыток.
Пример 5. Мини‑фильтр тела ответа
njs умеет фильтровать тело ответа. Осторожный пример: маскировка электронных адресов в JSON/HTML. Производительность здесь критична, используйте на малых объёмах и точечно.
# nginx.conf (фрагмент)
http {
js_import http from /etc/nginx/njs/http.js;
server {
listen 80;
js_header_filter http.add_security_headers;
js_body_filter http.mask_emails;
location / { proxy_pass http://app_v1; }
}
}// http.js (фрагмент)
function mask_emails(r, data, flags) {
// Фильтр обязан передать буфер дальше: либо модифицированный, либо как есть
try {
const replaced = data.replace(/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+/g, '***@***');
r.sendBuffer(replaced, flags);
} catch (e) {
r.warn('mask_emails error: ' + e);
r.sendBuffer(data, flags);
}
}
export default { mask_emails };Для тяжёлых трансформаций лучше перенести логику в бэкенд или специализированный фильтр.
Отладка и тестирование njs‑кода
Минимальный цикл отладки:
- Понизьте уровень шума в
error_logдоnotice/infoи используйтеr.log()/r.warn()внутри функций. - Проверяйте синтаксис:
nginx -t. - Перезагружайте конфигурацию:
nginx -s reload. - Локально воспроизводите входные данные: заголовки, cookie, URI — это ускоряет поиск ошибок.
Отдельная утилита интерпретатора njs (если установлена в системе) позволяет быстро проверить чистую JS‑логику и регулярные выражения, но доступ к объекту r вне Nginx недоступен.
Производительность и ограничения
Несколько практических правил:
- njs‑функция вызывается на каждый запрос/ответ в соответствующей фазе. Держите код небольшим и детерминированным.
- Избегайте тяжёлых вычислений и работы с крупными буферами в
js_body_filter— это упирается в CPU и латентность. - Храните таблицы и константы в модуле, а не генерируйте их заново на каждый вызов.
- Старайтесь работать строками и простыми структурами. Сложный парсинг JSON возможен, но контролируйте размеры.
- Это не Node.js: нет ввода‑вывода, файлов и сокетов из JS. Всё взаимодействие — через API Nginx.
По сравнению с альтернативами:
- OpenResty (lua) — богаче экосистема и возможностей, но сложнее. njs — проще для «точечных» задач.
- Nginx Unit — отдельный рантайм приложений. njs быстрее стартует и ближе к конфигурации Nginx.
- map/if/try_files — сначала попытайтесь решить задачу штатными директивами. njs — когда этих средств уже недостаточно.
Безопасность
Несмотря на компактность, njs влияет на критический путь обработки запроса:
- Валидация входных данных обязательна: не доверяйте заголовкам и параметрам, ограничивайте длину и формат.
- Ошибки в коде не должны «ронять» пайплайн: окружайте уязвимые участки
try/catchи логируйте. - Не внедряйте чувствительную бизнес‑логику, если она требует секрета на стороне сервера — храните секреты вне конфигурации и применяйте контроль доступа.
- Разделяйте зоны ответственности: фильтры тела — только там, где без них никак.
Типичные ошибки и как их избежать
- Модуль не подгружается: проверьте путь
load_moduleи наличие пакета. Сообщения ищите вerror_logпри старте. - Функция не видна: совпадает ли имя в
js_importи экспорт в файле (export default { fn }или явный экспорт)? - Переменная пустая: для
js_setвозвращайте строку из функции. Не вызывайтеr.return()внутри вычислителя переменной. - Циклы редиректов: следите, чтобы
canonical_redirectпрекращал редирект, когда всё уже канонично. - Регулярки «задушили» CPU: кешируйте шаблоны, упрощайте выражения, ограничивайте область применения фильтра.
Паттерны, которые часто «выстреливают»
- Маршрутизация по версии клиента: cookie/заголовки — в
js_setпеременную, затемproxy_passпо имени апстрима. - Единый ответ для ping/health:
js_contentс JSON и минимальными полями для мониторинга. - Условные security headers: общий
js_header_filterс проверкой типа контента и путей. - Простая авторизация:
js_accessдля ключей/токенов с чётким журналированием. - Нормализация URL: функция, аккуратно объединяющая правила, чтобы не плодить
ifиrewrite.
Итоги
njs уверенно закрывает слой «умной конфигурации» Nginx: быстрая маршрутизация, тонкие правила для заголовков, компактные ответы и простые проверки доступа. Это инструмент для админов и девопсов, которым важно реагировать быстро и не тащить в бой лишние компоненты. Прежде чем тянуть бэкенд ради мелочи, попробуйте njs — часто это тот самый «небольшой JavaScript» на периметре, которого достаточно.


