开启cURL详细调试需同时设置CURLOPT_VERBOSE和CURLOPT_STDERR(如php://temp),否则无输出;CURLOPT_HEADER仅控制响应体是否含响应头,不影响调试日志;要确认实际POST内容,须在curl_exec前手动记录$data,因日志只显示“sent n bytes”而不明文展示body。
curl_setopt 开启详细调试输出PHP 的 cURL 扩展本身不直接打印请求/响应体,但可通过 CURLOPT_VERBOSE 和 CURLOPT_STDERR 把底层通信过程(包括请求头、响应头、重定向跳转)输出到指定位置。关键不是“看到 POST 数据”,而是让 cURL 把它自己发了什么、收到什么都吐出来。
常见错误是只设 CURLOPT_VERBOSE => true 却没配 CURLOPT_STDERR,结果什么也没看到——因为默认输出到 STDERR,而 CLI 下可能被忽略,Web 环境下更常被静默丢弃。
fopen('php://temp', 'w+') 或临时文件句柄赋给 CURLOPT_STDERR
CURLOPT_HEADER 设为 true 只影响响应体是否包含响应头,不影响调试日志http_build_query($data)),得在 curl_setopt($ch, CURLOPT_POSTFIELDS, $data) 前单独 var_dump($data) 或记录cURL 调试日志里不会明文显示 POST body 的完整内容(尤其二进制或含特殊字符时),

curl_exec 前把准备好的数据存下来并格式化输出。
例如你传的是关联数组:$post_data = ['name' => '张三', 'file' => new CURLFile('/tmp/a.jpg')],cURL 会自动编码成 multipart/form-data,但你无法从日志里读出边界符和字段顺序。
http_build_query($post_data) 输出再传入 CURLOPT_POSTFIELDS
CURLFile,改用字符串形式构造 multipart body(较重,仅调试必要时)curl_getinfo($ch, CURLINFO_REQUEST_SIZE) 推断内容——它不含 header 长度,也不反映编码后真实字节数curl_setopt_array 组合调试参数更稳妥单个 curl_setopt 容易漏掉配套设置,导致调试信息不全。比如开了 CURLOPT_VERBOSE 却没关 CURLOPT_HEADER,响应体混着头信息一起输出,反而难定位。
推荐一次性配置好调试所需全部选项,尤其注意 CURLOPT_RETURNTRANSFER 必须为 true,否则 curl_exec 直接输出到页面,和调试日志搅在一起。
curl_setopt_array($ch, [
CURLOPT_URL => 'https://httpbin.org/post',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_VERBOSE => true,
CURLOPT_STDERR => fopen('php://temp', 'w+'),
]);执行后用 fseek 和 stream_get_contents 读取 STDERR 内容,再和你的 $data 对照。
当 cURL 日志仍不足以还原问题(比如 SSL 握手失败、代理干扰、gzip 压缩异常),最直白的办法是起一个本地接收端,把请求原样打出来。
不用搭完整服务,一行命令即可:php -S localhost:8000 -t /dev/null 配合简单路由脚本,或直接用 nc -l 8000 抓原始 TCP 流。这时你把 POST 目标 URL 改成 http://localhost:8000,所有 header + body 就裸露在终端里。
CURLOPT_FOLLOWLOCATION,否则重定向会绕过你的监听端口
调试真正卡住的地方,往往不在 POST 数据本身,而在 cURL 没告诉你它悄悄改了什么——比如自动添加 Host 头、强制升级 HTTP/2、或因证书问题根本没发出去。盯住 STDERR 输出里的第一行 “* Trying …” 和最后的 “