lolly/docs/nginx/22-nginx-third-party-modules.md
xfy 972eab4267 refactor(docs): 重构文档目录结构,nginx 文档移至子目录
将 docs/ 根目录下的 nginx 相关文档统一移动到 docs/nginx/ 子目录,
提高文档组织性和可维护性。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 10:48:14 +08:00

23 KiB
Raw Blame History

NGINX 第三方扩展模块详解

概述

第三方模块极大地扩展了 NGINX 的功能边界,使其能够处理各种复杂场景。本文档详细介绍常用的第三方扩展模块及其配置方法。


1. NJS (nginx JavaScript) 模块

NJS 是 NGINX 官方提供的 JavaScript 解释器,允许在 NGINX 中使用 JavaScript 编写动态逻辑。

核心指令

js_import

导入 JavaScript 文件。

js_import /path/to/script.js;                    # 导入整个模块
js_import main from /path/to/script.js;          # 命名导入

js_set

设置 NGINX 变量为 JavaScript 函数返回值。

js_set $auth_token validate_auth;                # 变量值由 validate_auth 函数返回

js_content

使用 JavaScript 生成响应内容。

location /api/custom {
    js_content custom_response;
}

js_body_filter

使用 JavaScript 修改响应体。

location / {
    js_body_filter modify_body;
    proxy_pass http://backend;
}

JavaScript 语法支持

NJS 基于 ECMAScript 5.1,支持部分 ES6 特性:

特性 支持情况 示例
基础类型 完整 string, number, boolean, null
函数 完整 function foo() {}
对象 完整 var obj = { a: 1 };
数组 完整 var arr = [1, 2, 3];
let/const 支持 let x = 1; const y = 2;
箭头函数 支持 (x) => x * 2
模板字符串 支持 `Hello ${name}`
Promise 部分 Promise.then()catch()
async/await 部分 async function
类 (class) 不支持 -
解构赋值 部分 const {a, b} = obj

NGINX JavaScript 对象

// r: 请求对象
function handler(r) {
    // 请求信息
    r.method;           // HTTP 方法
    r.uri;              // 请求 URI
    r.args;             // 查询参数对象
    r.headersIn;        // 请求头对象
    r.headersOut;       // 响应头对象
    r.remoteAddress;    // 客户端 IP

    // 响应操作
    r.return(200, "Hello");     // 直接返回
    r.error("message");          // 记录错误日志
    r.log("message");            // 记录日志
    r.warn("message");           // 记录警告

    // 子请求
    r.subrequest('/internal', function(reply) {
        // 处理子请求响应
        r.return(200, reply.body);
    });
}

配置示例

动态路由

// router.js
function route(r) {
    var version = r.headersIn['X-API-Version'];

    if (version === 'v2') {
        r.internalRedirect('/api/v2' + r.uri);
    } else {
        r.internalRedirect('/api/v1' + r.uri);
    }
}

export default { route };
js_import main from /etc/nginx/router.js;

location /api/ {
    js_content main.route;
}

location /api/v1/ {
    proxy_pass http://backend_v1/;
}

location /api/v2/ {
    proxy_pass http://backend_v2/;
}

自定义认证

// auth.js
async function auth(r) {
    // 从数据库验证 token
    var reply = await r.subrequest('/auth/check', {
        method: 'POST',
        body: JSON.stringify({ token: r.headersIn['Authorization'] })
    });

    if (reply.status === 200) {
        var result = JSON.parse(reply.body);
        r.headersOut['X-User-Id'] = result.user_id;
        r.internalRedirect('/internal' + r.uri);
    } else {
        r.return(401, "Unauthorized");
    }
}

export default { auth };
js_import auth from /etc/nginx/auth.js;

location /protected/ {
    js_content auth.auth;
}

location /internal/ {
    internal;                    # 只允许内部跳转
    proxy_pass http://backend/;
}

JWT 验证

// jwt.js
function verify_jwt(r) {
    var jwt = r.headersIn['Authorization'];
    if (!jwt) {
        return null;
    }

    jwt = jwt.replace('Bearer ', '');

    var parts = jwt.split('.');
    if (parts.length !== 3) {
        return null;
    }

    try {
        var header = JSON.parse(Buffer.from(parts[0], 'base64').toString());
        var payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());

        // 验证过期时间
        if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
            return null;
        }

        return payload;
    } catch (e) {
        return null;
    }
}

