Windows Server 2012 R2 下 XAMPP PHP 8.3 的 curl 扩展修复实录

症状:php.ini 里开着的,扩展却始终不加载

环境是 Windows Server 2012 R2 + XAMPP + PHP 8.3.33。php.ini 里 extension=curl 前面的分号早就去掉了,Apache 也重启过很多次,但 phpinfo() 里始终搜不到 curl 那一块,extension_loaded('curl') 恒为 false。

直接后果是:调用微信接口、百度推送接口这类 HTTPS 请求时,PHP 只能降级到 stream 通道。虽然也能用,但心里始终不踏实。

这个现象最让人困惑的地方在于:没有任何报错。php.ini 里明确开着,重启也做了,页面上不显示红色错误,日志里也找不到 PHP Startup 相关的行。它就像被静默跳过了一样。

动手之前,先确认三件事

这三步能拦掉绝大多数"改了没生效"的假象,务必先做完再看后面。

第一件:确认 php.ini 到底是哪一份

Windows 下经常存在多份 php.ini,改错了文件,后面全白做。不要相信自己的记忆,让它自己报:

C:\xampp\php\php.exe -r "echo php_ini_loaded_file();"

它输出的那一个才是要改的。也可以建一个临时文件 <?php phpinfo();,看页面里 Loaded Configuration File 那一行——看完记得删掉这个文件。

我这次就在这里绕了弯路:诊断脚本一度把 PHP 目录读成 C:\php,而那个路径根本不存在,真正的路径是 C:\xampp\php。

第二件:确认改完之后是真的重启了

XAMPP 控制面板上点 Restart 有时并不会重新读取 php.ini。可靠做法是 Stop,等按钮完全变红、进程真的退出,再 Start。

第三件:确认 DLL 文件本身存在

dir C:\xampp\php\ext\php_curl.dll
dir C:\xampp\php\libcrypto-3-x64.dll
dir C:\xampp\php\libssl-3-x64.dll
dir C:\xampp\php\libssh2.dll

四个都应该列出文件。缺任何一个,先补齐再往下走。

四个流传最广的解法,为什么都对不上症状

网上搜"PHP curl 加载失败",高票答案集中在下面这四条。它们本身都是对的,但只在特定前提下成立,套到本次症状上全部失效。

误诊一:SSL CA 根证书缺失

最常见的说法是:Windows 下的 PHP 不会读取系统根证书,访问 HTTPS 会报 SSL certificate problem: unable to get local issuer certificate,解法是下载 cacert.pem 并配置:

curl.cainfo = "C:\xampp\php\extras\ssl\cacert.pem"

这条有个隐藏前提:curl 扩展必须先加载成功。 扩展都没起来,curl.cainfo 这一行对它完全无效——配置项读进去了,但没有 curl 去消费它。

这是典型的因果倒置:CA 证书解决的是"curl 能用但访问 HTTPS 报错",而不是"curl 压根加载不了"。两者的报错表现也完全不同,前者会明确出现 SSL certificate problem 字样,后者是静默失败。

不过这一行后来还是加上了,因为扩展修好之后它就变成必需项了。详见第七节。

误诊二:TLS 版本过低

另一个说法是 Windows Server 2012 R2 默认只启用 TLS 1.0/1.1,而现代站点要求 TLS 1.2 以上,导致握手失败。

这条同样只在 curl 已加载的前提下才有意义。而且实际检查系统,TLS 1.2 早就启用了,这条直接排除。

误诊三:缺 VC++ 运行库

PHP 8.x 用较新的 VS 工具链编译,老系统常缺运行库。这条看起来很有希望,因为命令行里跑 php.exe 确实报过类似提示。

但有一个简单的反证:PHP 本身跑得好好的,phpinfo 页面能正常打开。php8ts.dll 同样依赖运行库,它要是真缺,PHP 根本起不来。

