본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 17. 18:56

curl 없이 HTTP 요청하기: Bash /dev/TCP 설명

요약

외부 도구인 curl이나 wget이 설치되지 않은 최소 환경에서 Bash의 /dev/tcp 기능을 사용하여 HTTP 요청을 보내는 방법을 설명합니다. Bash 내장 기능을 활용해 TCP 소켓 연결을 직접 제어하는 트릭을 다룹니다.

핵심 포인트

  • Bash의 /dev/tcp는 외부 도구 없이 TCP 소켓 연결을 가능하게 함
  • 최소화된 Docker 컨테이너나 보안 서버 디버깅 시 유용함
  • exec 명령어를 통해 양방향 파일 디스크립터를 생성하여 통신
  • 표준 Unix 기능이 아닌 Bash 전용 내장 기능임

curl 없이 HTTP 요청하기: Bash /dev/TCP 설명

Meta Description: Bash /dev/TCP를 사용하여 curl 없이 HTTP 요청을 보낼 수 있다는 사실을 알아두세요. 이는 별도의 도구 없이 모든 Linux 시스템에서 작동하는 내장 트릭입니다. 정확히 어떻게 작동하는지 설명합니다.

TL;DR: Bash에는 curl, wget 또는 기타 외부 도구를 설치하지 않고도 로우(raw) TCP 연결을 열고 HTTP 요청을 보낼 수 있게 해주는 숨겨진 초능력인 /dev/tcp/host/port가 있습니다. 이는 운영 환경(production)에서 curl을 대체하기 위한 것은 아니지만, 디버깅, 최소 환경(minimal environments), 그리고 팀원들을 놀라게 하기에 매우 유용한 트릭입니다.

이것이 생각보다 중요한 이유

이런 상황을 상상해 보세요. 패키지를 설치할 수 있는 권한이 전혀 없는 최소화된 Docker 컨테이너, 최소형 Alpine Linux 인스턴스, 또는 보안이 엄격한 서버에 SSH로 접속했습니다. API 엔드포인트가 살아있는지 확인해야 하거나, URL에서 구성 파일(configuration file)을 빠르게 가져와야 합니다. curl을 입력하니 command not found가 뜹니다. wget을 시도해 봐도 결과는 같습니다. 당황스러움이 밀려옵니다.

바로 이 지점에서 Bash의 /dev/TCP를 사용하여 curl 없이 HTTP 요청을 만드는 것이 구원투수가 됩니다. 이 기술은 Bash 버전 2.04부터 내장되어 왔으며, 이는 20년 넘게 눈에 띄지 않게 숨겨져 있었다는 것을 의미합니다. 대부분의 개발자는 이것이 절실히 필요한 순간이 오기 전까지는 이를 알지 못합니다.

지금 바로 그 문제를 해결해 봅시다.

Bash /dev/TCP란 무엇인가?

Bash에는 실제로는 Linux 커널의 일부가 아닌 특별한 의사 장치 파일 시스템(pseudo-device filesystem) 기능이 포함되어 있습니다. Bash 스크립트에서 /dev/tcp/hostname/port를 참조하면, Bash는 해당 경로를 가로채서 지정된 호스트 및 포트로 **TCP 소켓 연결 (TCP socket connection)**을 완전히 사용자 공간(userspace)에서 엽니다.

이것은 표준 Unix 기능이 아닌 Bash 전용 기능입니다. 기본적으로 sh, dash, 또는 zsh에서는 찾을 수 없습니다. 하지만 Bash는 대다수의 Linux 시스템에서 기본 쉘이며 (macOS에서도 사용 가능하므로), 거의 보편적으로 접근 가능합니다.

기본 구문

exec 3<>/dev/tcp/hostname/port

이 한 줄의 코드는 놀라운 일을 수행합니다:

  • **양방향 파일 디스크립터 (bidirectional file descriptor)**를 엽니다 (파일 디스크립터 3번, 하지만 3~9 사이의 어떤 숫자든 사용할 수 있습니다)
  • <>는 읽기(readable)와 쓰기(writable)가 모두 가능함을 의미합니다
  • 지정된 porthostname에 연결합니다