function get_user_id(r) {
    var payload = verify_jwt(r);
    return payload ? payload.sub : '';
}

export default { get_user_id };
js_import jwt from /etc/nginx/jwt.js;
js_set $user_id jwt.get_user_id;

server {
    location /api/ {
        if ($user_id = "") {
            return 401 "Invalid or expired token";
        }

        proxy_set_header X-User-Id $user_id;
        proxy_pass http://backend;
    }
}

安装

# 从源码编译
./configure \
    --add-dynamic-module=/path/to/njs/nginx \
    --with-compat

make modules

# 加载模块
# nginx.conf
load_module modules/ngx_http_js_module.so;

2. ngx_http_lua_module (OpenResty)

ngx_http_lua_module 是 OpenResty 平台的核心组件,允许在 NGINX 中嵌入 Lua 脚本。

OpenResty 平台介绍

OpenResty 是一个基于 NGINX 的全功能 Web 平台,集成了:

  • LuaJIT高性能 Lua 解释器)
  • 大量精心设计的 Lua 库
  • 完整的协程调度器
  • 异步非阻塞 I/O 能力

Lua 指令概述

指令 上下文 说明
init_by_lua http NGINX 启动时执行
init_worker_by_lua http 每个 worker 启动时执行
set_by_lua server/location/if 设置变量值
content_by_lua location/location if 生成响应内容
access_by_lua http/server/location 访问控制阶段执行
rewrite_by_lua http/server/location 重写阶段执行
header_filter_by_lua http/server/location 处理响应头
body_filter_by_lua http/server/location 处理响应体
log_by_lua http/server/location 日志阶段执行
balancer_by_lua upstream 自定义负载均衡

配置示例

http {
    # 全局初始化
    init_by_lua_block {
        require "cjson"
        redis = require "resty.redis"
    }

    server {
        location /api {
            content_by_lua_block {
                local cjson = require "cjson"

                local data = {
                    message = "Hello from Lua",
                    time = ngx.time()
                }

                ngx.header['Content-Type'] = 'application/json'
                ngx.say(cjson.encode(data))
            }
        }

        location /auth {
            access_by_lua_block {
                local token = ngx.var.http_authorization
                if not token then
                    ngx.exit(ngx.HTTP_UNAUTHORIZED)
                end
                -- 验证 token 逻辑
            }
        }
    }
}

应用场景

场景 说明
API 网关 路由、限流、认证、日志
WAF Web 应用防火墙,自定义安全规则
动态负载均衡 基于实时指标的流量调度
边缘计算 在边缘节点执行业务逻辑
实时数据处理 日志实时分析、指标采集

3. ngx_http_brotli_module (Brotli 压缩)

Brotli 是 Google 开发的新一代压缩算法,相比 Gzip 提供更好的压缩比。

核心指令

brotli

启用或禁用 Brotli 压缩。

brotli on;                       # 启用动态压缩
brotli_static on;                # 启用静态 .br 文件支持

brotli_comp_level

设置压缩级别0-11

brotli_comp_level 6;             # 默认值,平衡压缩比和速度

级别说明

级别 压缩比 速度 适用场景
1 最快 高流量低延迟场景
6 中等 通用场景(推荐)
11 最高 最慢 预压缩静态资源

brotli_types

指定压缩的 MIME 类型。

brotli_types text/plain text/css application/json application/javascript text/xml application/xml;

完整配置示例

# 动态压缩
location / {
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/json
                 application/javascript text/xml application/xml
                 application/rss+xml text/javascript
                 application/vnd.ms-fontobject application/x-font-ttf
                 font/opentype image/svg+xml image/x-icon;

    proxy_pass http://backend;
}

# 静态预压缩文件
location ~ \.(js|css|html|json)$ {
    brotli_static on;            # 优先查找 .br 文件
    gzip_static on;              # 回退到 .gz 文件
    try_files $uri $uri/ =404;
}

Brotli vs Gzip 对比