诊断脚本后来报 vcruntime140_1.dll 缺失,但回看真实依赖列表,里面只有 vcruntime140.dll(已存在),压根没有那个文件——属于脚本多查了一项造成的误报。系统里确实装了多个 VC++ Redistributable,这条可以排除。

误诊四:extension_dir 路径错误

extension_dir = "C:\xampp\php\ext"

路径正确,DLL 也在,排除。

真因:Apache 和 PHP 不在同一个目录

前面四条全部排除后,只剩一个方向:DLL 依赖链断裂。但文件明明都在,那断在哪?

答案在于 Windows 加载 DLL 时的搜索顺序:

顺序 搜索位置
1 加载者(php_curl.dll)自身所在目录,即 ext\
2 可执行文件所在目录,即 C:\xampp\apache\bin\
3 System32
4 PATH 环境变量

注意这个关键错位:

php_curl.dll        → C:\xampp\php\ext\
libssl-3-x64.dll    → C:\xampp\php\          ← 依赖在这
httpd.exe           → C:\xampp\apache\bin\   ← 进程在这

php_curl.dll 被加载后要去找 libssl-3-x64.dll,它按上面的顺序找了一圈:ext\ 没有、apache\bin\ 没有、System32 没有、PATH 也没有。而那个文件其实就在 C:\xampp\php\——这个目录根本不在搜索列表里。

于是加载失败。而 PHP 对扩展加载失败的处理是静默跳过,既不在页面报错,也不写日志,所以看起来就是"配置开着却没生效"。

这也解释了为什么 XAMPP 官方包通常会在 apache\bin 里也放一份这些 DLL。

一条命令定性:CURL OK 还是 CURL FAIL

这是整个排查过程里最有价值的一步——它把"猜测"变成了"判定"。

cd /d C:\xampp\php
php.exe -n -d extension_dir="C:/xampp/php/ext" -d extension=curl -r "echo extension_loaded('curl')?'CURL OK':'CURL FAIL';"

这条命令绕开了 Apache,直接让 php.exe 加载 curl。而 php.exe 就在 C:\xampp\php——正是那些依赖 DLL 所在目录。如果它能加载成功,说明 DLL 本身全部完好。

两种结果对应完全不同的处理方向:

结果 说明 下一步
CURL OK DLL 全都是好的,纯粹是 Apache 找不到它们 复制到 apache\bin,见第六节
CURL FAIL 确实缺文件,命令行会直接打印出错的模块名 按报错补 DLL

本次实测输出 CURL OK,假设成立。至此从"猜问题"变成了"确定问题"。

三种修复方案

方案一:把 DLL 复制到 Apache 目录(最简单)

copy C:\xampp\php\libcrypto-3-x64.dll C:\xampp\apache\bin\
copy C:\xampp\php\libssl-3-x64.dll   C:\xampp\apache\bin\
copy C:\xampp\php\libssh2.dll        C:\xampp\apache\bin\
copy C:\xampp\php\brotlidec.dll      C:\xampp\apache\bin\
copy C:\xampp\php\nghttp2.dll        C:\xampp\apache\bin\

后两个是 php_curl.dll 的可选依赖(brotli 压缩、HTTP/2 支持),存在就一起复制,不存在可跳过。

php8ts.dll 不需要复制——Apache 加载 PHP 模块时已经从 C:\xampp\php 载入过了。

方案二:用 LoadFile 让 Apache 预加载(不改目录结构)

打开 C:\xampp\apache\conf\httpd.conf,在 LoadModule 那一堆附近加:

LoadFile "C:/xampp/php/libcrypto-3-x64.dll"
LoadFile "C:/xampp/php/libssl-3-x64.dll"
LoadFile "C:/xampp/php/libssh2.dll"

注意这里用的是正斜杠。适合不想往 Apache 目录塞文件的场景。

方案三:把 PHP 目录加进 PATH(影响面最大)

系统环境变量里加入 C:\xampp\php,重启服务器生效。这会让所有程序都能找到这些 DLL,副作用范围也最大,一般不推荐。

三种方案任选其一即可,不要同时用。本次用的是方案一。