그다음부터는 Unix의 다른 파일들과 마찬가지로, 해당 파일 디스크립터에 HTTP 요청 데이터를 쓰고 응답을 다시 읽어올 수 있습니다.

curl 없이 만드는 첫 번째 HTTP 요청

가장 단순한 예시인 기본적인 GET 요청부터 시작해 보겠습니다.

#!/bin/bash

# example.com의 80번 포트로 TCP 연결을 엽니다
...

이 코드를 실행하면 헤더와 본문을 포함한 전체 HTTP 응답이 터미널에 출력되는 것을 볼 수 있습니다. 이는 외부 의존성 없이 수행되는 완전한 HTTP 트랜잭션(transaction)입니다.

HTTP 요청 분석하기

여기서 printf 라인이 핵심적인 역할을 수행합니다. 이를 해부해 보겠습니다:

  • GET / HTTP/1.1 — 요청 라인(request line): 메서드(method), 경로(path), HTTP 버전
  • \r\n — 캐리지 리턴(Carriage return) + 줄바꿈(newline) (단순한 \n이 아닌 HTTP 명세(spec)에서 요구하는 형식입니다)
  • Host: example.com — HTTP/1.1에서 필수적인 헤더(header)
  • Connection: close — 응답 후 서버가 연결을 닫도록 지시합니다 (스크립트 작성 시 매우 중요합니다)
  • \r\n\r\n — 헤더의 끝을 알리는 빈 줄

이 중 하나라도 누락되면 많은 서버가 유효한 요청을 기다리며 단순히 멈춰(hang) 있을 것입니다.

지금 바로 사용할 수 있는 실용적인 예시들

서비스 가동 여부 확인하기

#!/bin/bash

HOST="api.myservice.com"
...

이는 가장 실용적인 용도 중 하나입니다. 최소한의 컨테이너 환경에서도 작동하는 가벼운 상태 확인(health check) 도구입니다. curl도, netcat도, 그 무엇도 필요하지 않습니다.

HTTP 상태 코드만 가져오기

#!/bin/bash

exec 3<>/dev/tcp/httpbin.org/80
...

출력: Status: HTTP/1.1 200 OK

데이터를 포함한 POST 요청 보내기

#!/bin/bash

HOST="httpbin.org"
...

POST 요청은 Content-Length 헤더가 필요합니다. 이를 잘못 설정하면 서버가 멈추거나 요청을 거부할 것입니다.

응답 본문만 추출하기

#!/bin/bash

exec 3<>/dev/tcp/example.com/80
...

HTTPS 요청: 피할 수 없는 문제

여기서 우리는 솔직해질 필요가 있습니다: raw /dev/tcp는 HTTPS를 처리할 수 없습니다. TLS/SSL 암호화는 raw TCP보다 상위 계층에서 발생하며, Bash에는 내장된 TLS 지원 기능이 없습니다.

최소 환경(minimal environments)에서 HTTPS를 처리하기 위한 옵션은 다음과 같습니다:

접근 방식curl 없이 작동 여부?HTTPS 처리 가능 여부?복잡도
/dev/tcp (Bash)✅ 예❌ 아니요낮음
...

openssl을 사용할 수 있는 환경(curl이 없더라도 보통은 사용 가능합니다)에서 HTTPS를 처리하려면, openssl s_client를 통해 파이프(pipe)를 연결할 수 있습니다:

openssl s_client -quiet -connect api.example.com:443 2>/dev/null <<EOF
GET /endpoint HTTP/1.1
Host: api.example.com
...
EOF

curl보다 투박하지만, 작동은 합니다. 스크립트 내에서 본격적인 HTTPS 작업을 수행할 때는 curl이 여전히 표준(gold standard)입니다. curl은 TLS, 리다이렉트(redirects), 인증(authentication), 그리고 raw 소켓 스크립팅이 실수하기 쉬운 수백 가지의 예외 상황(edge cases)을 처리합니다.

/dev/UDP: 잊혀진 형제

