Contents

Nginx 文件服务器实战:FancyIndex 长文件名显示优化(Nginx 系列第 3 期)

Nginx 系列:第 1 期:源码编译安装 · 第 2 期:JSON 日志格式


1. 需求背景:当一个文件名太长

1.1 问题复现

汽车智能座舱团队需要在内网搭建一个 Android 代码发包平台。OTA 升级包的命名规则是:

IMX8QM_MCU_AGL_2026_Q3_Release_Candidate_3_UserDebug_eng_build_20260715_signed.zip

或华为鸿蒙座舱的:

HarmonyOS_Cockpit_4.0.0.126_SP6C00E125R3P6_car_release_20260630_full_ota_update.app

六七十个字符。文件名中包含硬件平台、系统版本、构建类型、日期、签名信息——这些是工程师识别正确包的刚需。

当你直接用 Nginx 的 autoindex on 开启目录浏览时,效果是这样的:

[IMAGE: autoindex-truncated-demo.png]

文件名在 50 个字符左右时被粗暴截断,最后面是一串 ..>。工程师只能靠文件名前半段(如 IMX8QM_MCU_AGL_2026_Q3_Rele...)去猜「这个是不是我要的包?」。猜错了就下载一个 2 GB 的镜像回来,解压完发现不对——这种体验在生产环境中是不可接受的。

1.2 为什么 Nginx 设计成截断?

这不是 Nginx 的 bug——是 autoindex 模块的刻意设计。autoindex 生成了一个 HTML 表格,每一行中有两个 <a> 标签(文件名链接 + 父目录/子目录链接),CSS 是硬编码在 ngx_http_autoindex_module.c 源文件中的。

截断的具体算法(来自 Nginx 源码):

#define NGX_HTTP_AUTOINDEX_NAME_LEN 50
// ...
if (entry[i].name.len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
    entry[i].name.len = NGX_HTTP_AUTOINDEX_NAME_LEN - 3;
    // 追加 "..>" 标记截断
}

直接把 C 字符串 truncate 到 47 个字符再加 ..>服务器端截断,不是 CSS text-overflow——浏览器收到 HTML 时就已经是截断后的短文件名了。你没办法通过前端 CSS 来恢复,因为数据已经丢了。


2. 实验环境

项目 规格
操作系统 Ubuntu 24.04 LTS
CPU 2 vCPU (Intel Xeon E5-2680 v4 @ 2.40 GHz)
内存 1 GB
Nginx 版本 1.30.3 Stable(源码编译安装)
编译路径 /usr/sbin/nginx/etc/nginx/nginx.conf
文件存储路径 /data/releases/android/
域名 filebox.example.com
Nginx 版本说明
本文使用 Nginx 1.30.3,与 第 1 期 编译版本一致。FancyIndex 支持 Nginx 0.8+ 的所有版本,新旧版本配置方式完全一致。

3. 方案对比:sub_filter 注入 CSS vs FancyIndex 编译安装

面对上面那个硬编码限制,有两条路可以走。

3.1 方案一:sub_filter 注入 CSS(零停机,轻量)

Nginx 的 sub_filter 能力(在 第 1 期 编译时通过 --with-http_sub_module 启用)可以修改 HTTP 响应体。利用它,在 autoindex 生成的 HTML 返回给客户端之前,注入一段 CSS 来绕过截断。

原理autoindex 输出的是一个 HTML 表格。我们通过 sub_filter</head> 前注入 CSS,让文件名列的宽度放开,再用 CSS overflow-wrap 处理超长文件名。

location /releases/ {
    alias /data/releases/android/;
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;
    charset utf-8;

    # 注入 CSS 前
    sub_filter '</head>' '
    <style>
        /* 覆盖默认表格样式 */
        pre {
            white-space: pre-wrap;
            word-break: break-all;
        }
        /* 扩大文件名列宽 */
        table tr td:first-child {
            min-width: 600px;
            word-break: break-all;
        }
    </style>
    </head>';
    sub_filter_once on;
    sub_filter_types text/html;
}

优点

  • 不需要重新编译 Nginx,nginx -s reload 即可生效
  • 零停机,不影响已有用户

缺点

  • 文件名仍被截断到 50 字符——数据已经在服务端丢失了。CSS 能做的是让显示区域更大,但 HarmonyOS_Cockpit_4.0.0.126_SP6C00E125... 永远不会显示完整
  • sub_filter 对每个 HTML 页面做正则替换,有一定的 CPU 开销(小,但存在)
  • 不能实现排序、搜索、主题等高级功能

3.2 方案二:FancyIndex 编译安装(完整方案,推荐)

