리눅스 curl 명령어 사용법 - 옵션 정리와 API 테스트 실전 예제
이 명령어를 언제 쓰는가
배포 직후 정말 200 이 떨어지는지 확인할 때, API 가 이상한 응답을 준다고 프론트가 말할 때, 인증서 만료로 접속이 막혔는지 볼 때 curl을 친다. 브라우저는 캐시, 쿠키, 확장 프로그램이 끼어들어 진실을 가리지만 curl 은 요청 한 번을 그대로 보여준다.
서버 안에서 curl localhost:8080/health가 되는데 밖에서 안 되면 문제는 애플리케이션이 아니라 앞단(프록시, 방화벽, DNS)이다. 이 한 줄로 원인 범위가 절반으로 줄어든다. 실무에서 curl 은 다운로드 도구가 아니라 진단 도구다.
기본 형식
curl [옵션] URL
- URL — 프로토콜을 생략하면 http 로 간주한다.
?나&가 들어가면 반드시 따옴표로 감싼다. - 옵션 — 순서는 상관없지만, 값이 필요한 옵션은 바로 뒤에 값을 붙여야 한다.
- 옵션이 없으면 응답 본문만 표준출력으로 뱉는다. 헤더도, 상태코드도 보여주지 않는다.
자주 쓰는 옵션
| 옵션 | 의미 | 예시 |
|---|---|---|
-i | 응답 헤더와 본문을 함께 출력 | curl -i https://devin.kr/ |
-I | HEAD 요청. 헤더만 확인할 때 | curl -I https://devin.kr/ |
-s / -sS | 진행률 숨김 / 숨기되 에러는 표시 | curl -sS https://devin.kr/ |
-f | 4xx·5xx 면 종료코드 22 로 실패 처리 | curl -fsS https://devin.kr/ |
-L | 리다이렉트를 따라간다 | curl -sIL http://devin.kr/ |
-o 파일 / -O | 파일로 저장 / URL 파일명 그대로 저장 | curl -o app.tgz https://.../app.tgz |
-H '헤더' | 요청 헤더 추가. 여러 번 쓸 수 있다 | -H 'Content-Type: application/json' |
-d '본문' | POST 본문 전송(자동으로 POST 가 된다) | -d '{"id":"test"}' |
-X 메서드 | 메서드 지정(PUT, DELETE 등) | curl -X DELETE http://127.0.0.1:8080/api/v1/items/3 |
-u 아이디:비번 | 기본 인증 | curl -u admin:secret http://127.0.0.1:9200/ |
--connect-timeout / --max-time | 연결 대기 / 전체 시간 상한(초) | --connect-timeout 3 --max-time 10 |
--resolve | DNS 를 무시하고 특정 IP 로 접속 | --resolve devin.kr:443:10.0.1.20 |
-w '형식' | 완료 후 지표 출력(상태코드, 소요시간 등) | -w '%{http_code}\n' |
-v | 요청·응답 헤더와 TLS 협상 과정을 전부 출력 | curl -v https://devin.kr/ |
실전 예제
1. 상태코드만 뽑아 배포 검증에 쓴다. 본문은 버리고 숫자만 받아 스크립트 조건문에 넣는다.
curl -s -o /dev/null -w '%{http_code}\n' https://devin.kr/
200
2. JSON API 에 POST 를 던진다. 프론트가 400 을 받는다고 할 때, 같은 요청을 서버에서 직접 재현해 본다.
curl -sS -i -X POST http://127.0.0.1:8080/api/login \
-H 'Content-Type: application/json' \
-d '{"userId":"tester","password":"1234"}'
본문이 길면 파일로 빼서 -d @payload.json 으로 넘긴다. 셸 따옴표 지옥을 피할 수 있다.
3. 리다이렉트 사슬을 확인한다. http 가 https 로, www 가 non-www 로 제대로 넘어가는지는 SEO 와 직결된다.
curl -sIL http://devin.kr/ | grep -iE '^(HTTP/|location:)'
HTTP/1.1 301 Moved Permanently
location: https://devin.kr/
HTTP/2 200
grep 에 -i 를 붙인 이유가 있다. curl 은 HTTP/2 응답 헤더는 소문자로 정규화해 보여주지만 HTTP/1.1 응답은 서버가 보낸 그대로 찍기 때문에, 앞단 서버에 따라 첫 줄이 Location: 대문자로 나온다. -i 가 없으면 정작 확인하려던 리다이렉트 대상이 걸러진다.
4. DNS 를 바꾸기 전에 새 서버를 검증한다. 도메인은 그대로 두고 접속 대상 IP 만 갈아끼우는 방식이라, Host 헤더와 TLS SNI 가 실제 서비스와 동일하게 동작한다. hosts 파일을 건드리는 것보다 안전하다.
curl -sI --resolve devin.kr:443:10.0.1.20 https://devin.kr/
5. 어느 구간이 느린지 나눈다. 응답이 느리다는 신고를 DNS, TCP, TLS, 서버 처리로 쪼갠다.
curl -s -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' https://devin.kr/
dns=0.004512 connect=0.008901 tls=0.061233 ttfb=0.132877 total=0.133402
ttfb와 tls 의 차이가 크면 서버 처리 시간이고, dns가 크면 리졸버 문제다.
6. 인증서 만료일을 확인한다. 갱신 누락은 새벽에 터진다.
curl -sIv https://devin.kr/ 2>&1 | grep -i 'expire date'
함정과 주의점
-s 만 쓰면 에러가 통째로 사라진다. cron 에 curl -s를 넣어두면 DNS 실패든 연결 거부든 아무 소리 없이 넘어간다. 실무에서는 -sS를 짝으로 쓴다.
HTTP 500 을 받아도 curl 의 종료코드는 0 이다. curl 입장에서는 응답을 잘 받아온 것이기 때문이다. 배포 검증 스크립트에서 curl ... && echo OK 같은 식으로 쓰면 서버가 죽어도 OK 가 찍힌다. 반드시 -f를 붙이거나 -w '%{http_code}'로 숫자를 직접 비교한다.
URL 을 따옴표로 감싸지 않으면 잘린다. curl http://host/api?a=1&b=2 는 셸이 &를 백그라운드 실행으로 해석해 ?a=1 까지만 요청하고 b=2는 명령으로 실행하려 한다. 파라미터가 있으면 무조건 작은따옴표로 감싼다.
-k(인증서 검증 생략)를 습관으로 만들지 않는다. 한 번 쓰면 계속 쓰게 되고, 그러면 실제 인증서 문제를 영영 발견하지 못한다. 사설 인증서라면 --cacert 사내CA.pem 으로 제대로 검증하는 편이 낫다.
-X POST와 -L을 같이 쓸 때 조심한다. 301/302 를 따라갈 때 curl 은 원래 메서드를 유지하려 해서 의도치 않은 POST 재전송이 일어날 수 있다. 리다이렉트 뒤 GET 이 맞다면 -X를 빼고 -d만 쓴다.
함께 보면 좋은 명령어
wget— 파일을 받고 이어받는 용도에서는 curl 보다 손이 덜 간다.jq— curl 이 뱉은 JSON 을 사람이 읽을 형태로 정리하고 특정 필드만 뽑는다.ss— curl 이 연결 거부를 받았을 때, 서버 쪽에서 그 포트가 실제로 열려 있는지 확인한다.