이왕 하는 김에 덧붙이자면, Bash는 UDP 연결을 위한 /dev/udp/host/port도 지원합니다. 이는 다음과 같은 경우에 유용합니다:

  • syslog 메시지 전송
  • DNS 쿼리 (단, 응답을 파싱하는 과정은 매우 고통스럽습니다)
  • 간단한 UDP 서비스 체크
# UDP 패킷 전송 (fire and forget)
echo "log message" > /dev/udp/syslog-server/514

UDP는 비연결형(connectionless) 프로토콜이므로 읽을 응답이 없습니다. 단순히 특정 주소로 데이터를 쏘고 결과가 잘 되기를 바랄 뿐입니다.

실제로 언제 이것을 사용해야 할까요?

사용 사례에 대해 현실적으로 살펴봅시다. 이것은 니치(niche)한 도구이지만, 특정 시나리오에서 빛을 발합니다:

✅ 권장되는 사용 사례

  • 최소 환경의 컨테이너에서 디버깅할 때 — 패키지 매니저 접근 권한이 없는 Docker 이미지
  • CI/CD 파이프라인 상태 체크 — 추가적인 의존성(dependencies)을 전혀 원하지 않을 때
  • Bash 전용 환경 — 임베디드 시스템, 최소 사양의 VM, 복구 셸(rescue shells)
  • 빠른 포트 연결 테스트 — netcat을 설치하는 것보다 빠름
  • HTTP 작동 원리 학습 — 요청을 직접 손으로 만들어보는 것만큼 HTTP를 잘 가르쳐주는 것은 없습니다
  • 면접 시 시연 — 이를 본 적 없는 사람들에게 진정으로 깊은 인상을 남길 수 있음

❌ 권장되지 않는 사용 사례

  • 프로덕션 HTTP 클라이언트 — curl 또는 적절한 HTTP 라이브러리를 사용하세요
  • HTTPS 엔드포인트 — TLS를 지원하지 않습니다
  • 리디렉션 처리(Handling redirects) — 직접 구현해야 합니다
  • 복잡한 인증 — OAuth, bearer tokens 등은 금방 복잡해집니다
  • JSON 응답 파싱 — 여전히 jq 또는 유사 도구가 필요합니다 [INTERNAL_LINK: bash에서 JSON 파싱]

비교: /dev/tcp 대 실제 HTTP 도구

기능/dev/tcpcurlwget
설치 필요❌ 안 함✅ 함✅ 함
...
For basic GET/POST를 통제된 환경에서 처리하는 것을 넘어선 모든 작업에는 curl이 올바른 도구입니다. /dev/tcp 트릭은 일상적인 사용(daily driver)이라기보다는 생존 기술에 가깝습니다.

일반적인 문제 해결 (Troubleshooting Common Issues)

스크립트가 영원히 멈추는 경우 (The Script Hangs Forever)

원인: Connection: close 헤더가 누락되었거나, 서버가 청크 전송 인코딩(chunked transfer encoding)을 사용하고 있는 경우.

해결책: 항상 헤더에 Connection: close를 포함시키세요. 읽기 작업의 경우, 타임아웃을 추가하세요:

# Bash 내장 timeout을 사용하여 읽기 시간 초과 설정
read -t 5 -r response <&3

  • **Bash의 /dev/tcp/host/port**는 외부 도구 없이도 로우 TCP 연결 (raw TCP connections)을 여는 내장 기능입니다.
  • exec 3<>/dev/tcp/hostname/port 구문은 읽기와 쓰기를 위한 양방향 파일 디스크립터 (file descriptor)를 엽니다.
  • 적절하게 형식이 갖춰진 HTTP를 파일 디스크립터에 작성함으로써 GET, POST 등을 포함한 완전한 HTTP/1.1 요청을 보낼 수 있습니다.
  • HTTPS는 지원되지 않습니다 — 암호화된 연결을 위해서는 openssl이나 실제 HTTP 클라이언트가 필요합니다.
  • 상태 확인 (health checks), 최소 환경 (minimal environments), 그리고 학습용으로 사용하기에 가장 적합하며, 프로덕션용 HTTP 클라이언트로 사용해서는 안 됩니다.
  • 스크립트가 멈추는 것을 방지하기 위해 헤더에 항상 Connection: close를 포함하세요.
  • 이 기능은 Bash 전용이며 sh, dash 또는 다른 쉘에서는 작동하지 않습니다.
  • 본격적인 HTTP 작업을 위해서는 curlHTTPie가 훨씬 더 유능하고 신뢰할 수 있습니다.