特性 Brotli Gzip
压缩比 更高(约 20-30% 较低
压缩速度 较慢 较快
解压速度
浏览器支持 现代浏览器 几乎所有浏览器
字典支持 有(静态字典)
最佳用途 静态资源预压缩 动态内容压缩

预压缩静态资源

# 使用 brotli 命令行工具压缩
brotli -q 11 -o file.js.br file.js

# 批量压缩
find . -type f \( -name "*.js" -o -name "*.css" \) -exec brotli -q 11 -o {}.br {} \;

4. ngx_cache_purge_module (缓存清除)

该模块允许通过 HTTP 请求清除 NGINX 代理缓存中的特定内容。

核心指令

proxy_cache_purge

配置缓存清除的访问控制。

proxy_cache_purge on;            # 启用清除功能
proxy_cache_purge $purge_method; # 基于变量控制

fastcgi_cache_purge

FastCGI 缓存清除。

fastcgi_cache_purge on;

配置示例

http {
    proxy_cache_path /var/cache/nginx levels=1:2
                     keys_zone=my_cache:10m
                     max_size=1g inactive=60m;

    map $request_method $purge_method {
        default 0;
        PURGE   1;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_cache my_cache;
            proxy_cache_key "$scheme$host$request_uri";
            proxy_cache_purge $purge_method;
            proxy_pass http://backend;

            # 可选:限制 PURGE 来源
            if ($purge_method = 1) {
                allow 127.0.0.1;
                allow 192.168.1.0/24;
                deny all;
            }
        }
    }
}

使用方式

# 清除单个 URL
curl -X PURGE http://example.com/path/to/resource

# 清除通配符(需配置 proxy_cache_purge 支持)
curl -X PURGE http://example.com/*

# 使用 API Key 认证
curl -X PURGE -H "X-Purge-Key: secret123" http://example.com/api/data

安全考虑

# 仅允许特定 IP 执行 PURGE
location / {
    proxy_cache_purge $purge_method;

    if ($purge_method = 1) {
        # 检查来源 IP
        set $allowed 0;
        if ($remote_addr ~ "^192\.168\.") {
            set $allowed 1;
        }

        # 或检查 API Key
        if ($http_x_purge_key != "secret_key") {
            set $allowed 0;
        }

        if ($allowed = 0) {
            return 403 "Purge not allowed";
        }
    }
}

5. ngx_headers_more_module (增强头部)

提供更灵活的 HTTP 头部操作能力。

核心指令

more_set_headers

设置响应头,支持条件表达式。

# 基础用法
more_set_headers "Server: MyServer";
more_set_headers "X-Frame-Options: DENY";

# 状态码限定
more_set_headers "X-Debug: on" always;
more_set_headers -s 200 302 "Cache-Control: public";

# 多行
more_set_headers "X-Powered-By: NGINX
X-Custom: Value";

more_clear_headers

清除响应头。

# 清除单个
more_clear_headers Server;

# 清除多个
more_clear_headers Server X-Powered-By X-AspNet-Version;

# 使用通配符
clear_headers "X-*";

more_set_input_headers

设置请求头(传递给后端)。

more_set_input_headers "X-Real-IP: $remote_addr";

配置示例

隐藏 Server 信息

server {
    # 清除默认 Server 头
    more_clear_headers Server;

    # 可选:设置自定义值
    more_set_headers "Server: Secure Server";

    location / {
        proxy_pass http://backend;
    }
}

添加安全头部

server {
    # 点击劫持防护
    more_set_headers "X-Frame-Options: SAMEORIGIN";

    # XSS 防护
    more_set_headers "X-XSS-Protection: 1; mode=block";

    # MIME 类型嗅探防护
    more_set_headers "X-Content-Type-Options: nosniff";

    # 内容安全策略
    more_set_headers "Content-Security-Policy: default-src 'self'";

    # 引用策略
    more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";

    # HSTSHTTPS 严格传输安全)
    more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";

    location / {
        proxy_pass http://backend;
    }
}

条件头部设置

# 仅在特定路径添加头部
location /api/ {
    more_set_headers "X-API-Version: 2.0";
    proxy_pass http://api_backend;
}

# 基于状态码
error_page 500 502 503 504 /50x.html;
location = /50x.html {
    more_set_headers "X-Error-Source: upstream";
    internal;
}

6. nginx-rtmp-module (流媒体)

为 NGINX 添加 RTMP/HLS/DASH 流媒体服务能力。

核心配置块

rtmp {
    server {
        listen 1935;             # RTMP 默认端口
        chunk_size 4096;

        # 推流应用
        application live {
            live on;

            # HLS 输出
            hls on;
            hls_path /var/www/hls;
            hls_fragment 3s;
            hls_playlist_length 60s;

            # DASH 输出
            dash on;
            dash_path /var/www/dash;
            dash_fragment 3s;
        }

        # 录制应用
        application record {
            live on;
            record all;
            record_path /var/recordings;
            record_unique on;
            record_suffix .flv;
        }
    }
}

