☰
PHP 7.1.31源码编译实战:php-fpm配置、扩展安装与安全加固要点
2026/10/16 22:15:22 网站建设 项目流程

简介:php-7.1.31.tar.gz 是 PHP 7.1.31 官方源码包,面向需要在 Linux 环境编译安装 PHP、或希望阅读源码理解内核机制的运维人员和 Web 开发者。该版本属于 7.1 系列稳定分支,自带若干性能改进与安全修复。整个压缩包约 18.8MB,共 19510 个文件,主体为 C 语言源码、头文件和 PHP 脚本,同时包含大量 phpt 测试用例、xml 数据文件以及 m4/am 构建配置脚本,兼顾运行构建与测试验证需求。目前已有 287 人学习下载。通过解压阅读,可完整了解 PHP 7.1 新增的 void 返回类型、nullsafe 操作符、类常量可见性等特性在源码中的实现方式,也能学习 Zend 引擎和标准扩展的代码组织;配合 configure、make、make install 流程,可在 Linux 上独立完成编译安装,为二次开发、故障排查和性能优化提供基础。

1. 为什么还在用 PHP 7.1.31:从源码包看兼容性与安全边界

在 2024 年还在下载 php-7.1.31.tar.gz 这件事,听起来像在翻老黄历,但接手过遗留系统的开发者都能理解:一套运行了五六年的业务系统,核心逻辑是用 PHP 7.1 写的,迁移成本远高于升级成本。这个源码包解决的不是“新不新”的问题,而是“能不能继续稳定跑”的问题。它适合三类人:一是维护老项目的运维,二是做二次开发的工程师,三是想研究 PHP 源码结构的初学者。源码包本身不含安装程序,它需要你在 Linux 上手动编译,这反而给了你完全掌控扩展、参数和运行环境的自由。本文会从编译参数、php-fpm 配置、避坑记录到安全加固,完整过一遍可复现的落地流程。

2. 编译前的准备:依赖、参数与 configure 选型

2.1 确认环境与安装依赖

编译 PHP 源码的第一步不是执行 ./configure,而是先把系统环境摸清楚。我习惯先确认三件事:操作系统版本、GCC 编译器、已有的扩展依赖。以 CentOS 7 和 Ubuntu 20.04 为例,命令略有差异,但思路一致。

cat /etc/os-release gcc --version make --version

如果 GCC 版本过低,比如 CentOS 6 自带的 4.4.7,编译 PHP 7.1 的某些模块会报错。建议 GCC 版本不低于 4.8,否则需要手动升级或者安装 devtoolset。接下来安装基础依赖:

# CentOS / RHEL 系 yum install -y epel-release yum install -y gcc gcc-c++ make automake autoconf libtool \ libxml2-devel openssl-devel curl-devel bzip2-devel \ libjpeg-devel libpng-devel freetype-devel libmcrypt-devel \ mhash-devel libxslt-devel libc-client-devel # Ubuntu / Debian 系 apt-get update apt-get install -y build-essential autoconf libtool \ libxml2-dev libssl-dev libcurl4-openssl-dev \ libbz2-dev libjpeg-dev libpng-dev libfreetype6-dev \ libmcrypt-dev libxslt1-dev

这里面的 libxml2-devel 和 openssl-devel 是大多数人第一次踩坑的地方:如果缺失,configure 会直接停在检查 libxml2 的步骤。另外 libmcrypt 在 PHP 7.1 里还是官方扩展,到了 PHP 7.2 就被移除了,所以很多老教程里的参数在 7.1 上依然能用,这点是 7.1.31 的一个“历史优势”。

依赖装完之后,建议做个系统快照或直接备份干净的虚拟机镜像。编译源码不像 apt install 那样有完整的卸载机制,一旦中途失败,残留文件很容易污染后续尝试。

2.2 configure 关键参数说明

PHP 的 configure 脚本是整个编译过程的灵魂。7.1.31 的 configure 支持几百个参数,但多数业务场景用不到。我常用的是一套“中间路线”:不追求全部模块,也不刻意裁剪,保证常用功能和安全扩展都在。