추가 읽기 및 관련 기술

/dev/tcp를 마스터했다면, 다음의 관련 주제들이 여러분의 쉘 네트워킹 기술을 완성해 줄 것입니다:

  • [INTERNAL_LINK: 포트 스캐닝 및 디버깅을 위한 netcat]
  • [INTERNAL_LINK: bash 스크립팅 베스트 프랙티스]
  • [INTERNAL_LINK: docker 컨테이너 디버깅 기술]
  • [INTERNAL_LINK: 모든 개발자가 알아야 할 HTTP 헤더]

오늘 바로 사용해 보세요

다음에 최소 환경에서 빠른 연결 테스트가 필요할 때, 당황하지 말고 다음과 같이 입력하세요:

(echo >/dev/tcp/google.com/80) 2>/dev/null && echo "Connected!" || echo "Failed"

이 글을 북마크해 두세요. 이 트릭이 필요한 순간이 오면 정말 절실할 것이며, 지켜보는 사람들에게 마법사처럼 보일 것입니다.

자주 묻는 질문 (FAQ)

Q: /dev/tcp가 macOS에서도 작동하나요?
네, macOS에는 Bash가 포함되어 있습니다 (비록 최신 버전들은 zsh를 기본값으로 사용하지만). 스크립트를 #!/bin/bash로 호출하기만 한다면, macOS에서도 /dev/tcp는 잘 작동합니다. 참고로 macOS는 라이선스 문제로 인해 Bash 3.2 버전을 제공합니다. 이 기능은 여전히 작동하지만, 최상의 경험을 위해 Homebrew를 통해 Bash 5.x로 업그레이드하는 것을 권장합니다.

Q: /dev/tcp를 사용하는 것이 보안 위험이 되나요?
이 기능 자체가 본질적으로 위험한 것은 아니지만, 스크립트 내의 다른 네트워크 기능과 마찬가지로 오용될 수 있습니다. 일부 보안이 강화된 시스템(SELinux, 특정 컨테이너 정책)은 이를 의도적으로 비활성화합니다. 신뢰할 수 없는 환경에서 실행되는 스크립트의 경우, 연결하려는 모든 호스트 이름(hostname)이나 포트(port)를 검증하십시오.

Q: /dev/tcp를 사용하여 데이터베이스에 연결할 수 있나요?
기술적으로는 가능합니다. MySQL (3306), PostgreSQL (5432), 또는 Redis (6379)를 포함한 모든 포트에 TCP 연결을 열 수 있습니다. 하지만 이러한 프로토콜들은 HTTP보다 훨씬 더 복잡한 자체 바이너리(binary) 또는 텍스트 기반 핸드셰이크(handshake)를 가지고 있습니다. 데이터베이스에는 용도에 맞게 제작된 CLI 클라이언트를 사용하는 것이 좋습니다.

Q: 왜 제 셸 스크립트에서는 작동하지 않나요?
가장 흔한 원인은 #!/bin/bash 대신 #!/bin/sh를 사용하는 것입니다. /dev/tcp 기능은 Bash 전용입니다. Shebang 라인을 확인하고 echo $BASH_VERSION을 통해 실제로 Bash를 실행 중인지 확인하십시오.

Q: /dev/tcp와 netcat의 차이점은 무엇인가요?
둘 다 TCP 연결을 열지만, netcat (nc)은 반드시 설치되어 있어야 하는 외부 바이너리(binary)인 반면, /dev/tcp는 Bash 자체에 내장되어 있습니다. Netcat은 더 유연하며(UDP, 리스닝 모드(listening mode), 포트 스캐닝(port scanning) 지원), /dev/tcp는 의존성이 전혀 없습니다. 실제로 netcat을 사용할 수 있다면 사용하는 것이 더 쉬운 경우가 많지만, 다른 도구가 전혀 없는 상황에서는 /dev/tcp가 유리합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0