FancyIndex(ngx-fancyindex,GitHub: aperezdc/ngx-fancyindex)是一个第三方 Nginx 模块,完全替代内置的 autoindex 模块。它的核心能力:

  • 不截断文件名——这是本文的核心需求
  • 自定义 CSS 样式(通过 fancyindex_css_href 引入外部样式表)
  • 自定义页头/页脚(PHP/HTML 任意内容)
  • 按文件名/大小/修改时间排序,用户点击表头实时切换
  • 支持隐藏指定文件(正则匹配,如隐藏 .gitkeep*.md
  • 显示人类可读的文件大小(1.2 GB 而不是 1234567890
  • 支持本地时区显示

FancyIndex 也有一个局限:它不单独计算目录大小(不递归统计)。如果需要显示「这个目录有多大」(GitHub 风格的目录大小显示),需要另写脚本。但对我这种代码发包场景——目录都是空的或只放 README——这完全不影响。

以下是一个真实的 FancyIndex 对比 autoindex 的效果对比:

特性 autoindex sub_filter 方案 FancyIndex
长文件名显示 ❌ 截断到 50 字符 ❌ 仍被截断(CSS 只优化排版) 完整显示,不截断
自定义 CSS ✅(注入) ✅(fancyindex_css_href
自定义页头/页脚 ✅(fancyindex_header/footer
用户排序 ✅(点击表头切换)
搜索过滤 ✅(配合自定义主题的 JS)
隐藏文件 ✅(正则匹配 fancyindex_ignore
文件大小人性化 ✅(autoindex_exact_size off ✅(fancyindex_exact_size off
需要重编译 Nginx ✅(首次)
后续维护 升级 Nginx 版本时需重新编译模块

对于生产环境的代码发包服务器,FancyIndex 是唯一正确的选择


4. FancyIndex 源码编译安装

以下是完整的编译安装流程。虽然思路与 第 1 期 类似,但本篇是完全独立、可直接复制的教程——不需要跨文章跳转。

4.1 安装编译依赖

apt update -y
apt install -y build-essential libpcre3-dev zlib1g-dev libssl-dev

4.2 下载 Nginx 与 FancyIndex

cd /usr/src

# Nginx 1.30.3 (截至 2026 年 7 月最新 Stable)
wget https://nginx.org/download/nginx-1.30.3.tar.gz
tar xzf nginx-1.30.3.tar.gz

# FancyIndex 模块(v0.5.2 兼容版本)
wget https://github.com/aperezdc/ngx-fancyindex/archive/refs/tags/v0.5.2.tar.gz \
    -O ngx-fancyindex-0.5.2.tar.gz
tar xzf ngx-fancyindex-0.5.2.tar.gz
FancyIndex 版本兼容性说明

截至 2026 年 7 月,FancyIndex 最新版本为 v0.6.0。如果你的环境下 v0.5.2configure 脚本与新版 Nginx 不一致,可以用 master 分支:

git clone https://github.com/aperezdc/ngx-fancyindex.git
cd nginx-1.30.3
./configure ... --add-module=../ngx-fancyindex

master 分支一般向后兼容 Nginx 新版本。

4.3 Configure + 编译

cd nginx-1.30.3

./configure \
    --prefix=/usr/share/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --pid-path=/run/nginx.pid \
    --lock-path=/run/nginx.lock \
    --http-client-body-temp-path=/var/lib/nginx/client_body \
    --http-proxy-temp-path=/var/lib/nginx/proxy \
    --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \
    --user=www-data \
    --group=www-data \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_sub_module \
    --with-http_addition_module \
    --with-http_gzip_static_module \
    --with-http_stub_status_module \
    --with-stream \
    --with-stream_ssl_module \
    --add-module=../ngx-fancyindex-0.5.2

make -j$(nproc) && make install

关键参数说明(FancyIndex 相关的两个):

参数 用途
--add-module=../ngx-fancyindex-0.5.2 将 FancyIndex 编译到最终的 Nginx 二进制中(静态链接,无需 load_module
--with-http_addition_module FancyIndex 的自定义页头/页脚功能依赖此模块

编译后验证 FancyIndex 是否编译成功:

/usr/sbin/nginx -V 2>&1 | grep -o 'fancyindex'
# 输出:fancyindex

如果有输出,说明 FancyIndex 已经编译进二进制了。

4.4 创建目录结构

mkdir -p /data/releases/android
mkdir -p /etc/nginx/conf.d
chown -R www-data:www-data /data/releases

4.5 生成 Systemd 服务

cat > /etc/systemd/system/nginx.service << 'EOF'
[Unit]
Description=The NGINX HTTP and reverse proxy server
After=syslog.target network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -c /etc/nginx/nginx.conf
ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now nginx

5. FancyIndex 配置示例

5.1 最小可用配置

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

    location / {
        root /data/releases;
        fancyindex on;
        fancyindex_exact_size off;
        fancyindex_localtime on;
    }
}

配置后平滑重载:

nginx -t && systemctl reload nginx

5.2 完整生产配置

以下是我在汽车座舱团队中实际使用的配置——包含自定义页头、CSS 主题、搜索结果过滤(使用 Naereen/Nginx-Fancyindex-Theme 社区的搜索功能):

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

    # 文件下载基础路径
    root /data/releases;

    # --- 访问日志(JSON 格式,详见第 2 期)---
    access_log /var/log/nginx/filebox.access.log json;
    error_log  /var/log/nginx/filebox.error.log;

    # --- 文件下载优化 ---
    sendfile on;
    tcp_nopush on;
    aio threads;
    directio 512;
    output_buffers 2 1m;

    location / {
        # ===== FancyIndex 核心配置 =====
        fancyindex on;
        fancyindex_exact_size off;              # 显示 "1.2 GB" 而非原始字节
        fancyindex_localtime on;                # 显示本地时间(Asia/Shanghai)
        fancyindex_default_sort date_desc;      # 默认按修改时间倒序(最新在前)
        fancyindex_directories_first on;        # 目录排在文件前面
        fancyindex_show_dotfiles off;           # 不显示 .hidden 文件
        fancyindex_case_sensitive off;          # 文件名排序不区分大小写

        # ===== 隐藏不展示的文件(Nginx 内置的正则匹配)=====
        fancyindex_ignore "README.md";
        fancyindex_ignore "\.gitkeep";
        fancyindex_ignore "\.DS_Store";

        # ===== 自定义 CSS(长文件名完整显示)=====
        fancyindex_css_href /fancyindex/style.css;

        # ===== 自定义页头 =====
        fancyindex_header "/fancyindex/header.html";

        # ===== 自定义页脚 =====
        fancyindex_footer "/fancyindex/footer.html";
    }

    # ===== FancyIndex 静态资源 =====
    location /fancyindex/ {
        alias /etc/nginx/fancyindex/;
    }
}

5.3 自定义 CSS(长文件名核心)

/* /etc/nginx/fancyindex/style.css */

/* 让文件名列足够宽,不截断 */
table {
    width: 100%;
    table-layout: auto;
}

/* 文件名列 */
td:first-of-type, th:first-of-type {
    white-space: nowrap;
}

/* 长文件名处理 */
td:first-of-type a {
    word-break: break-all;
    overflow-wrap: break-word;
}

/* 整体字距 */
body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    max-width: 1200px;
    margin: 0 auto;
    padding: 20px;
    color: #333;
    background: #f8f9fa;
}

/* 表格行悬停效果 */
tr:hover {
    background: #e9ecef;
}

/* 链接样式 */
a {
    color: #0366d6;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

5.4 自定义页头(团队信息)

<!-- /etc/nginx/fancyindex/header.html -->
<div style="background:#1a1a2e;color:#eee;padding:15px 20px;margin-bottom:20px;border-radius:6px;">
    <h1 style="margin:0;font-size:24px;">📦 Android 代码发包平台</h1>
    <p style="margin:5px 0 0;font-size:14px;color:#aaa;">
        最后更新:<span id="update-time"></span> &nbsp;|&nbsp;
        管理员:devops@example.com
    </p>
</div>
<script>
// 自动更新最后修改时间
document.getElementById('update-time').textContent = new Date().toLocaleString('zh-CN');
</script>

页脚可以放公司合规声明。部署静态文件:

mkdir -p /etc/nginx/fancyindex
# 将以上 3 个文件写入 /etc/nginx/fancyindex/

6. FancyIndex 指令速查表

以下是我从 aperezdc/ngx-fancyindex README 中提取的完整指令表——按使用频率排序:

指令 语法 默认值 说明
fancyindex on | off off 启用/禁用 FancyIndex
fancyindex_exact_size on | off on on=精确字节,off=人类可读(KB/MB/GB)
fancyindex_localtime on | off off off=UTC 时间,on=本地时区
fancyindex_default_sort name | size | date | name_desc | size_desc | date_desc name 默认排序字段和方向(_desc 后缀 = 降序)
fancyindex_directories_first on | off on 目录排在文件前
fancyindex_css_href URI "" 引入外部 CSS
fancyindex_header path [local] "" 页头模板(需要 ngx_http_addition_module
fancyindex_footer path [local] "" 页脚模板(同上)
fancyindex_ignore string1 [string2 …] 隐藏匹配的文件名(支持 PCRE 正则)
fancyindex_show_dotfiles on | off off 是否显示 . 开头的隐藏文件
fancyindex_hide_symlinks on | off off 是否在列表中隐藏符号链接
fancyindex_hide_parent_dir on | off off 是否隐藏父目录链接 ../
fancyindex_case_sensitive on | off on 文件名排序是否区分大小写
fancyindex_show_path on | off on 是否在页头显示当前路径(配合自定义 header 使用)
fancyindex_time_format string %Y-%b-%d %H:%M 时间格式(strftime 格式,不依赖 locale)

7. 最佳实践:Nginx 文件服务器的生产调优

7.1 大文件下载的 TCP 栈优化

Android OTA 包通常是 1-3 GB 的单个文件。默认的 Nginx 配置发送大文件时效率偏低——以下配置来自 第 1 期 的 TCP 调优思路:

# 在 http 或 server 块中
sendfile on;                  # 内核级零拷贝传输
sendfile_max_chunk 2m;        # 每次 sendfile 调用的最大数据量(防止单个 worker 被大文件占满)
tcp_nopush on;                # 在 sendfile 时,合并 TCP 数据包减少网络往返
aio threads;                  # 启用线程池异步 I/O
directio 512;                 # 超过 512 字节的文件使用 direct I/O(绕过 OS 页缓存)
output_buffers 2 1m;         # 输出缓冲区:2  1 MB 的缓冲区

directio 512 需要特别说明:对于大于 512 字节的文件(即几乎所有实际文件),Nginx 使用 O_DIRECT 打开文件——数据直接从磁盘到 socket,不经过操作系统的页缓存(page cache)。这意味着:

  • 优点:不会因为一个 2 GB 的 OTA 文件读取而把整个 OS 的页缓存冲掉(缓存污染)
  • 缺点:每次请求都是真实磁盘 I/O,没有缓存命中

如果你的服务器内存足够大(16 GB+),可以不使用 directio——让 OS 的页缓存用 LRU 算法自动管理。如果内存紧张(像我这种 1 GB 的 VPS),directio 512 是必需的。

7.2 防盗链与速率限制

代码发包服务器通常只在公司内网使用,但如果你需要暴露到公网:

# 限制并发连接数(每个 IP 最多 2 个连接同时下载)
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn perip 2;

# 限制下载速率(每个连接最多 50 MB/s)
limit_rate 50m;

# 禁止外部站点盗用下载链接
valid_referers none blocked *.example.com;
if ($invalid_referer) {
    return 403;
}

7.3 日志审计

使用 第 2 期 的 JSON 日志格式记录每一次文件下载。把各访问者的下载量汇总到 Grafana,一周 5 TB → 突然涨到 20 TB → 查日志确认是否有外部 IP 在扫你的下载目录。


8. FAQ

Q1: sub_filter 注入 CSS 方案和 FancyIndex 哪个更适合我?

sub_filter 如果:你的文件名不超过 40 个字符,只是格式不好看。不需要排序和搜索。

用 FancyIndex 如果:文件名超过 45 个字符(我的场景)、需要用户排序和搜索、或者你想自定义页面的视觉效果。

sub_filter 只是遮丑——数据已经丢了。如果你的文件名真的很长,只有一个选择:FancyIndex。

Q2: FancyIndex 编译失败——ngx_http_addition_module 相关错误?

FancyIndex 的 fancyindex_headerfancyindex_footer 指令依赖 ngx_http_addition_module。在 configure 时加上 --with-http_addition_module 即可。

如果你不需要自定义页头/页脚,也可以不加这个模块——FancyIndex 的核心功能(文件名完整显示、CSS 主题、排序)不依赖它。

Q3: 升级 Nginx 版本时 FancyIndex 怎么处理?

FancyIndex 不需要单独升级——它是静态编译进 Nginx 二进制的。升级 Nginx 时只需在 configure 中保留 --add-module 参数重新编译即可。

# 升级流程(零停机的平滑升级):
cd /usr/src/nginx-1.30.2
./configure ... --add-module=../ngx-fancyindex-0.5.2
make -j$(nproc)
cp objs/nginx /usr/sbin/nginx
kill -s USR2 $(cat /run/nginx.pid)
kill -s QUIT $(cat /run/nginx.pid.oldbin)

这是 第 1 期 第 5.1 节的平滑升级流程。

Q4: FancyIndex 可以显示文件预览或缩略图吗?

FancyIndex 本身不支持图片预览或缩略图。但这不意味着不能实现——你可以通过自定义 fancyindex_header 注入前端 JS 库(如 Lightbox2)来实现图片预览。


总结

这篇文章解决了两个层次的同一个问题:

  1. 短平快:用 sub_filter 注入 CSS,5 分钟搞定,文件名稍长时能用(但数据仍被截断)
  2. 根本解决:编译 FancyIndex,一步到位完整显示文件名,还附送排序、搜索、自定义主题

选择取决于你的文件名到底有多长:Package v1.0.0.tar.gz 用方案一够,IMX8QM_MCU_AGL_2026_Q3_Release_Candidate_3_UserDebug_eng_build_20260715_signed.zip 只有方案二能救你。


推荐阅读

三篇文章形成了 Nginx 从安装到监控到特定场景的完整链路。如果你有其他的 Nginx 实战场景需要分析,欢迎评论或邮件联系。