./configure \ --prefix=/usr/local/php71 \ --with-config-file-path=/usr/local/php71/etc \ --with-config-file-scan-dir=/usr/local/php71/etc/conf.d \ --enable-fpm \ --with-fpm-user=www \ --with-fpm-group=www \ --enable-mbstring \ --enable-zip \ --enable-pcntl \ --enable-sockets \ --enable-opcache \ --enable-bcmath \ --with-curl \ --with-openssl \ --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd \ --with-mhash \ --with-zlib \ --with-gettext \ --with-bz2 \ --with-mcrypt

参数分三组理解。第一组是路径相关:--prefix 指定安装目录,--with-config-file-path 指定 php.ini 位置,--with-config-file-scan-dir 让扩展配置可以分开管理,用 conf.d 目录批量加载。第二组是运行模式:--enable-fpm 必须启用,否则只能跑 CLI;--with-fpm-user 和 --with-fpm-group 指定了 php-fpm 的子进程身份,通常设为 www 用户,避免用 root 跑业务进程。第三组是扩展:mysqlnd 是 PHP 官方推荐的 MySQL 驱动,比 libmysqlclient 更容易编译且性能更好;--enable-pcntl 是给常驻脚本用的,如果你只是跑 Web,可以去掉,但加上也不吃亏。

需要注意,--enable-opcache 在 PHP 7.1 里已经是自带扩展,但它不是默认开启的,编译时加上这个参数才会生成 opcache.so,后续还要在 php.ini 里显式启用。这个细节很多人会漏,导致编译完发现 opcache 根本没生效。

2.3 快速编译安装流程

配置完成后,编译安装是三行命令的事,但每行都可能卡半小时以上。

make -j$(nproc) make install

-j 参数是并行编译的核数,用 $(nproc) 自动获取 CPU 核数。如果服务器内存小于 2GB,建议改成 -j2 或干脆不加,否则 make 过程会因内存不足被 OOM Killer 杀掉。我第一次在 1GB 内存的 VPS 上编译时,直接 -j4,结果还差 30% 的时候进程被系统杀了,后面老老实实 -j1 一次过。

make install 之后并不会自动生成 php.ini,需要手动从源码目录复制:

cp php.ini-production /usr/local/php71/etc/php.ini cp /usr/local/php71/etc/php-fpm.conf.default /usr/local/php71/etc/php-fpm.conf cp /usr/local/php71/etc/php-fpm.d/www.conf.default /usr/local/php71/etc/php-fpm.d/www.conf

php.ini-production 是生产环境推荐配置,错误显示关闭、日志开启;php.ini-development 则相反,适合本地调试。建议生产用 production,调试时再临时改 display_errors。

复制完配置文件,先验证版本:

/usr/local/php71/bin/php -v

如果输出版本号是 7.1.31,说明基础编译成功。但 php-fpm 还没有启动脚本,需要自己写 systemd unit 或直接用 /usr/local/php71/sbin/php-fpm 前台跑。这一步放在下一章详细说。

3. 把 PHP 跑起来:php-fpm、systemd 与扩展加载

3.1 php-fpm 的配置与启动

编译安装的 php-fpm 不会自动注册为系统服务,这是源码包和发行版软件包最大的区别。我最开始直接用 /usr/local/php71/sbin/php-fpm 启动,发现进程在前台跑,终端一关就没了。后来写了个 systemd unit 文件才彻底解决。

cat > /etc/systemd/system/php71-fpm.service <<'EOF' [Unit] Description=PHP 7.1 FastCGI Process Manager After=network.target [Service] Type=forking PIDFile=/usr/local/php71/var/run/php-fpm.pid ExecStart=/usr/local/php71/sbin/php-fpm --daemonize --fpm-config /usr/local/php71/etc/php-fpm.conf ExecReload=/bin/kill -USR2 $MAINPID ExecStop=/bin/kill -QUIT $MAINPID PrivateTmp=true [Install] WantedBy=multi-user.target EOF

Type 选择 forking,因为 php-fpm 默认是 master 进程启动时分叉出 worker 进程后自己守护化。ExecStart 里必须显式指定 --fpm-config,否则它会去默认路径找。PIDFile 也需要和 php-fpm.conf 里的 pid 配置保持一致,我习惯在 php-fpm.conf 里写死:

pid = /usr/local/php71/var/run/php-fpm.pid

写好 unit 后依次执行:

systemctl daemon-reload systemctl enable php71-fpm systemctl start php71-fpm systemctl status php71-fpm

状态显示 active (running) 就完成了。如果启动失败,看日志 /usr/local/php71/var/log/php-fpm.log,绝大多数错误是配置文件路径写错或监听端口被占用。

3.2 扩展动态加载与 php.ini 边界

PHP 扩展有两种加载方式:编译时静态编译进 PHP,或者编译成 .so 文件后动态加载。7.1.31 的 configure 参数里,像 mysqli、opcache 这类如果启用了,会在 make install 时自动生成 .so 文件。但有些扩展没在 configure 里指定,比如 redis、swoole,就需要事后用 phpize 单独编译。

动态加载的路径由 php.ini 里的 extension_dir 决定。查看这个值可以用:

/usr/local/php71/bin/php -i | grep extension_dir

我的习惯是把所有扩展的 .so 放在 /usr/local/php71/lib/php/extensions/ 下,然后在 conf.d 目录里每个扩展一个 ini 文件。例如:

; /usr/local/php71/etc/conf.d/opcache.ini zend_extension=/usr/local/php71/lib/php/extensions/opcache.so ; /usr/local/php71/etc/conf.d/redis.ini extension=/usr/local/php71/lib/php/extensions/redis.so

注意 zend_extension 和 extension 的区别:opcache 是 Zend 扩展,必须用 zend_extension 加载,定义在前;普通扩展用 extension 就行。如果只写 extension=/path/to/opcache.so,PHP 启动时会报“Cannot load Zend OPcache - it was already loaded”或直接忽略。

php.ini 本身的边界也要注意:PHP 7.1 的配置文件是按需读取的,不是每次请求都重新解析,而是 PHP 进程启动时加载一次。改完 php.ini 必须 reload php-fpm 才能生效:

systemctl reload php71-fpm

用 kill -USR2 也行,但 systemctl reload 更规范,不会中断正在处理的请求。

3.3 与 Nginx 对接的常见配置

php-fpm 默认监听 127.0.0.1:9000,Nginx 通过 fastcgi_pass 转发请求。这个配置本身不复杂,但有几个容易被忽略的参数会直接影响是否能跑通。

server { listen 80; server_name example.com; root /data/www; index index.php index.html; location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }

最关键的一行是fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name。很多人把 root 定义在 location 外面,导致 SCRIPT_FILENAME 指向了错误路径,Nginx 把请求转发给 php-fpm 后,PHP 找不到实际文件,直接返回 “Primary script unknown”。另外,如果 root 包含符号链接,$document_root 取到的是物理路径,这时需要用 realpath 或额外设置fastcgi_param SCRIPT_FILENAME /真实路径$fastcgi_script_name。

还可以改为监听 Unix socket,减少 TCP 开销:

; php-fpm.d/www.conf listen = /run/php71-fpm.sock listen.owner = www listen.group = www listen.mode = 0660

Nginx 侧对应:

fastcgi_pass unix:/run/php71-fpm.sock;

用 socket 需要注意的是权限:Nginx worker 进程必须对 socket 文件有读写权限,否则会报 connect() failed (13: Permission denied)。我在 CentOS 上用 nginx 用户跑 Nginx,把 socket 的 owner 设为 www 还不够,还必须把 nginx 用户加进 www 组,或者把 listen.mode 设成 0666(不推荐,安全性差)。

4. 避坑指南:编译安装 PHP 7.1.31 的五个常见问题

4.1 现象:configure 报错 libxml2 版本过低

我在一台 CentOS 6 的旧服务器上编译时,configure 输出:

checking libxml2 dir... no configure: error: xml2-config not found. Please check your libxml2 installation.

原因有两个:一是 libxml2-devel 没装,二是系统自带 libxml2 版本太老。CentOS 6 默认的 libxml2 是 2.7.x,PHP 7.1 要求至少 2.8.0。

解决办法是删除旧 libxml2 并编译安装新版源码。但直接替换系统 libxml2 风险很高,因为很多系统工具也依赖它。我采用了更保守的方案:单独编译一个 libxml2 到 /usr/local/libxml2,然后在 configure 时用 LDFLAGS 和 CPPFLAGS 指定路径:

