从 Apache 与 Traefik 迁移到 NGINX

面向既有 Apache 或 Traefik 环境的迁移指南,覆盖 rewrite 与 RewriteRule 的等价转换、虚拟主机与目录配置映射、Traefik 中间件到 location 的重写、header 与鉴权迁移,以及灰度切流与回滚的完整流程。

迁移网关不是把配置文件翻译一遍就完事,真正的工作量在于行为差异的补平:Apache 的 .htaccess 逐目录继承、Traefik 的标签式动态配置,与 NGINX 的静态层级配置在语义上有大量不对齐。本文按「等价转换 → 结构映射 → 灰度切流」三步展开,每步都给出可直接套用的转换模板与验证方法。

1. 迁移前的能力盘点

动手前先把现有配置按功能分类,否则很容易漏掉隐藏在 .htaccess 里的重写规则。

Apache 侧需要盘点:

  • 虚拟主机定义(<VirtualHost>)与 ServerAlias
  • 目录级重写(.htaccess 里的 RewriteRule,这是最容易漏的部分)
  • mod_rewrite 的条件链(RewriteCond)
  • 认证配置(AuthType、Require)
  • mod_proxy 与 ProxyPass
  • 响应头操作(Header set、mod_headers)

Traefik 侧需要盘点:

  • entryPoints 与路由规则(Host()、PathPrefix())
  • middlewares(stripPrefix、redirectRegex、headers、rateLimit、basicAuth)
  • 服务发现来源(Docker labels、Kubernetes CRD、Consul)
  • TLS 与 ACME 配置
# Apache:导出全部虚拟主机与重写规则
apachectl -S 2>&1
grep -rn "RewriteRule\|ProxyPass\|Redirect" /etc/apache2/ /etc/httpd/

# Traefik:导出当前生效的动态配置
curl -s http://traefik:8080/api/rawdata | jq '.routers, .middlewares'

把这份清单做成迁移对照表,逐条打勾,是避免「迁完发现少了条规则」的唯一可靠方法。

2. rewrite 规则的等价转换

Apache 的 RewriteRule 与 NGINX 的 rewrite 语义差异最大,是迁移的主要风险点。

2.1 基础模式对应

ApacheNGINX说明
RewriteRule ^/old/(.*)$ /new/$1 [R=301,L]rewrite ^/old/(.*)$ /new/$1 permanent;外部重定向
RewriteRule ^/a/(.*)$ /b/$1 [L]rewrite ^/a/(.*)$ /b/$1 last;内部重写并重新匹配 location
RewriteRule ^/x/(.*)$ /y/$1 [PT,L]rewrite ^/x/(.*)$ /y/$1 break;重写后交代理处理
RewriteCond %{HTTP_HOST} ^a\.com$if ($host = "a.com") 或 map条件判断
RedirectMatch 301 ^/p/(.*)$ /q/$1rewrite ^/p/(.*)$ /q/$1 permanent;等价

关键差异:

  1. Apache 的 .htaccess 中 pattern 不带前导斜杠(如 ^old/(.*)$),而虚拟主机配置里带斜杠。NGINX 的 rewrite 在 server 级匹配带斜杠的规范化 URI,在 location 内匹配去掉 location 前缀后的相对 URI。这一条不一致会导致大量规则静默失效。
  2. [L] 不等于 NGINX 的 last。Apache 的 [L] 表示「本轮重写到此为止」,但请求还会继续经过后续处理阶段;NGINX 的 last 会重新走一遍 location 匹配,可能陷入循环。若不需要重新匹配,用 break。
  3. [R] 默认 302,NGINX 的 rewrite 不带 flag 时是内部重写,要显式写 redirect 或 permanent。

2.2 RewriteCond 链的转换

Apache 的 RewriteCond 是「与下一条 RewriteRule 配对」的,多条条件默认 AND。NGINX 没有等价的多条件语法,需要用 map 或 if 组合。

# Apache:只有特定 UA 且不是内部请求时才重定向
RewriteCond %{HTTP_USER_AGENT} ".*Mobile.*" [NC]
RewriteCond %{REQUEST_URI} !^/mobile
RewriteRule ^/(.*)$ /mobile/$1 [R=302,L]
# NGINX:用 map 预计算条件,避免 if 嵌套
map $http_user_agent $is_mobile {
    default        0;
    "~*mobile"     1;
}

server {
    if ($is_mobile) {
        rewrite ^/(?!mobile)(.*)$ /mobile/$1 redirect;
    }
}

