curl命令行详解:从请求原理到错误码排查的实战指南
2026/9/18 19:09:40 网站建设 项目流程

如果你经常跟服务器打交道,恐怕没有哪一天能完全避开 curl。哪怕只是登录服务器敲两行命令,或者写个脚本去调一下接口,curl 总是那个甩不掉又最省事的家伙。它就是一个命令行下的网络请求工具,支持 HTTP、HTTPS、FTP 等几十种协议,能把 GET、POST、PUT、DELETE 这些请求原封不动地发出去,也能把服务器返回的内容老老实实地拉回来存成本地文件。

这些年我用 curl 的频率远高于任何图形化接口调试工具,原因很简单:任何一台 Linux 机器、macOS 都自带 curl,Windows 10 之后也内置了。只要你能敲命令行,就能用 curl 完成接口联调、文件下载、健康检查、数据抓取这些工作。这篇文章我尽量把日常最能派上用场的命令、参数、报错排查经验一次讲清楚,适合刚接触命令行的新手,也能帮老手省掉翻文档的时间。

1. curl到底是什么:核心概念与原理解析

1.1 一个命令行“快递员”的自我修养

先说个类比。你在浏览器里输入网址,浏览器会帮你把请求发出去,再把网页画出来;而 curl 干的是同一件事,只是它不做页面渲染,只把服务器返回的原始内容打到终端上。你可以把 curl 理解为一个“命令行快递员”:你告诉它快递单号(URL),它负责把请求包裹送到目的地,再把服务器的回执和货品原样拿回来。

curl 的历史可以追溯到 1997 年,最初只是作者 Daniel Stenberg 写的一个下载 FTP 文件的小工具,后来一路扩展成支持 HTTP、HTTPS、FTP、SFTP、SMTP、IMAP 这些主流协议的网络瑞士军刀。说它是“随系统自带的第一调试工具”一点不夸张,很多没有图形界面的服务器环境里,排查问题时第一个想到的就是 curl。

在开始前,可以在终端里先跑一下curl --version,你会看到当前版本号以及编译时支持的协议列表。不同发行版自带的 curl 版本可能不同,但常用参数基本都稳定了几十年,不用太纠结版本差异。

1.2 curl处理一个请求的完整流程