./configure \ --with-libxml-dir=/usr/local/libxml2 \ LDFLAGS="-L/usr/local/libxml2/lib" \ CPPFLAGS="-I/usr/local/libxml2/include"

这样既不干扰系统库,又能满足 PHP 的版本要求。如果服务器是 CentOS 7 以上,直接yum install libxml2-devel就能解决,不需要这么麻烦。

4.2 现象:make 时内存不足导致进程被杀

内存 1GB 的机器上跑 make -j4,编译到一半终端卡住,然后看到 “Killed” 或 “cc1: out of memory”。原因是多进程并行编译时,每个 cc1 进程都可能占几百 MB 内存,1GB 根本不够。

解决办法分两步:一是把并行数降下来,用make -j1或make -j2;二是临时增加 swap,给编译过程一个缓冲:

dd if=/dev/zero of=/swapfile bs=1M count=1024 mkswap /swapfile swapon /swapfile

编译完再把 swap 关掉,避免影响生产。注意这一步在不差内存的服务器上不需要做,但内存紧张时它比换机器更实用。另一个思路是只编译需要的模块,configure 时把没用的功能都去掉,比如 --disable-fileinfo --disable-debug,能显著减少内存占用。

4.3 现象:php-fpm 启动后 Nginx 返回 502 Bad Gateway

Nginx 已经配置好 fastcgi_pass,但访问页面返回 502。SSH 到服务器上看 php-fpm 进程还在,但ss -lntp | grep 9000没有输出。

这个坑多半是 php-fpm 启动失败但 systemd 状态没同步,或者 php-fpm 监听的不是 9000 端口。先看日志:

tail -f /usr/local/php71/var/log/php-fpm.log

常见的错误是listen = 127.0.0.1:9000与 Nginx 的 fastcgi_pass 写的不一致。比如 php-fpm 改成监听 127.0.0.1:9001,Nginx 还写 9000,自然连不上。也有权限问题:如果你用非 root 用户跑 php-fpm,而 php-fpm.conf 里设置了listen.allowed_clients,会出现连接拒绝。解决就是检查一致性和权限,然后 systemctl restart php71-fpm。

还有一种隐蔽情况:php-fpm 启动了,但 worker 进程全部处于阻塞状态,Nginx 长连接超时。这种要从业务代码排查,而不是改配置。

4.4 现象:扩展编译后无法加载

用 phpize 编译了一个扩展,把 .so 放到扩展目录,在 php.ini 里加 extension,但php -m里看不到,报错:

PHP Warning: PHP Startup: Unable to load dynamic library '/usr/local/php71/lib/php/extensions/redis.so' - cannot open shared object file

原因往往是 .so 依赖的某个系统库版本不对,或者编译扩展时用的 PHP API 版本与当前 PHP 不一致。phpize 会自动匹配当前 PHP 的 API,但如果你 system 里还装了另一个 PHP,PATH 环境变量可能导致 phpize 指向错误版本。

解决方式是用绝对路径调用:

/usr/local/php71/bin/phpize ./configure --with-php-config=/usr/local/php71/bin/php-config make && make install

注意第二行里的 --with-php-config 是很多教程不写的。如果不显式指定,configure 默认找 PATH 里的 php-config,非常容易串版本。我本地同时有系统 PHP 5.4 和自编译 PHP 7.1,第一次没指定,编译出来的扩展加载时直接报“API 版本不匹配”。

4.5 现象:php -v 显示版本不对

明明把 PHP 7.1.31 装到了 /usr/local/php71,执行php -v却看到 7.4 或其他版本。这是 PATH 环境变量的问题,系统自带的 PHP 目录 /usr/bin 排在 /usr/local/php71/bin 前面。

可以临时用全路径调用:

/usr/local/php71/bin/php -v

但业务脚本里也可能会出现同样问题,比如 crontab 里的命令。最稳妥的办法是调整 PATH 或建立软链接:

ln -s /usr/local/php71/bin/php /usr/local/bin/php71 ln -s /usr/local/php71/sbin/php-fpm /usr/local/sbin/php-fpm71