if 在 location 内是「邪恶的」(nginx 官方 wiki 的说法),因为 if 块内指令的执行顺序与直觉不符,尤其是 try_files、proxy_pass 在 if 内的行为有陷阱。能用 map 表达的条件一律用 map,这是 NGINX 配置的核心纪律。

2.3 常见重写场景模板

WordPress 风格的 front controller:

location / {
    try_files $uri $uri/ /index.php?$args;
}

对应 Apache 的:

RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

try_files 一次完成「文件存在则返回,否则交给 index.php」,比四条 Apache 规则更简洁,且没有 -f/-d 的多次 stat 开销(try_files 内部优化了查找)。

去除末尾斜杠:

# 只在非目录时去斜杠
if (!-d $request_filename) {
    rewrite ^/(.*)/$ /$1 permanent;
}

大小写规范化:NGINX 的 rewrite 正则默认大小写敏感,需要 ~* 前缀(用于 location)或正则里的 (?i)。

rewrite 与 location 匹配顺序的完整规则(前缀匹配、正则匹配、^~、= 的优先级)参见 Nginx rewrite 与 location 匹配 ,迁移前务必通读一遍。

3. 虚拟主机与目录配置映射

3.1 VirtualHost 到 server 块

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example

    SSLEngine on
    SSLCertificateFile /etc/ssl/example.crt
    SSLCertificateKeyFile /etc/ssl/example.key

    <Directory /var/www/example>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog /var/log/apache2/example-error.log
</VirtualHost>
server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

    root /var/www/example;
    index index.html index.php;

    ssl_certificate     /etc/ssl/example.crt;
    ssl_certificate_key /etc/ssl/example.key;

    autoindex off;
    access_log /var/log/nginx/example-access.log;
    error_log  /var/log/nginx/example-error.log;

    location / {
        try_files $uri $uri/ =404;
    }
}

映射要点:

  • ServerAlias 直接并入 server_name,空格分隔。
  • Options -Indexes → autoindex off;(NGINX 默认就是 off,可省略,但显式写出便于审计)。
  • Options +FollowSymLinks → NGINX 默认允许,无需配置。若要禁用符号链接跟随,用 disable_symlinks on;。
  • AllowOverride All 无对应物:NGINX 不支持目录级配置继承,所有 .htaccess 内容必须合并到 server 或 location 中。这是迁移工作量的主要来源。
  • Require all granted → 默认允许;Require ip 10.0.0.0/8 → allow 10.0.0.0/8; deny all;。

3.2 目录级认证的迁移

<Directory /var/www/private>
    AuthType Basic
    AuthName "Restricted"
    AuthUserFile /etc/apache2/.htpasswd
    Require valid-user
