说实话,curl 这个命令我几乎每天都在用,但直到最近帮几个朋友排查问题,才发现很多人对它的理解还停留在“下载个文件”这个层面。明明一条命令就能定位接口问题、复现请求、检查证书链、看响应耗时,结果愣是在浏览器 F12 和 Postman 之间来回倒腾半天。还有人在脚本里写死curl -k跳过证书校验,遇到线上报错又一脸懵。这篇东西不打算写成那种堆满参数表的官方文档,而是按我平时排查问题的思路来捋一遍 curl 的高频用法、进阶技巧和常见报错,希望你看完能少走点弯路。
1. 先搞清楚 curl 到底是个什么工具
1.1 一个命令走天下的底层逻辑
curl 全称是 Client URL,翻译成大白话就是“用命令行的方式发网络请求”。它支持的协议很多,日常开发最常用的就是 HTTP/HTTPS,但 FTP、SMTP、IMAP 这些它也都能碰。之所以说它是“底层工具”,是因为它不依赖任何图形界面,直接把请求发出去,再把响应原样打到终端里。这一点在今天浏览器、Postman、IDE 自带 HTTP 客户端满天飞的环境下,反而成了最大的优势——没有界面,所以更容易被脚本调用、被自动化任务集成,出问题时也更容易定位到底卡在哪一层。
很多人第一次接触 curl,是因为搜“Linux 下载文件命令”,结果发现curl -O就能把远程文件拉下来。这确实是 curl 的核心能力,但它的价值远不止下载。它本质上是一个“裸的 HTTP 客户端”,你可以完全控制请求方法、请求头、请求体、证书校验、重定向策略,甚至自定义 DNS 解析结果。这意味着什么?意味着前端联调时接口报错,你不需要拉产品经理、后端、运维一起在群里吵架,自己用 curl 模拟一发请求,就能判断问题出在参数、鉴权、网关还是服务端逻辑。
1.2 curl 和 wget:别再用错下载工具了
刚接触命令行的人经常问:curl 和 wget 到底有什么区别?我为什么不直接用 wget 下载?这个问题很有代表性。wget 的设计目标是“递归下载”,就是那种把整个网站镜像到本地的场景,它有非常强的目录抓取能力,而且断点续传做得比较好。而 curl 的设计目标是“和服务器交换数据”,它更强调对协议的控制力。打个比方,wget 像一个大货车,擅长整批整批地把货拉回家;curl 更像一个瑞士军刀,什么都带一点,单发请求、上传文件、调试协议、模拟客户端,样样都能来。
实际使用中,我下载文件反而更喜欢 curl,因为它默认行为更可控,-L是否跟随重定向、-o输出到哪个文件、-C -是否断点续传,全部显式控制,脚本里不会出幺蛾子。而 wget 下载一个文件时如果碰上服务器返回 302 跳转,默认还会留在原地址,有时候容易踩坑。所以我的习惯是:下载单个文件、调试 API 用 curl,做站点镜像、整站抓取用 wget,两边互补。
1.3 为什么每个开发者的工具箱里都得有 curl
这里分享一个我真实的排查经历。朋友在一个数据分析平台做后端,某天线上反馈某个接口偶发超时,他在服务器上用curl -v -w加了耗时统计,一条命令就把问题定位到了上游第三方服务响应慢,而不是自己服务的问题。整个过程不超过五分钟。当时我就感慨,如果他不熟悉 curl,可能得先登录监控平台、翻日志、查链路追踪,折腾一圈下来半小时起步。
另一个非常适合 curl 的场景是容器环境排查。很多时候你用docker exec进到容器里,发现里面既没有 Postman 也没有浏览器,甚至连 ping 都没有,但 curl 大概率是存在的。用 curl 去探测容器内服务的健康检查接口、检查环境变量是否生效、确认端口是否监听,几乎是运维排查的“标准动作”。
2. 高频参数逐个拆解:这些选项到底在干嘛
2.1 先掌握最常用的五个参数
我接触过很多把 curl 当“黑盒”用的人,参数全靠复制粘贴,报错就懵。其实高频参数并不多,我建议先把下面这五个吃透:
-o:把响应体写入指定文件,比如curl -o page.html http://example.com。注意这个参数是“把响应体保存到文件”,和-O(大写)不一样,-O是直接用 URL 末尾的文件名保存。-I:只取响应头,不取响应体。这个在调试时特别省流量,curl -I https://github.com一眼就能看到状态码、Server、Content-Type 这些关键信息。-s:静默模式,不显示进度条和错误信息。脚本里几乎必加,因为进度条会污染日志输出。但它会把错误信息也吞掉,所以排查问题时要配合-S一起用,-sS组合表示“不显示进度条,但保留错误信息”。-v:详细模式,把请求头、响应头、TLS 握手过程、DNS 解析过程全部打印出来。这是调试的神器,后面单独展开讲。-w:自定义输出格式,常用于统计耗时。比如curl -w "DNS解析: %{time_namelookup}s, 连接耗时: %{time_connect}s, 总耗时: %{time_total}s" -o /dev/null https://example.com。
这里特别说一下-v的输出怎么读。它会把发送给服务器的请求头以>开头的行打印出来,把服务器返回的响应头以<开头的行打印出来,中间的*开头的是 TLS 握手、DNS 解析这些过程信息。比如你看到一个* Connected to example.com,说明 TCP 连接已经建立;看到* SSL connection using TLSv1.3,说明 SSL/TLS 握手完成。多读几次-v输出,你对网络请求的整个链路会有一个非常直观的感知。
2.2 请求控制三件套:-X、-H、-d
讲完基础的五件套,接下来是控制请求的“三件套”,这三个参数几乎覆盖了日常接口联调的全部需求。
-X用于指定请求方法,比如curl -X POST https://api.example.com/users。不过需要注意,加-d时 curl 会自动变成 POST,所以很多时候不需要显式写-X POST。我见过有人写curl -X GET URL,这完全没必要,因为 curl 默认就是 GET。而且-X在某些场景会强制覆盖方法,比如配合-I时 HEAD 请求可能被覆盖掉,反而容易踩坑,所以我的习惯是“能用-d、-F隐式指定的方法就不显式写-X”。
-H用于添加请求头,一次可以带多个,比如-H "Content-Type: application/json" -H "Authorization: Bearer xxx"。这个参数在模拟认证请求、覆盖默认 User-Agent 时非常有用。有些服务会对非浏览器请求做限制,你用curl -H "User-Agent: Mozilla/5.0"就能绕过。-d用于携带请求体数据,最经典的是 POST JSON,写法是curl -X POST -H "Content-Type: application/json" -d '{"name":"curl","type":"tool"}' URL。注意-d默认发送的 Content-Type 是application/x-www-form-urlencoded,所以你传 JSON 时必须显式加-H指定类型。
2.3 调试和容错:-k、--location、--retry
这三个参数属于“进阶但极易踩坑”的类型。先说-k,它表示跳过 SSL 证书校验。很多人的第一反应是“这个参数好方便,一加上就不再报证书错误了”,但我要泼一盆冷水:它只应该在调试阶段使用,生产脚本里用-k等于把安全防线亲手拆了,中间人攻击、证书伪造全都不设防。正确做法是下载证书、用--cacert指定信任的根证书,或者至少用-k加个注释说明为什么临时跳过。
--location缩写是-L,表示跟随重定向。默认情况下,curl 遇到服务器返回 301/302 是不会自动跳转的,只会把跳转响应原样打印出来。如果你访问的 URL 做了跳转,比如http://跳到https://,必须加-L才能拿到最终的页面。但要注意,-L配合 POST 请求时,某些场景下 curl 会把 POST 改成 GET,这在 OAuth 之类的场景会出问题,需要配合--post301、--post302来保持方法。
--retry用于网络抖动时的自动重试,比如--retry 3 --retry-delay 5 --retry-all-errors。这个在脚本里非常实用,因为公网环境难免有瞬断,重试能显著提高任务成功率。不过重试次数和间隔要控制好,不然雪崩时你的重试就是补刀。
2.4 进阶:--resolve 自定义域名解析
这个参数我单独拎出来讲,是因为它实在太强了,但知道的人太少。--resolve的语法是--resolve 域名:端口:IP,作用是“请求这个域名时,不走系统 DNS,直接连指定 IP”。什么意思呢?比如你在切机房、验证负载均衡配置,或者服务器还在迁移 DNS 还没切换,你可以用curl --resolve api.example.com:443:192.168.1.10 https://api.example.com/v1/health,直接绕过 DNS 解析,把请求打到指定的那台机器上。
另一个典型用法是,本机 hosts 文件被改过了,但你不想动系统 hosts,就可以用--resolve临时覆盖。我之前排查 CDN 缓存是否生效时,也是用这个参数把域名强制解析到源站 IP,直接对比缓存节点和源站的响应头差异。一句话:这个参数是“临时改 hosts 的命令行版本”,而且比改 hosts 更灵活,因为它只对这次 curl 命令生效,不影响系统其他进程。
3. 实战场景:curl 在真实开发里怎么用
3.1 调试 REST API:从 Postman 导出到命令行复现
Postman 有个很方便的功能,就是把请求导出成 curl 命令。在 Postman 的请求面板里点 Code,选择 cURL,就能看到等价的命令行。但很多人导出之后只是看看,不会反过来用。其实这个功能最有价值的场景是“Bug 复现”:你在 Postman 里调某个接口是好的,但程序里调就报错,这时把 Postman 里的请求导出成 curl,在服务器上原样执行一遍,就能快速判断是代码的问题、网络的问题还是请求参数的问题。
反过来,我也经常把 curl 命令粘回 Postman。Postman 直接支持从 curl 导入,粘贴curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' URL,它会自动解析成图形化请求,连请求头都会帮你填好。这个来回切换的习惯非常高效,命令行适合快速验证和脚本化,图形界面适合构造复杂请求和保存历史记录,两者互补。
3.2 下载文件与一键安装脚本的幕后原理
很多人应该都见过这类命令:curl -fSSL https://example.com/install.sh | sh,一键安装某个软件。这个模式的三个参数值得拆解一下。-f表示失败时不输出 HTML 错误页面,直接返回非零退出码,防止把错误页面管道给 shell 执行;-S配合-s静默模式使用,出错时能输出错误信息;-L跟随重定向,有些安装包下载地址会先跳转到 CDN。三合一就是“静默下载、自动跟随跳转、出错即停”的稳健下载姿势。
为什么很多人觉得这个命令“安装进度太慢”?因为-s把进度条屏蔽了,终端上看起来像卡住了一样。解决办法是:去掉-s参数、加上--progress-bar查看进度,或者先单独用curl -fSSL -o install.sh URL把脚本下载下来,检查内容无误后再执行,这样更安全也更可控。顺便提醒一句,任何curl ... | sh的操作都等于把系统的最高权限交给了远程脚本,一定要确认来源可信、下载链路是 HTTPS,最好先下载到本地看一眼脚本内容再执行。
3.3 用 curl 看 SSE 实时数据流
SSE(Server-Sent Events)是服务端单向推送的一种实现方式,常用于实时通知、消息流、AI 回复等场景。很多人不知道 curl 可以直接用来观察 SSE 连接是否正常。SSE 的响应头是Content-Type: text/event-stream,响应体是一段一段的,用curl -N就能“实时”输出这些数据流。-N的作用是禁用缓冲区,让输出不被缓冲、及时刷新到终端。
我之前调试一个 AI 对话服务的流式输出,就是靠curl -N直接看原始 SSE 事件流,判断服务端是在逐步推送内容,还是憋了一大段一次性吐出来。这里有一个小技巧:加上--max-time给整个请求设置超时时间,防止连接一直挂着不结束。再配合-H "Accept: text/event-stream"显式声明你接受流式响应,基本就能模拟一个标准的 SSE 客户端了。
3.4 在 Shell 脚本里把 curl 变成自动化工具
curl 在脚本里的价值,体现在“把网络请求变成可编程的一等公民”。最常见的用法是用-f让请求失败时返回非零退出码,配合&&或if做条件判断。比如:
if curl -fsS -o /tmp/backup.sql "https://api.example.com/export"; then echo "备份下载成功" else echo "备份下载失败,退出码: $?" fi这里-f保证了 HTTP 4xx/5xx 状态码会被当作错误处理,而不是静默下载一个错误页面。另一个常用技巧是用-w配合-o /dev/null,只输出状态码和耗时,用于接口健康检查:
code=$(curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health) if [ "$code" = "200" ]; then echo "服务正常" fi还有一种场景是把 curl 的结果直接赋值给变量,配合jq做 JSON 解析。比如请求一个返回 JSON 的接口,然后从 JSON 里提取字段:
token=$(curl -s -X POST -H "Content-Type: application/json" \ -d '{"username":"admin","password":"secret"}' \ https://api.example.com/login | jq -r '.token')这种写法比用 Python、Node 写个脚本去发请求轻量得多,非常适合临时任务和运维脚本。
4. 常见报错逐条拆解:从 error 3 到 error 56
4.1 error 3:URL 格式出错,最常见的是端口号写错
curl: (3) URL rejected: Port number was not a decimal number between 0 and 65535这个报错很经典。意思就是 curl 解析 URL 时,发现端口号不是一个合法的十进制数字。常见原因是在 URL 里写了类似http://example.com:abc或者端口号带有中文标点、空格。我见过最离谱的是有人把 URL 从微信聊天记录里复制出来,冒号被自动变成了全角字符,结果 curl 怎么都解析不过。
排查方法很简单:先echo $URL看看 URL 变量里到底是什么样的,重点检查冒号、斜杠这些特殊字符是不是被转义或替换了。如果是脚本里拼的 URL,还要检查变量两侧有没有多余空格。另外,URL 里的端口范围是 0 到 65535,超过这个范围也会报同样的错。比如手滑写了80880,一眼看去像 8080 但其实就是超范围了。
4.2 error 7:连接被拒绝,先检查服务有没有监听
curl: (7) Failed to connect to 127.0.0.1 port 7897 after 0 ms: Connection refused这个报错太常见了。翻译一下就是“目标端口拒绝了 TCP 连接”,通常有几种原因:第一,目标服务器上根本没有服务监听这个端口;第二,服务在监听,但监听地址是127.0.0.1,只允许本机访问,外部访问就被拒了;第三,防火墙把端口拦了。
我的排查顺序是:先在目标机器上执行ss -lntp | grep 端口号,看端口是否处于 LISTEN 状态、监听地址是什么。如果没有输出,说明服务没起来;如果有输出但不是0.0.0.0或::,说明服务只监听了回环地址。确认端口在监听之后,再用curl telnet://目标IP:端口或者nc -vz做一次端口连通性测试,逐层定位。注意这类报错如果是连接公网服务出现,还要检查本机的防火墙和安全组配置。
4.3 error 35 和 error 56:TLS 握手与连接中断
先看error 35,常见的描述是Recv failure: Connection reset by peer,意思是 TLS 握手阶段连接被对方重置。这个情况在抓 HTTPS 接口时经常出现,原因可能是:客户端和服务端的 TLS 版本不兼容、服务器要求的 SNI 和客户端发的不一致,或者中间有人拦截了流量。快速判断方法是用-v看握手卡在哪一步,如果卡在Client hello之后,大概率是版本或证书问题,可以试着加--tlsv1.2强制指定版本。
再看error 56,常见的描述是Recv failure: Connection reset by peer或者GnuTLS recv error。和 error 35 不同,error 56 通常发生在 TLS 握手完成之后的 HTTP 请求/响应阶段,也就是说连接建立起来了,但在传输过程中被对端中断了。常见原因有:服务器主动断开空闲连接、文件下载到一半服务端崩了、中间设备(比如防火墙)做了连接超时清理。Git 在 clone 大仓库时经常报RPC failed; curl 56,其实就是这个原因。解决办法通常是关闭压缩、调大缓冲区,比如:
git config --global http.postBuffer 524288000 git config --global http.lowSpeedLimit 0 git config --global http.lowSpeedTime 999999这三个配置的意思是:把 HTTP 缓冲区调到 500MB、关闭低速限制,让 Git 在处理大仓库时不容易被中断。
4.4 git 场景下的 curl 报错
Git 在走 HTTPS 协议时,底层其实就是封装了 curl,所以你会看到很多 Git 报错里带了curl 56、curl 35这样的字眼。比如error: RPC failed; curl 56 GnuTLS recv error (-9): error decoding the received packet,这通常是在 clone 大仓库或推送大对象时出现的。原因多半是 HTTP/2 的 multiplexing 和某些代理设备不兼容。解决方法是强制 Git 使用 HTTP/1.1:
git config --global http.version HTTP/1.1另外,error: RPC failed; curl 56 schannel: server closed abruptly这种在 Windows 上更常见,因为 Windows 版 Git 默认走的是 Windows 的 schannel TLS 后端,有时和 GitLab/GitHub 的 TLS 配置配合不太好。解决办法是切回 OpenSSL 后端:
git config --global http.sslBackend openssl这两个配置我都在实际项目里验证过,能解决大部分 Git 走 HTTPS 协议时的 curl 报错。如果是公司内网的 Git 服务器,还可以检查是不是证书链不完整,用git config --global http.sslCAInfo /path/to/ca.pem指定内网 CA 证书。
4.5 常见错误速查表
| 错误码 | 典型信息 | 常见原因 | 优先排查方向 |
|---|---|---|---|
| error 3 | URL rejected: Port number... | URL 格式不对,端口号非法 | 检查 URL 里的冒号、斜杠、端口范围 |
| error 6 | Could not resolve host | DNS 解析失败 | 检查域名拼写、系统 DNS、hosts 文件 |
| error 7 | Failed to connect | TCP 连接被拒绝 | 检查端口监听、防火墙、服务状态 |
| error 28 | Operation timed out | 请求超时 | 检查网络连通性、服务端负载、--max-time |
| error 35 | Recv failure: Connection reset by peer | TLS 握手中断 | 检查 TLS 版本、SNI、中间设备 |
| error 56 | Recv failure: Connection reset by peer | 传输阶段连接中断 | 检查服务端稳定性、缓冲区、HTTP 版本 |
| error 60 | SSL certificate problem | 证书校验失败 | 检查证书链、过期时间、CA 配置 |
这张表格并不完整,但覆盖了日常开发和运维中最常见的几种。建议你把它截图存一下,下次报错直接对号入座。
5. 一些实操心得和避坑技巧
5.1 下载太慢怎么办
回到热词里那个“curl 安装脚本太慢怎么办”的问题。首先要明确,curl本身的下载速度取决于网络链路、服务器带宽和协议版本,不是调参数就能解决的。常规排查思路是加-v看请求都经过了哪些跳转,确认是不是被重定向到了某个速度很慢的节点。其次,很多下载慢是 DNS 解析到了不太好用的 IP,可以用--resolve手动指定一个已知较快的 IP 来验证是不是这个原因。再次,对于大型安装脚本,别直接用| sh,先下载到本地,用curl -C -断点续传,断了接着下,不浪费之前的流量。
我自己遇到这类问题时,还会用time_redirect、time_starttransfer这几个耗时指标定位瓶颈。比如curl -s -o /dev/null -w "重定向前耗时: %{time_redirect}s, 首字节耗时: %{time_starttransfer}s, 总耗时: %{time_total}s" URL,如果首字节耗时很长,说明网络往返或服务端处理慢;如果首字节快但总耗时很长,说明下载带宽受限。
5.2 安全使用 curl 的几个习惯
第一,不要在命令行里直接写账号密码。比如curl -u username:password URL,这条命令会出现在 shell history 里。正确做法是用-u username让它交互式输入密码,或者用~/.netrc文件,配合chmod 600控制权限。第二,谨慎使用-k跳过证书校验,尤其是涉及生产环境、敏感数据的请求,跳过了校验等于裸奔。第三,curl ... | sh这种模式要三思,你等于把机器的最高权限直接交给了远端脚本。如果一定要用,至少先下载到本地、查看脚本内容、确认逻辑无害再执行。
5.3 再补几个很实用的小技巧
最后分享几个我常在手边用的小技巧。第一个是curl ipinfo.io查出口 IP,配合-H "Accept: application/json"可以看到 IP 归属地、ASN 等详细信息,排查网络策略问题很好用。第二个是curl -v https://example.com/ 2>&1 | grep "SSL connection"快速确认 TLS 版本和加密套件,检查服务端是否还在用老旧的 TLSv1.0。第三个是curl -w "@curl-format.txt" URL配合模板文件,把耗时、状态码、下载大小全部按固定格式输出,适合做批量接口巡检。
还有一个比较冷门但很实用的:curl --limit-rate 200K URL,可以限制下载速率。在测试带宽控制策略、模拟弱网环境时,这个参数比在路由器上做限速方便多了。另外,--max-time和--connect-timeout一定要养成习惯加上,前者限制整个请求的最大耗时,后者限制建立连接的最大耗时,防止脚本因为某个请求卡死而挂机等待。
我在实际使用中最大的体会是:curl 的学习曲线其实不陡,但它的每个参数背后都对应着一个真实的网络问题。你把参数用熟了,等于把网络协议栈的那些概念也跟着过了一遍。与其遇到问题去搜“curl 报错”,不如花一个下午把-v的输出读明白,把常用的十来个参数组合练熟,往后再难缠的网络问题,你都会发现自己多了一把趁手的家伙。