1. 先搞清楚需求:为什么要在麒麟V10(Sword)上自己编译PHP
1.1 麒麟Kylin Linux Advanced Server V10系统的真实身份
在开始之前,先看看这套系统到底是什么。Kylin Linux Advanced Server V10 (Sword) 是用于服务器场景的Linux发行版,命令习惯和包管理跟RHEL 8/CentOS 8这一系非常接近,有yum/dnf可以用,系统下面也是glibc、gcc、systemd这一套标准组件。也就是说,你在CentOS 8上积累的编译排查经验,大部分可以直接迁移过来。
我第一次在这套系统上装PHP时也有点犹豫,怕麒麟有自己特殊的东西。实际用下来,除了软件源里某些包名、版本跟CentOS不完全一样,其余操作思路几乎一致。特别是在内网、离线环境,或者需要定制PHP扩展的时候,源码编译反而是最稳、最可控的路径。
很多人问:为什么不用yum或者dnf直接装?原因很简单:麒麟自带的仓库里PHP版本偏旧,有些还是7.x甚至更老的版本;对于需要跑现代框架、要用较新语法特性的项目来说,自带的版本可能不够。另一个场景是多版本共存,一台机器要同时跑PHP 7.4和PHP 8.3,各自独立目录,源码编译是天然合适的方案。
1.2 编译安装 vs 仓库安装,怎么选
| 对比项 | yum/dnf安装 | 源码编译安装 |
|---|---|---|
| 版本控制 | 由系统仓库决定,通常偏保守 | 自己选版本,紧跟官方稳定版 |
| 扩展定制 | 可安装仓库提供模块,灵活度一般 | configure阶段直接决定编译哪些扩展 |
| 安装目录 | 分散在/usr/bin、/etc等位置 | --prefix指定统一目录,卸载方便 |
| 升级维护 | dnf update即可,简单 | 需要重新编译,但可控性高 |
| 依赖管理 | 大部分自动解决 | 需要自己确保-devel包齐全 |
| 生产安全 | 有发布方测试过的二进制 | 依赖编译参数和系统环境,需要严格测试 |
我个人倾向:线上老项目、对性能要求不极端的,用yum装默认PHP够用;但如果你要跑Symfony/Laravel新版本、要做opcache调优、要自己编译Swoole这类扩展,或者需要在同一台机器上隔离多个版本,源码编译是更合理的选择。
1.3 这篇文章适合谁来参考
如果你正准备在一台新的麒麟V10服务器上把PHP从源码装起来,并且后续要跟Nginx对接,那么这篇文章可以给你一条完整路线。不需要你有特别强的C语言背景,只要会敲命令、能看懂终端报错,跟着步骤走就能装出来。
当然,我也要说清楚:编译安装最大的坑不是“不会敲命令”,而是“依赖东缺西缺”。你会发现 configure 和 make 过程中报的最多错误,都是缺某个头文件。所以这部分我会把依赖、参数、坑位都写到,尽量让你少走几趟弯路。
2. 环境准备和配置方案:动手前先抓好基础
2.1 用一条命令摸清系统家底
编译之前先确认系统版本、架构、CPU核数和可用内存,避免后面 make 的时候内存爆炸。
cat /etc/os-release uname -m nproc free -h gcc --versionuname -m的输出很关键。x86_64 是常见架构,arm64 会显示 aarch64。麒麟V10有大量ARM版本,很多人在ARM机器上直接抄x86的编译参数,结果依赖包名字不一样,或者某些扩展编译失败。命令里的nproc是CPU核数,决定了后面make -j用多少并行任务;free -h看内存,内存小于2GB的时候编译要保守一些。
系统里如果没有gcc,后面安装依赖时一并装上,不需要提前单独处理。按理说,麒麟服务器版会自带基础开发工具,但如果是一台裁剪过的内网机器,可能连make都没有。
2.2 依赖库和编译工具链一次性装全
这一步最容易偷懒,但也是后面报错最少的保证。编译PHP需要两样东西:编译工具链,以及PHP各种扩展所依赖的开发头文件。
dnf install -y gcc gcc-c++ make autoconf libtool automake pkg-config m4 dnf install -y pcre2-devel zlib-devel libxml2-devel openssl-devel curl-devel bzip2-devel sqlite-devel dnf install -y libjpeg-devel libpng-devel freetype-devel libwebp-devel oniguruma-devel如果你的麒麟系统把dnf屏蔽了,换成yum install也可以,包名完全一样。
这些包分别干什么,我简要说明一下:pcre2-devel是PHP正则表达式底层所使用的PCRE2库头文件;libxml2-devel提供XML解析能力,PHP 8基本强制依赖它;openssl-devel用于HTTPS、加密函数等;curl-devel提供cURL扩展;oniguruma-devel是mbstring多字节字符串处理的依赖。libjpeg/libpng/freetype则是给GD图像扩展用的,如果不需要图片处理可以暂时不装,但大多数Web应用早晚会用到。
有些精简系统可能缺少bzip2-devel、sqlite-devel,这两个不装也不影响基础运行,但根据你的项目情况提前装掉,避免PHP configure时又说找不到库。总体原则:宁可多装,不要缺装,一个编译周期很耗时,反复重新configure更痛苦。
2.3 下载PHP源码包,顺手校验完整性
源码包建议下载一个稳定版本。我这边以 PHP 8.3 系列为例,在干净环境里编译稳定性和兼容性都表现不错。下载目录推荐放到/usr/local/src,不要放在/home下,避免权限和路径问题。
cd /usr/local/src wget https://www.php.net/distributions/php-8.3.10.tar.gz sha256sum php-8.3.10.tar.gz tar -zxvf php-8.3.10.tar.gz cd php-8.3.10sha256sum是很多人忽略的习惯。官网每发布一个源码包,都会给一个hash值,下载后自己算一下,确认没有被修改或原包没损坏。编译环境里用损坏包很坑,往往make到一半才报莫名其妙的“内部错误”,实际是压缩包本身有问题。如果你在内网环境不能访问外网,那就从内部软件仓库拿到源码包,同样算一遍hash再解压。
2.4 configure参数怎么选,每个都讲清楚
configure是编译安装的重头戏,也是最容易出现“选择困难症”的地方。我的习惯是:先想清楚这个PHP要不要跑PHP-FPM,要接什么数据库,要不要图像处理,然后按需配置。
下面是一套覆盖大多数Web场景的配置:
./configure --prefix=/usr/local/php \ --with-config-file-path=/usr/local/php/etc \ --enable-fpm \ --with-fpm-user=www \ --with-fpm-group=www \ --enable-mbstring \ --enable-zip \ --enable-bcmath \ --enable-pcntl \ --enable-sockets \ --enable-opcache \ --with-openssl \ --with-curl \ --with-zlib \ --with-bz2 \ --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd \ --with-jpeg \ --with-freetype \ --with-webp \ --enable-gd参数逐个拆解:
--prefix=/usr/local/php是把PHP装到独立目录,所有文件都放在这里,后续升级或者不想用了,直接删目录再清理PATH就行,不污染系统目录。--with-config-file-path指定php.ini的位置,默认路径在不同系统上变化多端,很容易出现“装了PHP却找不到配置”的情况,所以显式指定。
--enable-fpm是编译PHP-FPM,这是Nginx需要的关键组件。配合--with-fpm-user和--with-fpm-group指定PHP-FPM进程运行身份。生产环境一般不用root跑PHP,所以后面要新建一个www用户。
--enable-mbstring、--enable-zip、--enable-bcmath、--enable-pcntl、--enable-sockets都是日常高频扩展。mbstring不用多说,中文字符串处理;zip用于打包解压;bcmath处理高精度数字;pcntl和sockets是很多后台任务、消息队列可能用到的;opcache是官方字节码缓存,必须开,提升性能非常明显。
数据库方面,--with-mysqli=mysqlnd和--with-pdo-mysql=mysqlnd都使用mysqlnd驱动,不用依赖libmysqlclient,省心。GD库则按需配置jpeg、freetype、webp支持,大多数图片处理都会用到。
这里有个重要的原则:configure参数不是越多越好,没用的扩展会增加编译时间和潜在风险。如果暂时用不到gd,可以先把GD相关参数删掉;如果项目不需要pcntl,也可以去掉。等到后面需要再单独编译扩展,反而更灵活。
3. 完整编译安装流程:从零跑到Nginx
3.1 先创建PHP-FPM运行用户
我用的是独立运行用户,一个习惯操作是:
groupadd www useradd -M -s /sbin/nologin -g www www-M 表示不创建home目录,-s 指定为不可登录shell,这样即使PHP被攻击,攻击者也很难用这个账号直接登进系统。很多新手会跳过这一步,直接用root跑PHP-FPM,虽然能跑,但安全上风险相当大。如果你后面编译Nginx也要用www用户,同一个账号可以直接复用。
3.2 configure、make、make install的完整过程
进入源码目录后,先执行上文给出的 configure 命令。这一步主要检查依赖是否齐全,正常情况下会输出一长串“creating cache”“checking for...”之类的内容,最后出现config.status: creating ...和一句话Thank you for using PHP。
如果中途报错,先不要慌,排查表我放在下一大节。configure完成之后,当前目录已经生成了Makefile。接下来先确认核数:
nproc make -j4-j后面的数字不是越大越好。编译PHP时每个编译进程都会占用内存,比如-j4在4核服务器上很有可能同时跑4个cc进程,每个进程占用几十甚至上百MB,如果总内存只有2G,容易直接卡死。内存充足时用-j$(nproc)确实快,但保守起见,2G内存的机器用make -j2更稳。
整个编译过程一般需要10到20分钟,ARM机器会更久。出现一堆gcc调用和一个Build complete.就说明make成功了。这一步最常见失败原因是virtual memory exhausted,就是内存不足,解决办法是关掉一些并行任务,或者临时扩大swap。
make成功之后继续:
make install这一步会把编译好的二进制、库文件复制到/usr/local/php下。结束后验证一下:
/usr/local/php/bin/php -v能打印PHP版本号,说明主体已经落地。
3.3 初始化php.ini和php-fpm.conf
源码包里默认带两个php.ini模板:php.ini-development适合本地调试,php.ini-production适合线上环境。我强烈建议生产用production模板,然后按需调几个参数。
cp php.ini-production /usr/local/php/etc/php.ini cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf需要注意:改完php.ini之后,要确认PHP能读到它。用php --ini查看:
/usr/local/php/bin/php --ini如果返回的路径不是/usr/local/php/etc/php.ini,再回头检查configure时是否写了--with-config-file-path,最好也检查环境变量PHP_INI_SCAN_DIR有没有干扰。
php-fpm.conf默认会include同目录php-fpm.d/*.conf,所以核心池配置在www.conf中。编辑www.conf,找到这几行改成自己想要的:
user = www group = www listen = /var/run/php-fpm/php83.sock listen.owner = www listen.group = www listen.mode = 0660这里我选的是Unix Socket而不是TCP 9000端口。同一台机器上的Nginx走socket比走TCP更快,也少暴露一个端口。如果以后要跨机器通信,再改成listen = 127.0.0.1:9000。
创建socket目录并给权限:
mkdir -p /var/run/php-fpm chown www:www /var/run/php-fpm然后用php-fpm -t检查配置语法:
/usr/local/php/sbin/php-fpm -t显示test is successful就可以启动了。
3.4 做成一键管理的systemd服务
直接php-fpm也能跑,但重启、开机自启不方便。我一般会写一个systemd unit。
vim /etc/systemd/system/php-fpm.service内容如下:
[Unit] Description=PHP FastCGI Process Manager After=network.target [Service] Type=forking ExecStart=/usr/local/php/sbin/php-fpm ExecReload=/bin/kill -USR2 $MAINPID ExecStop=/bin/kill -QUIT $MAINPID PrivateTmp=true [Install] WantedBy=multi-user.target然后执行:
systemctl daemon-reload systemctl enable --now php-fpm systemctl status php-fpmType=forking 是因为php-fpm启动后会自己转入后台运行,配合ExecStart正好。如果启动不了,先跑一次/usr/local/php/sbin/php-fpm -F看前台日志,一般比abstract报错更明确。
3.5 Nginx配置,让PHP真正对外跑起来
Nginx本身不会解析PHP,它通过fastcgi协议把请求转给PHP-FPM。先在Nginx配置里建一个server块。
server { listen 80; server_name _; root /var/www/html; index index.php index.html; location ~ \.php$ { fastcgi_pass unix:/var/run/php-fpm/php83.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }fastcgi_pass的地址必须和php-fpm的listen保持一致。如果你用的是TCP,就写fastcgi_pass 127.0.0.1:9000;。SCRIPT_FILENAME是Nginx把请求的文件路径告诉PHP-FPM的关键参数,写错了很容易出现File not found。
写完后重载Nginx:
nginx -t nginx -s reload然后在网站根目录放一个index.php,内容写:
<?php phpinfo();打开浏览器访问服务器IP,看到PHP信息页,说明整套流程已经通了。看到信息页后,记得把这个临时文件删掉,避免泄露环境信息。
4. 编译过程常见问题和排查记录
4.1 configure阶段报错速查表
configure阶段报错通常是最容易解决的,因为错误信息已经把缺失的包名写在脸上了。
| 报错关键词 | 实际原因 | 解决命令 |
|---|---|---|
| No package 'libxml-2.0' found | 缺libxml2开发包 | dnf install -y libxml2-devel |
| No package 'openssl' found | 缺openssl开发包 | dnf install -y openssl-devel |
| No package 'libcurl' found | 缺curl开发包 | dnf install -y curl-devel |
| Cannot find pcre.h / pcre2.h | 缺PCRE或PCRE2 | dnf install -y pcre2-devel |
| oniguruma.h not found | mbstring依赖缺失 | dnf install -y oniguruma-devel |
| Please reinstall the libzip distribution | 缺zip扩展依赖 | dnf install -y libzip-devel |
| bz2 not found | 缺bzip2开发包 | dnf install -y bzip2-devel |
| libjpeg / freetype / webp not found | GD相关开发包缺失 | dnf install -y libjpeg-devel freetype-devel libwebp-devel |
需要注意的是,某些麒麟内网源可能没有libzip-devel,这种情况可以先用dnf search libzip确认一下。如果没有,可以临时去掉--enable-zip,后面用PECL单独补。
4.2 make阶段遇到“内存不足”和“编译中断”
make阶段最常见的问题是:
virtual memory exhausted: Cannot allocate memory这种通常是因为make -j并行任务太多,内存被多个cc进程吃光了。先free -h看内存,然后降到make -j1试试。如果内存还是不够,可以临时加swap:
dd if=/dev/zero of=/swapfile bs=1M count=4096 chmod 600 /swapfile mkswap /swapfile swapon /swapfile编译完再swapoff清理掉。这种方法应急可以,不建议长期依赖swap跑生产。
还有一种情况是网络下载源安装的源码包本身不完整,导致某个C文件缺失,make直接报No such file or directory。重新解压再编译即可,不必怀疑自己的参数。
4.3 php-fpm启动失败和Nginx 502
php-fpm启动失败,先运行:
/usr/local/php/sbin/php-fpm -t如果报ERROR: unable to bind listening socket,大概率是socket目录不存在或者权限不对。解决办法就是我在3.3节写的,提前创建目录并chown给www。
如果php-fpm明明在跑,Nginx却返回502,最可能是两个原因:
- fastcgi_pass地址跟php-fpm的listen不一致。TCP和socket写混了,肯定502。
- Nginx的worker进程或PHP-FPM socket文件权限设置不对。Nginx worker用nginx用户跑,PHP-FPM用www用户跑,socket文件的listen.owner/group就必须配好,否则nginx没权限访问。
还有一个坑是SELinux。麒麟V10某些安全模式下,Nginx访问Unix Socket会被拦截。临时验证可以用:
setsebool -P httpd_can_network_connect 1具体策略名可能因版本不同有区别,最好用getsebool | grep httpd_can_network查看支持项。生产环境不要简单setenforce 0,而是要按策略放行。
4.4 扩展加载路径和PATH环境变量混用问题
很多人在源码编译完PHP后,敲php -v发现还是旧版本。这是因为系统PATH里还有之前装的PHP路径,比如/usr/bin/php。解决方式有三种:修改/etc/profile,或使用绝对路径,或把/usr/local/php/bin和/usr/local/php/sbin加到PATH最前面。
export PATH=/usr/local/php/bin:/usr/local/php/sbin:$PATH想永久生效就写进/etc/profile.d/php.sh。结合之前说的扩展问题,如果php -m里看不到 mysqli、pdo_mysql,先检查编译参数是否正确,再检查php --ini里的extension_dir路径,确认要加载的.so文件是否真的存在。大多数情况下,编译参数对就不需要额外改php.ini。
4.5 ARM64架构上需要额外留神的事
麒麟V10有很大一批机器是arm64,也就是uname -m输出aarch64。在ARM上编译PHP,绝大多数源码本身没问题,但有几个细节:
- 下载的源码包要对应官方对ARM的兼容支持,目前PHP主线版本都是支持的。
- 如果某些扩展依赖的第三方库没有arm64版本的二进制包,可能需要通过源码编译这些库,比x86多一步。
- ARM上编译时间比x86长,建议用
make -j2或-j4更稳妥。 - 别拿x86机器上编译好的PHP二进制直接拷到ARM机器,绝大多数不能跑。
5. 最后分享几点实战经验和建议
编译安装PHP这件事,我第一次做的时候也被各种依赖问题折磨了一天。后来养成一个习惯:每次configure之前,先把dnf groupinstall "Development Tools"装上,再单独把上面列出的-devel包补齐。这一步能把一半的报错提前消灭掉。
另一个经验是,编译参数写进一个Shell脚本保存好。比如在/usr/local/src/build-php.sh里记录configure命令和依赖清单。操作系统升级、换机器、给同事复现环境时,直接跑脚本,不用每次重新回忆参数。我后来维护的多台PHP服务器,都是通过对脚本的目录结构和配置路径统一管理,升级PHP版本也就变成了“换源码包+改版本号+重跑脚本”这么简单。
如果后续还需要安装Nginx、MySQL等组件,建议把端口、运行用户、日志路径都提前规划好,别等全部装完再发现用户冲突、目录权限互相影响。最后再提醒一点:源码编译出来的PHP体积比较大,生产环境一定要在部署前做一轮性能测试和opcache参数调整,别装完直接扛线上流量。编译只是开始,线上的事还在后面。