加载成功后,还要补两行配置

扩展加载成功只是第一步。要让它正常访问 HTTPS,CA 证书那一行现在才真正起作用:

curl.cainfo = "C:\xampp\php\extras\ssl\cacert.pem"
openssl.cafile = "C:\xampp\php\extras\ssl\cacert.pem"

两行都要:

配置项 作用于
curl.cainfo curl 扩展
openssl.cafile stream / openssl 通道(file_get_contents 走 https 时用)

证书包从 https://curl.se/ca/cacert.pem 下载,放到 C:\xampp\php\extras\ssl\(没有 ssl 目录就新建)。

改完必须彻底重启 Apache,否则读到的还是旧配置。

如果命令行也返回 CURL FAIL

那就说明确实缺文件。命令行会直接打印出错的模块名,按这个名字搜:

dir /s C:\xampp\<缺失的模块名>.dll

搜到之后复制到 C:\xampp\php\ 和 C:\xampp\apache\bin\,再重启验证。

若提示的是运行库相关(如 vcruntime140_1.dll),去微软官网装 Visual C++ Redistributable for Visual Studio 2015–2022 x64,装完重启服务器(运行库要进系统,只重启 Apache 有时不够)。

实在修不好:用 stream 通道绕开

$ctx = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json; charset=utf-8\r\n", 'content' => json_encode($data, JSON_UNESCAPED_UNICODE), 'timeout' => 15, 'ignore_errors' => true, ], 'ssl' => [ 'verify_peer' => true, 'verify_peer_name' => true, ], ]); $res = file_get_contents($url, false, $ctx);

更稳妥的做法是让 WordPress 自己选:用 wp_remote_post(),它在 curl 不可用时会自动降级到 WP_Http_Streams。再补一层兜底——wp_remote_post 失败时改用 file_get_contents——基本就不会出问题了。

顺带说一句:如果你的服务器 curl 也是坏的,WordPress 后台的插件更新、主题更新、在线安装都会受影响,它们同样依赖 HTTP 层。修好 curl 之后这些也会跟着恢复正常。

避坑清单

坑 说明
改了 php.ini 却没生效 多半改的不是 php_ini_loaded_file() 报出来的那份
点了 Restart 以为重启了 必须 Stop 等变红再 Start,Restart 有时不重读配置
curl 没加载就配 curl.cainfo 顺序反了,此时这一行完全无效
看到 TLS 版本就往 TLS 上想 TLS 只在 curl 已加载后才相关
phpinfo 的 Build System 造成误导 显示 Server 2022 而系统是 2012 R2,看着像不兼容,实际只是编译环境,不代表跑不了
依赖扫描报一堆"缺失" 先确认脚本读的 PHP 目录对不对,路径错了就会全判缺失
复制 DLL 后没重启 Apache 已加载的进程不会重新找 DLL,必须彻底重启
诊断脚本用完没删 会暴露服务器绝对路径,务必删掉

排查顺序总结

下次再遇到类似问题,按这个顺序走,能少走很多弯路:

  1. 先确认扩展到底加载了没有。 这一条决定了后面所有配置有没有意义。
  2. 再确认 php.ini 是哪一份、有没有真重启。
  3. 然后用 CLI 那条命令定性——OK 说明 DLL 完好、是路径问题;FAIL 说明真缺文件。
  4. 最后再谈 CA 证书和 TLS。 它们是"能用但报错"层面的问题,不是"加载不了"层面的问题。

整个排查里最有用的一步,是那条绕开 Apache 直接测试的命令。它把一堆可能性收敛成了两个明确分支,避免了在 CA 证书、TLS、运行库这些方向上反复空转。

验证

C:\xampp\php\php.exe -m | findstr /i curl

输出 curl 即成功。也可以刷新 phpinfo 页面,此时应该能看到完整的 curl 信息块,以及 curl.cainfo 显示为你配置的路径。


发布日期:

所属分类: SEO 网站运营 标签:  



下一篇:

没有了,已经是最新文章