关键指令

指令 说明
live on 启用直播模式
hls on 启用 HLS 输出
hls_path HLS 文件存储路径
hls_fragment HLS 切片时长
dash on 启用 DASH 输出
push rtmp://... 推流到其他服务器
pull rtmp://... 从其他服务器拉流

完整直播配置示例

rtmp {
    server {
        listen 1935;
        listen [::]:1935 ipv6only=on;

        application live {
            live on;

            # 推流认证
            on_publish http://localhost:8080/auth;

            # HLS 配置
            hls on;
            hls_path /var/www/stream/hls;
            hls_fragment 3s;
            hls_playlist_length 60s;
            hls_nested on;              # 为每个流创建子目录
            hls_cleanup on;             # 自动清理旧切片

            # 多码率 HLS
            hls_variant _low BANDWIDTH=500000;
            hls_variant _mid BANDWIDTH=1500000;
            hls_variant _high BANDWIDTH=3000000;

            # DASH 配置
            dash on;
            dash_path /var/www/stream/dash;
            dash_fragment 3s;
            dash_nested on;

            # 录制配置
            record all;
            record_path /var/recordings;
            record_unique on;
            record_suffix .flv;
            record_max_size 500M;
            record_max_frames 2;

            # 推流到其他平台
            push rtmp://youtube.com/live2/stream_key;
            push rtmp://facebook.com/rtmp/stream_key;
        }
    }
}

http {
    server {
        listen 80;
        server_name stream.example.com;

        # HLS 播放
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            root /var/www/stream;
            add_header Cache-Control no-cache;
            add_header Access-Control-Allow-Origin *;
        }

        # DASH 播放
        location /dash {
            types {
                application/dash+xml mpd;
            }
            root /var/www/stream;
            add_header Cache-Control no-cache;
            add_header Access-Control-Allow-Origin *;
        }

        # 推流认证接口
        location /auth {
            proxy_pass http://auth_backend/verify;
            proxy_set_header Content-Type application/x-www-form-urlencoded;
        }
    }
}

推流与播放

# OBS / FFmpeg 推流
ffmpeg -re -i input.mp4 -c copy -f flv rtmp://server/live/stream_name

# 播放地址
# HLS: http://server/hls/stream_name.m3u8
# DASH: http://server/dash/stream_name.mpd

7. ngx_http_fancyindex_module (美化目录)

提供更美观、功能更强大的目录列表页面。

核心指令

fancyindex

启用美化目录。

fancyindex on;

fancyindex_css_href

自定义 CSS 样式表。

fancyindex_css_href /fancyindex.css;

fancyindex_exact_size

显示精确文件大小。

fancyindex_exact_size off;       # 显示人性化大小1K, 234M, 2G

自定义页眉页脚。

fancyindex_header /header.html;
fancyindex_footer /footer.html;

配置示例

server {
    listen 80;
    server_name files.example.com;
    root /var/www/files;

    location / {
        fancyindex on;
        fancyindex_exact_size off;
        fancyindex_localtime on;        # 使用本地时间
        fancyindex_show_path off;       # 隐藏路径显示
        fancyindex_name_length 50;      # 文件名最大显示长度

        # 自定义样式
        fancyindex_css_href "/style/fancyindex.css";
        fancyindex_header "/style/header.html";
        fancyindex_footer "/style/footer.html";

        # 忽略隐藏文件
        fancyindex_ignore ".." ".*";

        # 默认排序
        fancyindex_default_sort name_desc;
    }

    # 静态资源不显示目录列表
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        fancyindex off;
    }
}

主题配置

创建自定义主题:

<!-- header.html -->
<!DOCTYPE html>
<html>
<head>
    <title>文件列表</title>
    <link rel="stylesheet" href="/style/custom.css">
</head>
<body>
    <div class="container">
        <h1>文件下载中心</h1>

<!-- footer.html -->
    </div>
    <footer>
        <p>&copy; 2024 Example Corp</p>
    </footer>
</body>
</html>

8. Sticky Module (会话保持)

基于 Cookie 的会话保持模块,确保同一客户端的请求始终路由到同一后端服务器。

核心指令

sticky

启用基于 Cookie 的会话保持。

upstream backend {
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com;

    sticky cookie srv_id expires=1h domain=.example.com path=/;
}

