Bugzilla Windows部署报错720002:CGI子进程创建失败排查与修复
1. 问题现象与背景拆解1.1 这个报错到底在说什么装完 Bugzilla 兴冲冲打开浏览器准备登录结果页面直接甩出一行couldnt create child process: 720002: index.cgi后面往往还跟着一句The specified CGI application misbehaved by not returning a complete set of HTTP headers。这个场景我遇到过不止一次第一次碰到的时候也是一头雾水因为报错信息本身非常“吝啬”只告诉你子进程创建失败但没告诉你为什么失败。先把这句话拆开看。couldnt create child process是 Web 服务器在 Windows 环境下通常是 IIS配合某个 CGI 包装器在尝试启动index.cgi这个进程时失败了。720002是一个错误码它并不是 Bugzilla 自己抛出来的而是底层进程创建机制返回的。index.cgi是 Bugzilla 的入口脚本所有请求都要经过它。换句话说服务器连 Bugzilla 的门都没敲开进程根本没起来。这里有个关键点很多人会忽略Bugzilla 在 Windows 上跑本质上依赖的是一个 Perl 解释器去执行.cgi脚本。Web 服务器本身不认识 Perl它只是按照配置去调用perl.exe或者某个包装程序来执行脚本。所以这个报错 90% 以上的情况问题不在 Bugzilla 的代码而在“谁来执行这个脚本、以什么身份执行、执行时能不能找到依赖”这条链路上。1.2 为什么 Windows 环境下特别容易踩这个坑Bugzilla 官方其实更推荐在 Linux 环境下部署因为它的依赖链Perl 模块、数据库驱动、邮件发送、图表生成在类 Unix 系统上装起来顺滑得多。但现实是很多团队的内网环境就是 Windows Server没得选只能在 Windows 上硬啃。Windows 下部署 Bugzilla 的典型组合是 IIS ActivePerl或 Strawberry Perl MySQL中间还要装一堆 Perl 模块。这个组合里最容易出问题的就是 CGI 执行环节。IIS 处理 CGI 的方式和 Apache 完全不同它需要通过“处理程序映射”把.cgi后缀关联到具体的可执行程序上还要配置好工作目录、环境变量、执行权限。任何一环没对上就会出现couldnt create child process。而720002这个错误码根据我的经验绝大多数时候指向的是执行身份和路径权限的问题而不是脚本语法错误。注意不要一看到报错就去改 Bugzilla 的 Perl 代码方向完全错了。这个报错发生在脚本被执行之前代码根本没机会运行。1.3 影响范围与排查思路总览这个问题的影响范围其实很明确只要你的部署方式是 Windows IIS CGI 模式跑 Bugzilla初次访问就必然经过这个环节。它不影响已经跑起来的系统只卡在“第一次能不能打开”这一步。所以排查的核心就是让index.cgi能被成功创建为一个进程。我一般按下面这个顺序排查从外到内逐层缩小范围排查层级检查对象典型问题第一层IIS 处理程序映射.cgi没关联到 Perl 解释器第二层执行身份与权限应用程序池身份无权访问 Perl 和脚本目录第三层Perl 解释器路径路径含空格未加引号、路径写错第四层脚本首行与依赖shebang 行错误、Perl 模块缺失第五层环境变量PATH 中找不到 perl.exe 或依赖 DLL这个顺序不是随便排的。couldnt create child process的字面意思就是“创建子进程失败”所以优先怀疑进程创建相关的配置而不是脚本内容。下面我逐层展开把每一层的原理、操作和坑点都讲透。2. 核心原因深度解析2.1 错误码 720002 的真实含义很多人搜这个错误码搜不到有用信息因为它不是 Windows 系统错误码的标准格式。实际上这个数字是 CGI 包装层不同部署方式下可能是 IIS 的 CGI 模块也可能是某个第三方 CGI 执行器自己定义的错误标识。它的核心含义是尝试启动目标进程时操作系统层面拒绝了这次创建请求。操作系统拒绝创建进程常见原因就那么几个可执行文件路径不存在、当前身份没有执行权限、工作目录不可访问、命令行参数格式错误。这四类原因里路径和权限占了绝大多数。我个人的统计是权限问题大概占六成路径问题占三成剩下的一成是环境变量或依赖缺失。理解这一点很重要因为它决定了你的排查方向。如果你去查 Perl 模块装没装全那是在查“脚本能不能跑”但现在的问题是“进程能不能起来”这是两个阶段。进程都起不来谈脚本内容是南辕北辙。2.2 IIS 处理程序映射的配置逻辑IIS 要执行.cgi文件必须有一条处理程序映射告诉它“遇到.cgi结尾的请求交给谁处理”。在 IIS 管理器里这个配置在“处理程序映射”功能里。你需要添加一条映射请求路径填*.cgi可执行文件填 Perl 解释器的完整路径后面跟上%s %s这样的参数占位符。这里的参数占位符是第一个大坑。%s代表脚本的物理路径IIS 会把请求对应的脚本路径替换进去。正确的写法通常是C:\Perl\bin\perl.exe %s %s注意 Perl 解释器路径如果含空格必须用引号包起来。脚本路径的%s也要用引号包起来因为 Bugzilla 的安装路径里很可能有空格比如C:\Program Files\Bugzilla。第二个%s是传给脚本的命令行参数一般不加引号。我见过有人写成C:\Perl\bin\perl.exe %s %s路径一含空格进程创建直接失败报的就是这个错。还有一点处理程序映射里有个“请求限制”选项卡要确保“仅当请求映射到此处理程序时才调用”这个选项没有把.cgi排除掉。另外“谓词”要允许 GET、POST、HEAD默认一般没问题但如果被人改过就要检查。2.3 应用程序池身份与文件系统权限这是最容易出问题、也最难排查的一层。IIS 里每个站点或应用都跑在一个应用程序池里应用程序池有一个身份Identity。默认可能是ApplicationPoolIdentity也可能是NetworkService、LocalSystem或者自定义账户。这个身份决定了进程以什么权限去访问文件系统。index.cgi要被成功执行这个身份必须满足几个条件能读取 Perl 解释器所在目录和perl.exe、能读取 Bugzilla 安装目录下的所有脚本和配置文件、能写入 Bugzilla 的data目录和template缓存目录、能访问数据库如果走本地 socket 或命名管道。任何一条不满足进程创建或后续执行就会失败。我踩过最典型的一个坑是Perl 装在C:\PerlBugzilla 装在D:\bugzilla应用程序池身份是默认的ApplicationPoolIdentity。这个虚拟账户对C:\Perl有读取权限因为安装时给了 Everyone 读取但对D:\bugzilla没有显式授权结果进程创建时能启动 perl.exe但 perl.exe 去读脚本时被拒绝最终表现还是这个报错。解决办法是给应用程序池身份显式授予 Bugzilla 目录的读取和执行权限以及data目录的修改权限。提示给权限的时候不要图省事直接给 Everyone 完全控制这是安全隐患。正确做法是给具体的应用程序池身份格式是IIS AppPool\你的池名称授予最小必要权限。2.4 Perl 解释器与 shebang 行的关系.cgi脚本的第一行通常是 shebang比如#!C:\Perl\bin\perl.exe或者#!/usr/bin/perl。在 Linux 下这行决定用哪个解释器执行脚本。但在 Windows IIS 的 CGI 模式下shebang 行其实不一定被使用因为 IIS 是通过处理程序映射直接指定解释器的。不过某些 CGI 包装器会读取 shebang 行如果这行指向的路径不存在也会导致进程创建失败。所以稳妥的做法是确保 shebang 行指向的 Perl 路径和实际安装路径一致。如果你用的是 Strawberry Perl路径可能是C:\Strawberry\perl\bin\perl.exe如果是 ActivePerl可能是C:\Perl64\bin\perl.exe。装完之后用命令行跑一下perl -v确认路径然后把这个路径同时写进处理程序映射和脚本的 shebang 行。这里还有个细节Bugzilla 的index.cgi默认 shebang 可能是#!/usr/bin/perl -w这个路径在 Windows 下根本不存在。虽然 IIS 模式下不一定读它但为了保险改成实际的 Windows 路径更稳妥。改的时候注意保留-w参数开启警告这对后续排查有帮助。3. 完整排查与修复实操3.1 第一步确认 Perl 能独立运行在动 IIS 配置之前先打开命令行直接跑一下 Perl确认解释器本身没问题C:\Perl\bin\perl.exe -v如果这条命令能正常输出版本信息说明 Perl 装好了。接着直接跑 Bugzilla 的入口脚本看它能不能在命令行下执行cd D:\bugzilla C:\Perl\bin\perl.exe index.cgi这一步非常关键。如果命令行下能跑出 HTML 输出哪怕报数据库连接错误说明脚本本身和 Perl 模块没问题问题纯粹在 IIS 的进程创建环节。如果命令行下就报错那要先解决 Perl 模块依赖问题再回头看 IIS。我一般会在这个阶段把常见的依赖模块检查一遍用perl -M模块名 -e 1的方式逐个验证。Bugzilla 依赖的模块比较多核心的有DBI、DBD::mysql、CGI、Template、Email::Send等。缺哪个装哪个用ppm install 模块名ActivePerl或cpan 模块名Strawberry Perl安装。3.2 第二步核对 IIS 处理程序映射打开 IIS 管理器选中 Bugzilla 所在的站点或应用双击“处理程序映射”。找到.cgi那条映射检查可执行文件路径。正确的格式应该是C:\Perl\bin\perl.exe %s %s如果原来写的是C:\Perl\bin\perl.exe %s %s没有引号改成带引号的版本。改完之后在映射上右键选择“编辑”确认“请求限制”里的谓词包含 GET、POST、HEAD并且没有勾选“仅当请求映射到此处理程序时才调用”导致被排除。还有一个容易被忽略的地方IIS 的“ISAPI 和 CGI 限制”功能。在某些 IIS 版本里CGI 执行是被默认禁用的需要在服务器级别的“ISAPI 和 CGI 限制”里把 CGI 设为“允许”。如果这里是禁止状态任何.cgi请求都会被拒绝表现也可能类似。检查路径是IIS 管理器根节点 → ISAPI 和 CGI 限制 → 找到 CGI → 确认状态是“允许”。3.3 第三步配置应用程序池身份与权限先确认应用程序池用的什么身份。在 IIS 管理器里点“应用程序池”找到 Bugzilla 用的池右键“高级设置”看“标识”这一项。如果是ApplicationPoolIdentity那么对应的实际账户是IIS AppPool\池名称。然后给这个身份授权。打开 Bugzilla 安装目录的属性 → 安全 → 编辑 → 添加输入IIS AppPool\池名称确定后授予以下权限对 Bugzilla 根目录读取和执行、列出文件夹内容、读取对data目录修改、写入对template目录如果存在编译缓存修改、写入对 Perl 安装目录读取和执行、读取授权完成后最好重启一下应用程序池让权限变更生效。重启方法是右键应用程序池 → 回收或者在命令行用iisreset重启整个 IIS生产环境慎用会中断所有站点。注意如果你用的是NetworkService身份对应的账户名就是NETWORK SERVICE授权方式一样。但我不推荐用LocalSystem权限太大有安全风险。3.4 第四步检查环境变量与工作目录IIS 启动 CGI 进程时继承的是应用程序池的环境变量而不是你当前登录用户的环境变量。如果 Perl 依赖某些环境变量比如PERL5LIB指向额外的模块目录必须在应用程序池级别配置或者在脚本里显式设置。配置方法是在 IIS 里选中站点打开“配置编辑器”定位到system.webServer/cgi或者用应用程序池的环境变量设置不同 IIS 版本位置不同。更简单的办法是在 Bugzilla 的index.cgi开头显式加上use lib D:/bugzilla/lib; use lib D:/perl/site/lib;这样就不依赖环境变量了。工作目录方面IIS 默认会把脚本所在目录作为工作目录一般没问题但如果你的配置里指定了别的工作目录要确保那个目录存在且可访问。3.5 第五步验证修复结果改完上面这些重启应用程序池然后重新访问 Bugzilla 的登录页面。如果还是报同样的错别急打开 IIS 的详细错误日志。日志位置一般在C:\inetpub\logs\LogFiles下找到对应站点的日志看请求返回的状态码和子状态码。同时可以打开 Windows 事件查看器看“Windows 日志 → 应用程序”里有没有来自 IIS 或 CGI 的错误记录那里往往有更详细的失败原因。如果错误变了比如变成了数据库连接错误或者 Perl 模块缺失错误那说明进程创建这一关已经过了问题推进到了下一阶段这是好事。按新的错误继续排查即可。4. 常见问题速查与避坑经验4.1 高频问题速查表现象可能原因快速验证方法解决方向报错 720002 且命令行能跑IIS 处理程序映射路径错误检查映射里的 perl 路径和引号修正映射格式报错 720002 且命令行也报错Perl 模块缺失命令行跑 index.cgi 看报错安装缺失模块改了权限还是报错应用程序池未重启回收应用程序池后再试重启池或 iisreset报错变成 500 但无详细信息IIS 详细错误未开启开启详细错误和日志看日志定位能打开但样式全无静态文件权限或路径问题检查 images、css 目录权限授予读取权限登录后报数据库错误数据库连接配置问题检查 localconfig 配置修正数据库参数4.2 几个我踩过的坑第一个坑是路径里的空格。Bugzilla 默认安装路径经常带空格比如C:\Program Files\Bugzilla。处理程序映射里如果没给%s加引号IIS 会把路径从空格处截断导致进程创建失败。这个坑很隐蔽因为报错信息完全不提路径问题。解决办法就是老老实实加引号。第二个坑是权限继承。我给 Bugzilla 目录授了权但子目录data的权限没有正确继承因为之前有人手动断开了继承。结果进程能创建但脚本运行到写缓存时失败表现还是各种奇怪错误。后来我在每个关键子目录上单独确认了权限问题才解决。所以授权之后一定要到子目录里确认权限是否生效。第三个坑是 Perl 版本和模块版本不匹配。我装的是 64 位 Perl但某个关键模块只有 32 位版本装上去之后命令行能跑IIS 下就崩。后来统一换成 64 位模块才稳定。所以装模块的时候要注意架构一致别混用。第四个坑是杀毒软件拦截。某些安全软件会把 CGI 进程创建行为当成可疑操作拦截导致进程创建失败。这个最难查因为日志里看不出明显原因。如果前面都排查完了还不行可以临时关闭安全软件测试一下确认是它的问题后再加白名单。4.3 预防性配置建议与其等报错了再排查不如一开始就配好。我的建议是安装 Perl 和 Bugzilla 时路径尽量用短路径、不含空格和中文比如C:\Perl和D:\bugzilla。这样能避开一大类路径解析问题。应用程序池单独建一个用独立的身份权限按最小必要原则授予不要图省事用高权限账户。处理程序映射配好之后先用命令行验证脚本能跑再配 IIS这样能把问题隔离在更小的范围内。装完所有 Perl 模块后跑一遍 Bugzilla 自带的checksetup.pl它会检查依赖完整性缺什么会明确告诉你。这个脚本是官方提供的非常有用别跳过。最后把 IIS 的详细错误日志打开日志保留时间设长一点。出问题的时候日志比任何猜测都可靠。我现在的习惯是任何 Web 应用部署第一步就是把日志配好后面省下的排查时间远超这点配置成本。5. 从根上理解 CGI 进程创建机制5.1 CGI 模式的工作原理CGI 的全称是通用网关接口它的工作模式很朴素Web 服务器收到请求发现是.cgi脚本就启动一个新进程来执行这个脚本脚本把结果输出到标准输出服务器再把这个输出作为 HTTP 响应返回给浏览器。每次请求都启动一个新进程请求结束进程就销毁。这种模式的好处是隔离性好一个脚本崩了不影响服务器坏处是性能差每次请求都有进程创建开销。Bugzilla 在 Windows 下用 CGI 模式就是这种机制。理解了这一点就能明白为什么couldnt create child process这个错误如此关键——它意味着整个请求处理链条在第一步就断了。进程创建在操作系统层面是一个相对昂贵的操作需要分配内存、加载可执行文件、初始化运行环境。Windows 对进程创建有严格的安全检查当前身份必须对可执行文件有执行权限对工作目录有访问权限对依赖的 DLL 有读取权限。任何一项不满足创建就会失败。这就是为什么权限问题在这个错误里占比这么高。5.2 为什么权限问题如此普遍Windows 的权限模型比 Linux 复杂得多。Linux 下文件权限就是读、写、执行三个位加上所有者和组概念清晰。Windows 下除了基本的读、写、执行还有“列出文件夹内容”“遍历文件夹”“取得所有权”等一堆细分权限还有继承、显式授权、拒绝优先等规则。配置不当的概率自然高。再加上 IIS 的应用程序池身份是个虚拟账户很多人不熟悉它的授权方式。默认的ApplicationPoolIdentity在文件系统里不是直接可见的用户授权时要手动输入IIS AppPool\池名称这个格式很多人不知道就随便给了个 Everyone 或者 Users结果要么权限不够要么权限过大留下隐患。我的经验是部署任何 IIS 应用之前先把应用程序池身份确定下来然后一次性把该授的权限都授好别等到报错了再一个个补。授权的时候用命令行工具icacls批量操作效率更高比如icacls D:\bugzilla /grant IIS AppPool\BugzillaPool:(OI)(CI)(RX) /T icacls D:\bugzilla\data /grant IIS AppPool\BugzillaPool:(OI)(CI)(M) /T这两条命令分别授予读取执行权限和修改权限(OI)(CI)表示对象继承和容器继承/T表示递归应用到子目录。这样一次配好省得后面反复折腾。5.3 命令行验证的价值我反复强调命令行验证是因为它能帮你快速定位问题层次。命令行下跑脚本用的是你当前登录用户的身份和权限绕过了 IIS 的应用程序池身份。如果命令行能跑通说明脚本、Perl、模块、数据库配置都没问题问题一定在 IIS 的进程创建环节。如果命令行跑不通那先解决脚本层面的问题别去动 IIS。这个二分法能省下大量时间。很多人一上来就怀疑 IIS 配置改来改去结果真正的问题是 Perl 模块没装全。反过来也有人一直在命令行折腾脚本其实脚本没问题是 IIS 权限没配好。先用命令行做一次验证方向立刻就清晰了。验证的时候注意命令行的当前目录要切到 Bugzilla 根目录因为脚本里可能用了相对路径引用其他文件。另外命令行下跑 CGI 脚本它会输出 HTTP 头加 HTML 内容你看到一堆Content-Type: text/html开头的文本就说明脚本执行成功了哪怕后面跟着数据库错误也不影响判断。6. 部署环境选型与长期维护建议6.1 Windows 与 Linux 部署的取舍如果你的团队对 Linux 不排斥我真心建议把 Bugzilla 部署在 Linux 上。不是 Windows 不能用而是 Windows 下的坑明显更多CGI 进程创建只是其中一个。Linux 下用 Apache mod_cgi 或者 mod_perl配置简单权限模型清晰Perl 模块安装也顺畅。同样的 BugzillaLinux 下可能半小时装完Windows 下折腾一整天还在排查权限。但如果环境限制只能用 Windows那也没关系把上面讲的这些配置一次做对后续维护其实也不复杂。关键是第一次部署时把基础打牢路径、权限、映射、日志这四样配好后面基本不会出大问题。我维护过的一个 Windows 版 Bugzilla稳定跑了三年多中间只因为系统升级重启过几次日常使用没出过故障。6.2 日常维护的几个关键点Bugzilla 跑起来之后日常维护主要关注几件事。一是日志清理IIS 日志和 Bugzilla 自己的日志都会增长定期归档或清理避免占满磁盘。二是数据库备份Bugzilla 的数据都在数据库里定期备份是必须的别等出事了才后悔。三是 Perl 模块更新安全补丁出来要及时打但更新前先在测试环境验证别直接在生产环境上操作。还有一点容易被忽略Bugzilla 的data目录里存着附件、图表缓存等文件这个目录要定期检查大小附件多了会占很多空间。可以配置定期清理过期附件或者把附件存储迁移到独立位置。这些配置在 Bugzilla 的管理界面里都有花点时间熟悉一下后面省心。6.3 遇到问题时的排查心态最后说点虚的但我觉得挺重要。这类部署问题最忌讳的就是东改一下西改一下改到最后自己都不知道改了什么问题反而更乱。正确的做法是先理解报错的含义确定问题发生在哪个阶段然后按层次逐层排查每改一处就验证一次确认有效再继续。couldnt create child process这个错误本质上就是进程创建失败围绕“路径、权限、身份、环境”这四个关键词去查基本都能解决。我处理过的同类问题里没有一个是靠猜解决的都是按这个思路一步步定位到的。所以别慌按顺序来问题一定能找到。