연결 문제는 안쪽부터 바깥쪽으로 확인합니다. 각 단계가 통과해야 다음으로 갑니다.
| 단계 | 질문 | 명령 |
|---|---|---|
| 1 | 앱이 포트를 듣는가 | ss -ltnp |
| 2 | 서버 안에서 응답하는가 | curl localhost:8080 |
| 3 | 방화벽이 여는가 | firewall-cmd --list-ports |
| 4 | 다른 서버에서 닿는가 | nc -zv 서버 8080 |
| 5 | 이름이 풀리는가 | getent hosts 이름 |
1·2 가 실패면 앱 문제, 3 이 실패면 방화벽, 4 만 실패면 중간 네트워크 장비, 5 가 실패면 DNS 입니다.
ss 는 netstat 의 후속으로 RHEL 기본입니다. 옵션 네 개를 묶어 외웁니다.
| 옵션 | 뜻 |
|---|---|
-l |
듣는 소켓만 |
-t |
TCP |
-n |
이름 대신 숫자 |
-p |
프로세스 표시 |
ss -ltnp | grep :8080 # LISTEN 0 128 *:8080 users:(("java",pid=1234))
ss -tnp state established | grep :1521 # DB 로 맺힌 연결
ss -s # TCP 요약, TIME-WAIT 수*:8080 이면 모든 IP 에서 받고 127.0.0.1:8080 이면 서버 안에서만 받습니다. 밖에서 안 열리는 흔한 원인이 이 바인드 주소입니다.
| 옵션 | 뜻 |
|---|---|
-s |
진행 표시 끔 |
-o /dev/null |
본문 버림 |
-w '%{http_code}' |
상태 코드 출력 |
-I |
헤더만 |
-f |
4xx·5xx 면 실패 코드 |
-m 5 |
전체 5초 제한 |
--connect-timeout 2 |
연결만 2초 제한 |
-v |
요청·응답 전부 |
curl -s -o /dev/null -w '%{http_code}\n' URL 이 스크립트에서 가장 많이 쓰는 형태입니다. 200 이면 정상, 000 이면 연결 자체가 안 된 것입니다.
-f 를 쓰면 HTTP 실패가 종료 코드로 나옵니다. 코드로 원인이 나뉩니다.
| 코드 | 뜻 | 원인 |
|---|---|---|
| 0 | 성공 | |
| 6 | 이름 못 풀음 | DNS |
| 7 | 연결 실패 | 포트 닫힘·방화벽 |
| 22 | HTTP 4xx·5xx | 앱 오류 |
| 28 | 타임아웃 | 느림·방화벽 DROP |
| 35·60 | TLS 실패 | 인증서 |
7 은 즉시 거부된 것이고 28 은 응답이 없는 것입니다. 방화벽이 REJECT 면 7, DROP 이면 28 이 나옵니다. Windows 의 Git Bash 는 거부도 28 로 나올 수 있습니다.
-w 로 단계별 시간을 뽑으면 느린 곳이 DNS 인지 연결인지 앱인지 갈립니다.
curl -s -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} first=%{time_starttransfer} total=%{time_total}\n' URLconnect 가 크면 네트워크, first 에서 connect 를 뺀 값이 크면 앱이 느린 것입니다. 실무에서는 이 한 줄을 스크립트로 두고 배포 전후에 비교합니다.
API 를 서버에서 직접 호출할 때의 형태입니다. 브라우저 없이 백엔드만 검증합니다.
curl -s -X POST -H 'Content-Type: application/json' -d '{"id":1}' URL
curl -s -H 'Authorization: Bearer TOKEN' URL
curl -sv URL 2>&1 | grep -E '^(> |< )' # 요청은 >, 응답은 <-v 출력은 표준 에러로 나오므로 2>&1 로 합쳐야 grep 이 됩니다. > 줄이 보낸 헤더, < 줄이 받은 헤더입니다.
상대 서버의 포트에 TCP 연결이 되는지만 봅니다. HTTP 가 아닌 DB·Redis 포트도 됩니다.
nc -zv db-prod 1521 # Connected 또는 Connection refused
timeout 3 bash -c '</dev/tcp/db-prod/1521' && echo open || echo closednc 가 없는 서버에서는 bash 내장 /dev/tcp 를 씁니다. timeout 3 을 앞에 붙여야 DROP 방화벽에서 무한히 기다리지 않습니다. telnet 은 대개 설치돼 있지 않습니다.
ip addr | grep 'inet ' # 내 IP
ip route | head -n 1 # 기본 게이트웨이
ping -c 3 10.0.0.5 # ICMP, 막는 망이 많아 실패해도 단정 못 함
traceroute -n 10.0.0.5 # 어느 홉에서 끊기나
getent hosts db-prod # /etc/hosts 와 DNS 순서로 풀기
dig +short db-prod # DNS 만
cat /etc/resolv.conf # 어느 DNS 서버를 쓰나getent 는 자바가 쓰는 것과 같은 순서로 이름을 풉니다. dig 는 되는데 getent 가 안 되면 /etc/hosts 나 nsswitch.conf 문제입니다.
RHEL 의 방화벽은 firewalld 이고 firewall-cmd 로 다룹니다. 영구 설정과 즉시 설정이 분리돼 있어 둘 다 해야 합니다.
sudo firewall-cmd --state # running
sudo firewall-cmd --zone=public --list-all # 열린 포트·서비스
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload # permanent 적용
sudo firewall-cmd --zone=public --query-port=8080/tcp # yes--permanent 없이 열면 재부팅 뒤 닫히고, --permanent 만 하고 --reload 를 안 하면 지금은 안 열립니다. 이 레슨의 firewall.sh 가 세 줄을 묶습니다.
외부로 나가는 요청은 프록시를 거칩니다. 셸 도구는 환경 변수, 자바는 시스템 속성으로 설정합니다.
export http_proxy=http://proxy.corp:3128
export https_proxy=http://proxy.corp:3128
export no_proxy=localhost,127.0.0.1,.corp,10.0.0.0/8java -Dhttp.proxyHost=proxy.corp -Dhttp.proxyPort=3128 \
-Dhttps.proxyHost=proxy.corp -Dhttps.proxyPort=3128 \
-Dhttp.nonProxyHosts='localhost|127.0.0.1|*.corp' -jar app.jarno_proxy 에 사내 도메인을 넣지 않으면 사내 DB 호출까지 프록시로 가서 실패합니다. 자바는 환경 변수를 안 읽으므로 -D 옵션이 따로 필요합니다.
폐쇄망 프록시나 사내 서버는 자체 서명 인증서를 씁니다. curl 은 시스템 저장소, 자바는 cacerts 에 CA 를 넣어야 TLS 오류가 사라집니다.
sudo cp corp-ca.crt /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust
keytool -importcert -alias corp-ca -file corp-ca.crt -cacerts -storepass changeit -noprompt
curl -v https://api.corp/ 2>&1 | grep -i 'SSL certificate verify'curl -k 는 검증을 끄는 것이라 진단에만 쓰고 스크립트에는 넣지 않습니다. 실무 확장 11 레슨의 CA 설정과 같은 내용입니다.
위 방법으로 안 잡히면 패킷을 봅니다. 개수 제한을 반드시 둡니다.
sudo tcpdump -i any port 8080 -c 20 -nn
sudo tcpdump -i any host 10.0.0.5 and port 1521 -c 10 -w /tmp/db.pcapSYN 만 나가고 SYN-ACK 이 없으면 중간에서 DROP 된 것입니다. -w 로 저장한 파일은 Wireshark 로 엽니다.