</Directory>
location /private/ {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

.htpasswd 文件格式兼容,可以直接复用。但要注意 Apache 的 AuthType Digest 在 NGINX 中不支持,必须换成 Basic 或改用外部鉴权服务。

3.3 响应头与 CORS

Header always set X-Frame-Options "SAMEORIGIN"
Header set Cache-Control "no-store"
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Cache-Control "no-store";

add_header 有继承陷阱:一旦在某个 location 里出现 add_header,父级的 add_header 就全部不再继承。这与 Apache 的 Header 逐层累加完全不同,是迁移后「某些安全头突然消失」的最常见原因。解决方案是用 include 片段在每个需要的层级显式引入,或改用较新的 add_header_inherit(若版本支持)。

4. Traefik 中间件到 location 的映射

Traefik 的中间件是「挂在路由上的可组合单元」,NGINX 没有同名概念,需要逐类翻译。

Traefik 中间件NGINX 等价实现
stripPrefixlocation /api/ { proxy_pass http://upstream/; }(末尾斜杠自动去前缀)
addPrefixrewrite ^/(.*)$ /prefix/$1 break;
redirectRegexrewrite + permanent/redirect
replacePathRegexrewrite ... break;
headersadd_header / proxy_set_header
basicAuthauth_basic
rateLimitlimit_req_zone + limit_req
circuitBreakermax_fails/fail_timeout + proxy_next_upstream
retryproxy_next_upstream
compressgzip on; 或 brotli 模块
ipWhiteListallow / deny

4.1 stripPrefix 的斜杠陷阱

Traefik 的 stripPrefix: /api 把 /api/users 变成 /users 转发。NGINX 里靠 proxy_pass 末尾斜杠实现:

location /api/ {
    proxy_pass http://backend/;   # 末尾斜杠 = 剥离 /api/
}

末尾斜杠是行为开关:proxy_pass http://backend/(带斜杠)会剥离 location 前缀;proxy_pass http://backend(不带斜杠)会把完整 URI 透传。写错一个斜杠,上游就会收到 /api/users 而不是 /users,导致 404。这是 NGINX 迁移中最高频的错误,没有之一。

4.2 多中间件链的组合

Traefik 的中间件链是顺序执行的,NGINX 中同一功能由不同阶段的指令完成,顺序由 NGINX 的阶段模型固定:

rewrite 阶段 → access 阶段 → content 阶段 → header filter → body filter → log 阶段

也就是说,rewrite 永远在 proxy_pass 之前,add_header 永远在响应生成之后。不要试图用指令书写顺序控制执行顺序,NGINX 的指令顺序与执行顺序无关。理解阶段模型是写出正确配置的前提。

# 一个典型的「鉴权 + 限流 + 转发 + 加头」组合
location /api/ {
    auth_request /internal/auth;          # access 阶段
    limit_req zone=api burst=20 nodelay;  # preaccess 阶段

    proxy_pass http://backend/;           # content 阶段
    proxy_set_header X-Request-Id $request_id;
    add_header X-Served-By $hostname always;
}

location = /internal/auth {
    internal;
    proxy_pass http://auth-service/verify;
    proxy_pass_request_body off;
    proxy_set_header Content-Length "";
}

若已经在用 Envoy 或计划保留部分动态路由能力,可以参考 Envoy 高级代理配置 做能力对比,避免迁移后反而丢掉必要特性。

5. 配置校验与灰度切流

5.1 迁移前先做配置校验

NGINX 的 nginx -t 只能查语法,查不出「上游地址写错」「证书路径不存在」这类语义问题。把校验放进 CI 是迁移期的必要投入。

# 语法检查
nginx -t -c /etc/nginx/nginx.conf

# 容器内校验(不启动服务)
docker run --rm -v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro \
  nginx:1.25 nginx -t

更进一步的做法是用 gixy 静态分析(检查 if 陷阱、alias 目录穿越等),以及在预发环境用真实流量做回归。完整的 CI 校验流程参见 Nginx 配置测试与 CI 。

5.2 双跑与流量镜像

迁移最稳的方式是让新老网关同时在线,用镜像流量验证新配置。NGINX 的 mirror 模块可以把生产请求复制一份到新网关,观察其响应是否正确,而不影响真实用户。

server {
    listen 443 ssl;
    server_name example.com;

    location / {
        mirror /mirror-to-nginx;      # 复制到新网关
        mirror_request_body on;
        proxy_pass http://apache_backend;
    }

    location = /mirror-to-nginx {
        internal;
        proxy_pass http://nginx_new$request_uri;
        proxy_set_header Host $host;
    }
}

对比两边日志的 status 与响应长度,差异收敛后再切流。

5.3 灰度切流与回滚

切流用 DNS 权重或上游负载均衡权重实现。若前端是 NGINX,用 weight 做灰度:

upstream gateway {
    server 10.0.0.1:443 weight=90;   # 老网关 Apache
    server 10.0.0.2:443 weight=10;   # 新网关 NGINX
}

按 1% → 10% → 50% → 100% 逐步放量,每一档观察错误率、P99 延迟、5xx 比例。回滚只需把权重调回,秒级生效。

回滚预案必须提前演练:确认老网关的配置未删除、证书未过期、DNS TTL 已调低。很多团队的「回滚」在真正需要时才发现老环境已被清理。

5.4 迁移后必须复核的清单

  • 所有 301/302 重定向的目标 URL 与状态码一致(用 curl -I 批量比对)
  • 响应头(安全头、CORS、缓存控制)逐条比对
  • 大文件上传与下载的完整性(Content-Length 一致)
  • WebSocket 与 SSE 长连接可用
  • 客户端真实 IP 正确透传到后端
  • 日志格式变化对现有分析管道的影响

反向代理与负载均衡的通用配置要点(keepalive、超时、健康检查)在 Nginx 反向代理与负载均衡 中有系统整理,迁移时可直接对照。

6. 总结

迁移的核心不是语法翻译,而是行为对齐。Apache 的目录级继承与 Traefik 的动态中间件在 NGINX 里都没有直接对应物,必须用 map、location 与阶段模型重新表达。三个最容易翻车的地方是:rewrite 的 URI 前缀差异、proxy_pass 末尾斜杠、add_header 的继承中断。

工程上,把迁移拆成「配置转换 → 语法校验 → 镜像双跑 → 灰度切流 → 清单复核」五步,每一步都有明确的验收标准,比一次性切换的风险低一个数量级。留好回滚路径,比追求切换速度重要得多。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「nginx」更多文章

  1. NGINX 容器镜像精简与加固
  2. 多租户虚拟主机与配置生成
  3. njs 模块与 JavaScript 扩展