curl访问HTTPS地址,证书验证正常,最后却报"curl: (23) Failed writing received data"。先查输出路径,勿重签证书。下面用隔离实验拆开TLS、HTTP与写入验收。
一、先看23属于哪一层
curl官方将CURLE_WRITE_ERROR(23)定义为本地写接收数据或写回调失败。命令行先查输出路径、权限、文件系统和标准输出消费者;嵌入libcurl的应用还要查自己的回调,不能一律归为磁盘满。
23不是证书过期错误或HTTP状态。请求可通过证书验证、收到200,再因写入退出23;但不能仅凭23反推TLS通过,须联合日志。本轮未关闭验证。
二、先记录版本,再保留真正的退出码
本轮为Linux、curl 7.61.1及OpenSSL 1.1.1k后端,回环TLS 1.2、HTTP/1.1。旧构建仅作行为对照,不是部署建议;文案随版本变化,本机实际打印Failed writing body。
bash
curl -V
openssl version
以下各Shell块保存为独立Bash脚本运行,勿直接粘贴含exit的整段到登录Shell。example.com/file须替换为授权目标;实测改为随机回环端口并指定测试CA。生产使用授权CA,不用-k绕过验证。
bash
#!/usr/bin/env bash
# 在可写的诊断目录运行;example.com为占位地址
curl -q --noproxy '*' --http1.1 -sS \
--connect-timeout 5 --max-time 20 \
-o body.bin -w 'http=%{http_code} verify=%{ssl_verify_result}\n' \
https://example.com/file
rc=$?
printf 'curl_exit=%s\n' "$rc"
exit "$rc"
-q在首位只跳过默认curl配置,不清除代理或CA环境;--noproxy显式绕过代理,只适合允许直连的诊断。此段不加-f,为的是分别观察HTTP码和传输退出码。
rc紧接curl保存,否则可能读到打印命令的状态。verify仅描述所请求证书验证的结果,不证明内容正确或落盘持久,不能单看verify=0放行。
三、同一个HTTPS响应,只改变输出目标
隔离服务使用测试CA签发、含example.com SAN的证书,返回200和三个字节OK加换行。保持信任和逻辑URL一致,只改-o目标,另记服务端TLS完成和HTTP请求事件。

配图由DM-Fox辅助生成并经复核,仅示意本次回环对照。
| 输出条件 | 本轮结果 | 说明 |
|---|---|---|
| 可写普通文件 | 退出0,HTTP200 | 文件为预期三个字节 |
| 父目录不存在 | 退出23,HTTP200 | 无法打开目标文件 |
| 把目录当文件 | 退出23,HTTP200 | 目标类型错误 |
| 写Linux /dev/full | 退出23,HTTP200 | 模拟写入失败 |
| 改用无关CA | 退出60,无HTTP请求 | 这是信任验证失败 |
四个输出对照均有HTTP请求,错误CA负例没有。200不等于下载成功;未占满真实磁盘或修改系统信任、生产服务与时钟。
四、写失败先检查本地,不要反复换证书
先查-o完整路径与工作目录,相对路径不一定相对于脚本。缺父目录时按部署约定创建;本轮--create-dirs对照成功,但它不解决只读挂载、配额或已有目录权限。
用实际服务身份检查目录和文件;root试写不能证明普通用户有权限,不用chmod 777兜底。容量、inode、配额、只读挂载与安全策略分别检查,不能仅凭错误短句定因。
Linux的/dev/full用于可控写入失败,不该作为业务文件删除修复,也不代表磁盘真满。测试响应须有正文,零长度不覆盖同样路径。
bash
#!/usr/bin/env bash
# 仅Linux故障对照:/dev/full写入会失败,不占满真实磁盘
curl -q --noproxy '*' --http1.1 -sS \
--connect-timeout 5 --max-time 20 \
-o /dev/full https://example.com/file
rc=$?
printf 'curl_exit=%s\n' "$rc"
exit "$rc"
五、标准输出也是输出目标
不指定-o时通常写标准输出,下游提前关管道可能造成写失败;具体状态受构建、信号和消费者影响,不能保证都是23。本轮未实测断管,可先去掉管道改写临时文件定位。
区分curl打开-o失败和Shell启动curl前的重定向失败;后者可能根本没发请求,不能标成curl错误码。日志或目标文件存在不证明下载完成。
六、先下载到临时文件,再验收替换
直接覆盖可能留下截断文件,旧目标也可能冒充新结果。下例在可信、无并发写入的目录建临时文件,检查HTTP200和精确内容后替换。生产改用可信摘要、签名或业务格式校验。
bash
#!/usr/bin/env bash
# 独立脚本:当前目录须可信、可写,无并发发布者
set -u
umask 077
tmp=$(mktemp ./.download.XXXXXX) || exit 1
trap 'rm -f -- "$tmp"' EXIT
trap 'exit 1' HUP INT TERM
if http=$(curl -q --noproxy '*' --http1.1 -fsS \
--connect-timeout 5 --max-time 20 \
-o "$tmp" -w '%{http_code}' https://example.com/file); then
:
else
rc=$?
printf 'curl failed: %s\n' "$rc" >&2
exit "$rc"
fi
[[ "$http" == 200 ]] || exit 1
# 本例契约是精确三个字节:OK加换行;按真实业务替换
printf 'OK\n' | cmp -s - "$tmp" || exit 1
[[ ! -d verified.bin && ! -L verified.bin ]] || exit 1
mv -f -- "$tmp" verified.bin || exit 1
printf 'download verified\n'
最终HTML代码已提取重放:正确响应替换,错误内容、HTTP500、错误CA失败并保留旧目标,临时文件清理。不是生产更新事务验收。
同目录改名不等于断电持久化或多文件事务;并发须另加锁。强制杀进程或掉电不保证trap执行,残留另治理。本机不支持7.83.0新增的--remove-on-error,未冒称实测。
七、验收清单:四道门分别过
| 层次 | 放行依据 |
|---|---|
| TLS身份 | 正确名称与授权CA,验证未被关闭 |
| 传输及HTTP | curl退出0,状态符合业务约定 |
| 本地成品 | 新临时文件内容检查通过 |
| 发布替换 | 替换命令成功,失败保留旧目标 |
- 保存版本、退出码、stderr与脱敏URL,不上传Cookie、认证头或带凭据地址。
- 检查工作目录、输出类型、实际服务身份与文件系统限制。
- 至少跑一次正确CA和一次错误CA,确认没有把验证悄悄关闭。
- 至少跑一次输出失败和一次内容错误,确认脚本拒绝放行。
- 新文件验收通过后再交业务使用,不能用"200"代替完整结果。
八、参考资料与适用边界
证书验证像核对身份,写文件像放进柜子:身份没问题,柜门仍可能打不开。修23别拆安全检查。