然后在脚本里直接写 php71,避免和系统 php 冲突。这个方法比直接覆盖 /usr/bin/php 安全得多,起码系统自带工具不会被破坏。

5. 性能与安全:针对 PHP 7.1 的调优参数

5.1 opcache 配置推荐

PHP 7.1 的 opcache 扩展已经非常成熟,开启后能显著降低 CPU 负载。核心参数如下:

; /usr/local/php71/etc/conf.d/opcache.ini zend_extension=/usr/local/php71/lib/php/extensions/opcache.so opcache.enable=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=10000 opcache.revalidate_freq=60 opcache.fast_shutdown=1 opcache.enable_cli=0

memory_consumption 是给 opcache 缓存脚本预留给内存,按每个 PHP 文件平均 10KB 估算,1 万个文件大概 100MB,所以 128MB 起步。max_accelerated_files 设置缓存文件数量上限,如果项目文件超过这个数,opcache 会自动驱逐旧缓存,导致命中率下降。revalidate_freq 是每隔多少秒检查一次源文件时间戳,生产环境设 60 甚至更大,测试环境可以设 0。

注意 enable_cli 在 PHP 7.1 里默认是 0,如果你用 CLI 跑长生命周期脚本,比如 pcntl 写的常驻进程,建议设为 1,否则每次脚本启动都会重新编译一遍。

php -i | grep opcache可以看到 opcache.enable 和 loaded configuration 是否为有效状态。如果 opcache.so 加载了但 enable 值为 Off,多半是 php.ini 里没有注释掉之前的默认设置。

5.2 php-fpm 进程池参数

php-fpm 的 www.conf 里最关键的是 pm 相关参数。三种模式:static、ondemand、dynamic。我一般用 dynamic,适合业务请求量相对平稳的场景。

; /usr/local/php71/etc/php-fpm.d/www.conf pm = dynamic pm.max_children = 20 pm.start_servers = 5 pm.min_spare_servers = 3 pm.max_spare_servers = 8 pm.max_requests = 1000

max_children 是最大 worker 进程数,这个值取决于内存:每个 PHP-FPM worker 平均占用内存大约 30-60MB,按 2GB 可用内存计算,max_children 建议不超过 40,保守一点 20 更稳。如果业务内存曲线波动大,可以用 static 模式固定进程数,避免频繁创建销毁进程。

pm.max_requests 是一个 worker 处理完多少请求后自动回收,目的是防止长时间运行导致的资源泄漏。PHP 7.1 的官方文档建议设为 500-1000 之间。注意,这个参数并不保证内存泄漏修复,只是给了一个“重启后释放掉”的机制。

改完配置后必须先 reload 才能生效:

systemctl reload php71-fpm

然后监控一下 worker 数量:

ps aux | grep php-fpm | grep -v grep | wc -l

如果 worker 数持续走高逼近 max_children,说明请求量超过了处理能力,要么加机器,要么优化代码,单纯调大 max_children 只是把问题往后拖。

5.3 安全加固的几项设置

PHP 7.1 有些默认参数在公网环境很危险,下面是三处必改项。

第一,禁用危险函数。在 php.ini 的 disable_functions 里加上:

disable_functions = exec,shell_exec,passthru,system,proc_open,popen,curl_exec

但注意 curl_exec 不能全局禁用,因为很多业务依赖 curl 请求第三方 API。这里建议只禁用前五个,curl_exec 需要按业务场景判断。

第二,隐藏 PHP 版本信息。在 php.ini 里设置:

expose_php = Off

否则 HTTP 响应头会带 X-Powered-By: PHP/7.1.31,攻击者可以直接判断你的框架版本。

第三,open_basedir 设置文件访问边界。对于单一站点,在 php-fpm 的 www.conf 里:

php_admin_value[open_basedir] = /data/www:/tmp

这样即使脚本被注入恶意代码,也只能访问指定目录,无法读取系统关键文件。注意这个配置是 php_admin_value,意味着业务代码里无法修改,比在 php.ini 里设置更安全。

安全加固没有终点,但这些基础参数能挡住 80% 的自动化扫描脚本。至于 7.1.31 本身存在的已知漏洞,如果没有能力升级 PHP 版本,至少要把官方发布的补丁打上,比如某些拒绝服务类漏洞的修复。