搞懂 curl 的底层流程,对你排查报错会有很大帮助。执行一条简单的curl http://example.com,实际经历了这几个阶段:

  1. URL 解析:curl 先分析你给的地址,确定协议类型(http://、https://、ftp:// 等)、主机名、端口、路径。
  2. DNS 解析:把主机名翻译成 IP 地址。这一步相当于“查通讯录”,如果查不到就会报错误码 6。
  3. TCP 三次握手:与目标服务器建立可靠的传输连接。
  4. TLS/SSL 握手:如果地址是 https:// 开头,还会进行加密协商和证书校验,这一步出事就会报错误码 35 或 60。
  5. 发送 HTTP 请求:把请求行、请求头、请求体按协议格式发给服务器。
  6. 接收响应:等待服务器返回响应头和响应体,然后原样输出到终端。
  7. 连接关闭:请求结束后按 Keep-Alive 策略关闭或复用连接。

理解这个顺序后你会发现,curl 报错其实是在告诉你“卡在了哪一步”:6 是 DNS 挂了,7 是 TCP 连不上,35/60 是 TLS 握手失败,22 是 HTTP 层被拒。后面第 4 章我会完整展开这些错误码的排查方法,这里先留个印象。

2. 高频命令第一梯队:日常开发最常用的参数组合

2.1 最简单的GET与“照着抄”的POST写法

最基本的用法就是一个 URL 打到底:

curl https://www.example.com

不加任何参数时,curl 默认发送 GET 请求,并把响应体打印到终端。但是真实开发场景里,POST 请求才是重头戏,尤其是 JSON 接口的联调。我经常看到有人把 POST 写得很啰嗦,其实核心只需要两点:指定请求头里的Content-Type,以及用-d传请求体。

curl -X POST https://api.example.com/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'

这里有个细节容易踩坑:-d默认会把请求方法从 GET 改成 POST,所以理论上不加-X POST也能工作。但当你遇到特殊情况,比如要发一个带请求体的 GET 请求,或者想用-X PUT覆盖默认动作时,-X就派上用场了。我的习惯是:常规 POST 不写-X,让-d自己决定;特殊方法如 PUT、DELETE 再用-X明确指定,避免命令行上发生行为冲突。

如果接口接收的是普通表单格式,写法更简单:

curl -X POST https://api.example.com/form \ -d "name=zhangsan&age=18"

-d会自动设置Content-Type: application/x-www-form-urlencoded,所以不需要手动加请求头。

2.2 请求头、Cookie与会话保持的实战组合

很多接口需要带鉴权信息或模拟登录态。带请求头用的是-H,可以重复出现多次:

curl https://api.example.com/user \ -H "Authorization: Bearer your_token_here" \ -H "Accept: application/json"

如果你想模拟浏览器访问,可以指定-A自定义 User-Agent;想直接带一个 Cookie 头,用-b "name=value"。但更常用的场景是两步登录:先请求登录接口,让服务器下发会话 Cookie,再带着这个 Cookie 请求业务接口。

第一步,登录并把返回的 Cookie 保存在本地文件:

curl -c cookies.txt -X POST https://api.example.com/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'

第二步,后续请求带上这个 Cookie 文件:

curl -b cookies.txt https://api.example.com/profile

-c是“写入 Cookie 到文件”,-b是“从文件读取 Cookie”,这两个参数配合起来,可以很轻松地在命令行里模拟一个完整的登录会话流程。很多内部系统的接口联调,我就是靠这一招几天都不用重新登录。

2.3 用 -w 输出状态码与请求耗时

日常调试时,光看到响应体还不够,我经常还需要确认 HTTP 状态码、DNS 耗时、总耗时这些信息。curl 的-w参数可以把这些变量打印出来,配合-o把响应体丢弃,只保留统计信息,是写脚本时的黄金组合:

curl -s -o /dev/null -w "HTTP状态码: %{http_code}\n总耗时: %{time_total}s\n" https://www.example.com

-o /dev/null的意思是响应体不写入文件也不打到终端(Linux 下/dev/null就是个黑洞),-w后面的字符串里可以嵌入 curl 支持的各种变量。常用的几个我整理在下面:

变量含义典型使用场景
%{http_code}HTTP 响应状态码,如 200、404判断接口是否成功
%{time_total}从开始到结束的总耗时(秒)评估接口性能
%{time_connect}TCP 建连耗时(秒)排查网络延迟
%{time_starttransfer}从开始到收到第一个字节的耗时(秒)看服务端处理速度
%{size_download}下载的字节数确认响应大小
%{url_effective}最终请求的 URL检查重定向结果

-w配合-o /dev/null后,你在脚本里就可以轻松写“如果状态码不是 200 就报警”这种逻辑了。说实话,我写的不少定时监控脚本,底层就是这一条命令再加一个 if 判断。

3. 进阶场景:下载、断点续传、SSE与调试输出

3.1 文件下载与上传的正确姿势

下载文件是 curl 的看家本领。最简单的是用-o指定保存文件名:

curl -o linux.tar.gz https://mirrors.example.com/linux.tar.gz

如果想要自动用远程文件名保存,就换成大写-O

curl -O https://mirrors.example.com/file.zip

实际运维中你经常会看到类似这样的命令:

curl -o /etc/yum.repos.d/centos-base.repo https://mirrors.aliyun.com/repo/Centos-7.repo

这就是利用-o把远程仓库配置文件直接写入指定目录,避免了先下载再手动移动的两步操作。同理,下载安装包、备份文件、拉取脚本配置时,-o都是最常用的选项。

如果下载一半断了,想续传怎么办?加-C -,curl 会自动检测本地已有文件大小,从断点处继续下载:

curl -C - -O https://mirrors.example.com/bigfile.zip

这个参数我强烈建议记下来,下载大文件时遇到断网,能省下从头再来的时间。上传文件则用-F模拟表单的 multipart 格式:

curl -X POST https://upload.example.com/upload \ -F "file=@./report.pdf"

-F的语法是字段名=@本地文件路径,服务端按普通表单文件字段接收即可。我常用它来快速往测试服务器传日志包,比开 SFTP 工具还快。

3.2 SSE流式响应,curl能不能吃?

很多人不知道 curl 也能处理 SSE(Server-Sent Events)这种流式响应。SSE 是服务器主动向客户端持续推送消息的技术,典型场景是 AI 聊天、实时通知、日志流。用 curl 调试 SSE 接口时,关键参数是-N

curl -N https://api.example.com/events

-N的全称是--no-buffer,作用是关闭 curl 的输出缓冲。这里解释一下为什么默认要缓冲:当 curl 的 stdout 不是终端而是管道或文件时,它会把收到的数据攒到一定量再输出,避免频繁写磁盘的开销;但这对于实时流式的场景就致命了,数据会积压在你眼前半天刷不出来。加上-N后,每条服务器推送到达时都会立即打印出来,调试 SSE 接口体验好了不止一个档次。

如果担心流式接口挂住不退出,可以配合--max-time设定一个观察窗口:

curl -N --max-time 30 https://api.example.com/events

这样 curl 最多运行 30 秒就自动结束,非常适合脚本里做短时探测。

3.3 调试三板斧:-v、-i、-s

排查接口问题时,我的第一步永远是加-v。它会完整打印请求行、请求头、TLS 握手信息、响应头,最后才是响应体:

curl -v https://api.example.com/login

输出里带>的表示发出的请求内容,带<的表示收到的响应内容。你能看到实际发给了服务器什么数据,这是定位“为什么接口说参数缺失”最直接的手段。

只想看响应头的话,用-i就能把响应头和响应体一起打印出来,比-v清爽一些。脚本里如果不想看到进度条和额外信息,加-s进入静默模式,它只输出响应体本身。但要注意:-s会把错误信息也吞掉,所以我一般用-sS组合,静默但保留错误提示,排查问题时不会一头雾水。

curl -sS -o /dev/null -w "%{http_code}\n" https://api.example.com/health

调试思路可以总结成一句话:先用-v看细节,再用-i看响应头,最后用-sS保持输出干净。三个阶段逐步收紧,问题基本都能定位。

4. 常见错误码排查手册:从35到60再到7的避坑实录

4.1 连接层故障怎么定位:错误码6和7

错误码 6 的完整提示是Could not resolve host,意思是 DNS 解析失败。域名拼错了,或者本机 DNS 配置有问题,都会触发它。排查思路很简单:先检查域名拼写,再ping 域名nslookup 域名手动验证解析是否正常。如果解析正常但 curl 依然报 6,那就要查看/etc/resolv.conf里的 DNS 服务器配置了。

错误码 7 的出现频率更高,提示是Failed to connect to host。TCP 层连不上。可能的原因很多:端口没开、服务没启动、防火墙拦截、IP 地址不通。我排查时按顺序走四步:

  1. 确认端口是否监听:ss -lntp | grep 8080(或netstat -lntp
  2. 确认服务状态:systemctl status 服务名
  3. 确认防火墙规则:iptables -L -nfirewall-cmd --list-all
  4. telnet 目标IP 端口nc -vz 目标IP 端口直测端口连通性

有一个经常被忽略的因素是环境变量代理。公司内网机器经常会在系统里设置http_proxyhttps_proxy,某些软件安装时会改动这些变量。之后的 curl 请求都走了代理,而代理本身又连不上目标地址,就会出现“本机明明能访问,curl 却报 7”的诡异现象。排查时可以临时清掉这些变量再试:

unset http_proxy https_proxy curl -v https://example.com

这个坑我踩过好几次,强烈建议所有遇到连接失败的人先做这一步。

4.2 TLS以及SSL证书问题:错误码35与60的组合拳

如果说错误码 7 是“连不上”,那错误码 35 和 60 就是“连上了但不敢确认对方身份”。这两个是日常开发里最常见的 HTTPS 报错。

错误码 60:证书验证失败。

提示一般是SSL certificate problem: unable to get local issuer certificatecertificate has expired。产生原因是服务器使用的 SSL 证书未通过 curl 内置 CA 证书库的校验——自签名证书、内网私有 CA 颁发的证书、证书链不完整、证书过期,都会触发 60。

遇到 60 时,按安全性从高到低有三种解法:

方案命令/操作适用场景
将证书加入系统信任库将 cer/crt 文件复制到/etc/pki/ca-trust/source/anchors/,执行update-ca-trust公司内部服务,希望长期信任
指定 CA 文件校验curl --cacert /path/to/ca.crt URL临时验证某个私有证书
跳过证书校验curl -k URL仅限本地测试,不推荐生产

-k是最后的手段,它等于告诉 curl “不管你是谁,我都信你”。这个参数对排查问题很快,但一旦用于生产环境,等于把 HTTPS 的加密保护完全放弃,中间人攻击风险极高。我自己的原则是:-k只出现在本地调试的临时命令里,任何写进脚本的命令都尽量走证书信任方案。

错误码 35:TLS/SSL 连接错误。

提示五花八门,Windows 上常见的有一句:

curl: (35) schannel: next InitializeSecurityContext failed: CRYPT_E_REVOCATION

这个报错说的是 Windows 自带的 Schannel 在检查服务器证书是否被吊销时,无法完成吊销状态查询。说白了就是系统想要确认这张证书有没有被提前作废,但查询不到吊销服务器。解决办法是在 curl 命令里加上--ssl-no-revoke,跳过吊销检查:

curl --ssl-no-revoke https://example.com

如果是 Linux 上出现 35,常见原因则是 TLS 版本不兼容。老服务端不支持新 TLS 协议时,可以用--tlsv1.2--tlsv1.1强制指定版本。还有一个经常被忽略的因素:系统时间不对。TLS 握手时客户端要校验证书有效期,如果本机时间差了好几年,证书校验必然失败。遇到 TLS 相关报错,第一件事看一下date输出。

4.3 超时与HTTP响应类的区分:错误码22和28

错误码 22 的提示是HTTP page not retrieved。它和前面几种错误本质不同——连接、TLS 全部正常,请求也发出去了,但服务器返回的 HTTP 状态码不是 2xx/3xx 范围内的“正常码”。简单说,就是服务端“给了个面子但拒绝了内容”。

要看到具体状态码,配合-w就行:

curl -sS -o /dev/null -w "%{http_code}\n" https://api.example.com

比如得到 404 是路径不对,得到 500 是服务端程序崩了,得到 401/403 是鉴权失败。错误码 22 只是个笼统的结果,真正要解决的是那个具体的 HTTP 状态码背后的业务问题。

错误码 28 是Operation timeout。curl 默认不设超时,如果对方一直不响应,它会一直等下去,这在脚本里非常危险。所以写脚本时要养成分开设置两个超时的习惯:--connect-timeout控制 TCP 连接建立的超时,--max-time控制整个请求的总超时。

curl --connect-timeout 5 --max-time 30 https://api.example.com

前者能快速失败,不会因为端口不通等半天;后者能兜底,防止响应体太大拖死脚本。建议所有自动化脚本里的 curl 都加上这两个参数,代价极低但收益巨大。

4.4 两个真实报错的排查全过程

整理错误码时,我特意把网上高频出现的两个案例拿出来完整走一遍排查流程,因为它们的典型性值得借鉴。

案例一:访问内网系统报 60

你可能会看到这样的报错:

curl https://esign.cqipu.edu.cn:9100/ curl: (60) SSL certificate problem: self signed certificate

这通常是学校、企业内部系统的常见问题:用自签名证书开启的 HTTPS 服务,curl 不认。我在排查时是这样处理的:先不带-k看完整错误信息,确认到底是self signed certificate还是unable to get local issuer certificate,这能帮助判断证书是自签名还是证书链缺失。然后把这个地址在浏览器里打开,点击地址栏的小锁图标,把证书导出为.cer.crt文件。最后把这个文件放入系统的 CA 信任目录并更新。

对于 Ubuntu/Debian 系统:

sudo cp mycert.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates

对于 CentOS/RHEL 系列:

sudo cp mycert.crt /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust

之后 curl 访问这个地址就不会再报 60。整个过程不需要-k,安全且一劳永逸。

案例二:报 35 且提示 TCP connection reset by peer

这个报错原文是:

curl: (35) TCP connection reset by peer

意思是 TLS 握手过程中,服务器把 TCP 连接直接重置了。常见原因有两个:一是 TLS 版本不兼容,比如服务器只支持 TLS 1.0,而你机器上的 curl 默认尝试 TLS 1.3;二是某些安全软件或防火墙策略主动重置了不支持加密套件的连接。排查时我先尝试锁定 TLS 版本:

curl --tlsv1.2 -v https://example.com

如果不行,再换--tlsv1.1甚至--tlsv1.0。如果版本怎么试都不行,再排查防火墙、安全组策略,或者换一台网络环境验证。这类问题在网络隔离严格的企业内网中尤其常见,换个网络往往就好了。

5. 提升效率的联动玩法:脚本、Postman与日常命令行

5.1 安装脚本里的那条“curl管道命令”是什么原理

现在很多开发工具、运行环境的官方安装文档里,都会给出一行类似这样的命令:

curl -fsSL https://example.com/install.sh | sh

很多新手第一次看到时是懵的。拆开来看,curl负责把远程脚本内容下载到标准输出,管道符|把输出转交给sh来执行。这里的几个参数也各有讲究:

  • -f:遇到 HTTP 错误时直接失败退出,避免把 404 页面当脚本执行。
  • -s:静默模式,不显示进度条。
  • -S:与-s一起用,保留真正的错误提示。
  • -L:自动跟随重定向,很多下载地址会先跳转一次。

所以-fsSL合起来的意思是“静默下载但保留错误提示,跟随重定向,下载失败绝不硬执行”。这套参数对官方脚本是安全的,但我要提醒一句:先下载到本地看一眼,再执行,永远是更稳妥的做法。尤其是从非官方网站拉脚本时,至少先执行:

curl -fsSL -o install.sh https://example.com/install.sh # 然后用 vim/less 查看 install.sh 内容,确认没有危险操作后再执行 bash install.sh

命令行管道虽然方便,但“下载即执行”意味着你完全信任了远端脚本的内容。多看一步,能避免很多安全风险。

5.2 Postman一键导出curl命令

团队协作时,最常遇到的场景是同事在 Postman 里调通了接口,但到了命令行环境却不知道该怎么复现。这时候 Postman 的“导出 curl”功能就非常实用。具体步骤是:

  1. 在 Postman 中构造好请求,点击Send确认能正常返回。
  2. 点击请求面板右侧的</>代码图标。
  3. 在弹出窗口左侧语言列表中选择cURL
  4. 点击Copy to Clipboard,复制整条 curl 命令。

粘贴到终端后,你就能直接得到一条完整的 curl 命令:

curl --location 'https://api.example.com/login' \ --header 'Content-Type: application/json' \ --data-raw '{"username":"admin","password":"123456"}'

注意 Postman 导出的命令里常带--location(等价于-L)以及多个--header,其中有些是 Postman 自动加上的模拟浏览器请求头,在命令行调试时删掉也无所谓。我一般会把它精简成-H-d两部分,可读性更好。这个技巧在给同事复现问题、写 Bug 报告时简直太方便了,直接丢一条命令过去,比截图和文字描述省事得多。

5.3 curl与jq、grep、vim的组合拳

curl 本身只负责“收发数据”,要让它融入日常命令行工作流,还需要几个搭档。第一个搭档是jq,专门用来解析 JSON 响应。直接把 curl 的输出管道给 jq,格式化和提取字段一步到位:

curl -s https://api.example.com/user | jq '.name'

第二个搭档是grep,适合从 HTML 或纯文本响应里过滤关键词:

curl -s https://www.example.com | grep -i "title"

第三个搭档是vim。当接口返回的是一大段 JSON 或 HTML,直接在终端里看很难受时,我会把响应保存到临时文件再用 vim 查看:

curl -s https://api.example.com/data > /tmp/response.json vim /tmp/response.json

最后分享一个我每天都在用的封装思路:在 shell 配置里(比如~/.bashrc~/.zshrc)写一个简易函数,把超时、静默、状态码输出这些常用参数组合起来,避免每次敲一长串参数:

mycurl() { curl --connect-timeout 5 --max-time 30 -sS \ -w "\n=== HTTP状态码: %{http_code} ===\n" "$@" }

保存后用source ~/.bashrc生效,后面的所有 curl 调用都用mycurl替代,脚本里处理异常就轻松多了。

我在实际工作中还经常用 curl 做一件事:写一个几行的巡检脚本,对多个服务的健康检查接口做轮询,状态码非 200 就输出告警。核心逻辑就是这一章讲的-w--connect-timeout--max-time的组合。说实话,很多东西都是从第一条 curl 命令开始长出来的,你把它用熟了,后面一整条自动化链路都会顺畅起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询