参数说明

参数 说明
cookie name Cookie 名称
expires=time Cookie 过期时间
domain=name Cookie 域名
path=path Cookie 路径
secure 仅 HTTPS 传输
httponly 禁止 JavaScript 访问

sticky vs ip_hash 对比

特性 sticky (cookie) ip_hash
会话保持机制 基于 Cookie 基于客户端 IP
支持 NAT/代理 是(无视 IP 变化) 否(同一 NAT 下用户共享会话)
移动端支持 优秀 较差IP 频繁变化)
首次请求 轮询分配 哈希分配
Cookie 依赖 需要 不需要

配置示例

upstream backend {
    server 10.0.0.1:8080 weight=5;
    server 10.0.0.2:8080 weight=5;
    server 10.0.0.3:8080 backup;

    # 基础配置
    sticky cookie srv_id expires=1h domain=.example.com path=/;

    # 完整配置
    # sticky cookie srv_id
    #        expires=1h
    #        domain=.example.com
    #        path=/
    #        secure
    #        httponly;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
upstream backend {
    zone backend 64k;

    server backend1.example.com;
    server backend2.example.com;

    # 尝试 sticky失败时使用 ip_hash
    sticky cookie srv_id expires=1h;
}

# 或使用 route 指令结合其他逻辑
upstream backend {
    server backend1.example.com route=a;
    server backend2.example.com route=b;

    sticky route $route_cookie;
}

9. 安装方法

动态模块安装

动态模块可以在不重新编译 NGINX 的情况下添加功能。

步骤

# 1. 获取与 NGINX 版本匹配的模块源码
cd /usr/local/src
git clone https://github.com/nginx/njs.git
git clone https://github.com/google/ngx_brotli.git

# 2. 下载相同版本的 NGINX 源码
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz
cd nginx-1.24.0

# 3. 配置动态模块
./configure \
    --with-compat \
    --add-dynamic-module=../njs/nginx \
    --add-dynamic-module=../ngx_brotli

# 4. 编译模块
make modules

# 5. 安装模块
mkdir -p /usr/local/nginx/modules
cp objs/ngx_http_js_module.so /usr/local/nginx/modules/
cp objs/ngx_http_brotli_filter_module.so /usr/local/nginx/modules/
cp objs/ngx_http_brotli_static_module.so /usr/local/nginx/modules/

加载模块

# nginx.conf
# 在顶级上下文加载
load_module modules/ngx_http_js_module.so;
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

user nginx;
worker_processes auto;
...

编译安装(静态链接)

将模块静态编译进 NGINX 可执行文件。

# 1. 准备源码
cd /usr/local/src
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -xzf nginx-1.24.0.tar.gz

# 2. 下载第三方模块
git clone https://github.com/openresty/headers-more-nginx-module.git
git clone https://github.com/FRiCKLE/ngx_cache_purge.git

# 3. 配置编译
cd nginx-1.24.0
./configure \
    --prefix=/usr/local/nginx \
    --user=nginx \
    --group=nginx \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_gzip_static_module \
    --with-http_stub_status_module \
    --with-pcre \
    --with-threads \
    --add-module=../headers-more-nginx-module \
    --add-module=../ngx_cache_purge

# 4. 编译安装
make
make install

包管理器安装

Ubuntu/Debian

# NJS 模块
apt install nginx-module-njs

# OpenResty包含 Lua 模块)
apt install openresty

CentOS/RHEL

# 使用官方仓库
yum install nginx-module-njs
yum install nginx-module-image-filter

# 启用模块
echo 'load_module /usr/lib64/nginx/modules/ngx_http_js_module.so;' > /etc/nginx/conf.d/njs.conf

动态模块 vs 编译安装对比

特性 动态模块 静态编译
灵活性 高(可随时加载/卸载) 低(需重新编译)
维护成本
性能 略低(模块加载开销) 略高
部署便捷性
版本兼容性 需版本匹配 无兼容性问题
适用场景 生产环境、频繁更新 性能敏感、定制化需求

最佳实践

  1. 优先使用官方包:通过包管理器安装的模块通常经过充分测试
  2. 版本匹配:动态模块必须与 NGINX 版本完全匹配
  3. 最小化模块:仅加载必要的模块,减少攻击面
  4. 测试环境先行:新模块先在测试环境验证
  5. 文档记录:记录所有加载的第三方模块及其版本