6. 验证安装与进阶技巧:从 phpinfo 到自编译扩展

6.1 用 phpinfo 验证关键参数

安装完成后,很多人直接写个 phpinfo 页面放在 web 根目录,但忘了之后删掉,这是很大的安全隐患。我会先跑 CLI 验证:

/usr/local/php71/bin/php -i > /tmp/phpinfo_cli.txt grep -E "PHP Version|Loaded Configuration|opcache.enable|memory_limit" /tmp/phpinfo_cli.txt

重点检查四项:PHP Version 是不是 7.1.31,Loaded Configuration File 是不是指向 /usr/local/php71/etc/php.ini,opcache.enable 是否 On,memory_limit 是否符合预期。如果 Loaded Configuration File 为空,说明 php.ini 没被加载,所有配置都没生效。

再看 Web 环境的 phpinfo 是否和 CLI 一致。有些系统会存在两套 PHP,CLI 和 php-fpm 可能加载不同的配置文件。用php-fpm -i也能输出 phpinfo,但更直观的是临时建一个页面:

<?php phpinfo();

访问后确认 Server API 显示 FPM/FastCGI,PHP Version 一致。验完马上删掉这个文件,别留到第二天。

6.2 手动编译安装一个扩展的完整流程

掌握了 PHP 7.1.31 的编译环境,之后装任何 PECL 扩展都顺手。以 redis 为例,完整流程走一遍。

cd /usr/local/src wget https://pecl.php.net/get/redis-5.3.7.tgz tar zxvf redis-5.3.7.tgz cd redis-5.3.7 /usr/local/php71/bin/phpize ./configure --with-php-config=/usr/local/php71/bin/php-config make && make install

make install 完成后,终端会列出 redis.so 的安装路径,通常是 /usr/local/php71/lib/php/extensions/no-debug-non-zts-20160303/。然后在 conf.d 里新建 redis.ini:

extension=redis.so

最后 reload php-fpm:

systemctl reload php71-fpm /usr/local/php71/bin/php -m | grep redis

看到 redis 输出就算成功。这里有一个细节:redis.so 文件已经复制到了扩展目录,但 php.ini 里 extension_dir 没设置的话,PHP 默认去编译时指定的路径找,建议显式写清楚。

我后来自己写了一个小脚本,把 phpize、configure、make、make install 这四个步骤封装成函数,参数传入扩展名,几秒钟就能编译完一个扩展。但注意 PECL 扩展源码版本要和 PHP 7.1 兼容,如 redis 的 5.x 系列已经验证过,6.x 则要求 PHP 7.2 以上。

6.3 备份与回滚的实用技巧

编译安装的 PHP 没有包管理器帮你回滚,所以我的习惯是任何改动前都做一次目录备份。具体做法是把整个 /usr/local/php71 目录打个包:

tar czf /backup/php71-$(date +%F-%H%M).tar.gz \ /usr/local/php71/etc \ /usr/local/php71/bin \ /usr/local/php71/lib/php/extensions

不备份整个目录,因为二进制文件恢复了也许不匹配,重点备份 etc 配置文件、bin 可执行文件和已编译的扩展。换服务器时这个包也能直接解压到新机器,配合系统依赖库,几分钟恢复一个 PHP 环境。

另一个技巧是使用 php-fpm 的平滑重载来“回滚”配置。比如改了 php.ini 导致 FPM 异常,不要直接 restart,那会中断所有请求。先保存当前配置,再改,然后 reload;如果出问题,把备份配置盖回去再 reload。用 pid 文件的进程号发kill -USR2也能重载,但 systemctl reload 更省事。

从那以后,我每次在 PHP 7.1 上动 php.ini 或扩展前,都强制自己走一遍“先打包、再修改、观察 10 分钟”的流程。哪怕是加一个小小的 opcache 参数,也会在备份后执行。这个习惯帮我避免了一次凌晨三点的线上事故——那次改了 memory_limit 后 FPM 直接起不来,靠备份包一分钟内回滚成功。希望这台“后悔药”的流程能帮你也少踩一次坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询