인바운드와 아웃바운드에 대해
인바운드와 아웃바운드는 기준점에 따라 달라진다
1. 인바운드/아웃바운드는 기준점의 문제다
혼동에서 시작합니다. 처음엔 이렇게 이해하고 있었습니다.
"아웃바운드 = 브라우저(외부)에서 우리 서버에 접속할 수 있게 하는 것"
그런데 정의를 찾아보면 반대로 적혀 있습니다. 유저가 서버에 접속하는 트래픽은 인바운드라고 합니다. 내가 아는 것과 정의가 반대인가 — 아닙니다. 둘 다 맞습니다. 같은 트래픽인데 서 있는 위치가 달라서 이름이 반대로 보이는 것입니다.
브라우저 ──── 요청 ────→ 회사 서버
브라우저 기준: 나가는 트래픽 = 아웃바운드 ← 내가 이해한 것 (맞음)
회사 서버 기준: 들어오는 트래픽 = 인바운드 ← 문서의 정의 (이것도 맞음)인바운드/아웃바운드는 트래픽의 절대적 속성이 아닙니다. 어느 장비의 네트워크 인터페이스를 기준으로 보느냐에 따라 같은 패킷이 아웃바운드도 되고 인바운드도 됩니다. 모순처럼 보였던 건 정의가 틀려서가 아니라, 나는 브라우저 쪽에 서서 봤고 문서는 서버 쪽에 서서 봤기 때문입니다.
그래서 기준점을 어디로 잡는가
규칙은 하나입니다.
방화벽 룰이 붙어 있는 장비가 기준점입니다. 즉 "내가 지금 설정하는(지키는) 장비" 기준.
- 회사 방화벽에 "유저가 우리 서비스에 접속하게 허용" → 방화벽 기준으로 들어오는 연결 = 인바운드 룰
- 서버가 외부 PG사 API를 호출하게 허용 → 서버 기준으로 나가는 연결 = 아웃바운드 룰
그리고 관용이 하나 얹힙니다. 인프라/서버 쪽 대화에서 기준점 없이 "인바운드/아웃바운드"라고 말하면 거의 항상 서버(지키는 쪽) 기준입니다. "인바운드 열어줘" = 외부에서 우리 서버로 들어오는 연결을 허용해달라는 뜻입니다. 유저 브라우저 기준으로 말하는 경우는 실무에 거의 없습니다 — 유저의 방화벽은 우리가 설정할 대상이 아니기 때문입니다.
경로 전체로 보면
유저가 회사 서비스에 요청을 보내는 경로에 이 기준점을 대입하면 이렇게 됩니다.
유저(외부) ── 요청 ──→ [회사 방화벽] ──→ 서버 ──→ nginx
아웃바운드 인바운드 인바운드
(유저 기준) (방화벽 기준) (서버 기준)- 유저 기준: 자기 인터페이스에서 나가는 패킷 = 아웃바운드
- 회사 방화벽 기준: 외부에서 들어오는 패킷 = 인바운드 — 여기서 첫 번째 심사
- 서버 기준: 자기 인터페이스로 들어오는 패킷 = 인바운드 — OS 방화벽이 두 번째 심사
- nginx: 방향 심사의 주체가 아니라 최종 수신 프로세스 — 심사를 통과한 패킷이 도달하는 곳
같은 요청 하나가 지나는 지점마다 다른 이름으로 불립니다. 응답은 정확히 역방향입니다 — 서버 기준 아웃바운드, 유저 기준 인바운드.
패킷에는 방향이 적혀 있지 않다
이게 기준점 문제가 되는 근본 이유입니다. 패킷 헤더에 들어 있는 건 출발지와 목적지뿐입니다.
src 203.0.113.5:51234 → dst 198.51.100.10:443"인바운드"라는 필드는 없습니다. 방향은 패킷의 속성이 아니라 관찰 지점의 속성입니다. 이 패킷이 유저의 NIC를 빠져나가는 순간엔 아웃바운드고, 회사 방화벽의 외부 인터페이스에 도착하는 순간엔 인바운드입니다. 헤더는 한 글자도 안 바뀌었는데 이름이 바뀝니다.
장비의 속성도 아니다
"서버니까 인바운드"도 틀린 모델입니다. 같은 서버가 외부 API를 호출하면 그 요청은 서버의 아웃바운드입니다. 결제 모듈이 PG사를 부르는 것, apt update가 미러 서버에 붙는 것, 전부 서버발 아웃바운드입니다.
즉 인바운드/아웃바운드는 장비의 속성도, 패킷의 속성도 아니고 (장비, 패킷) 쌍의 속성입니다. 방화벽 룰을 읽을 때 항상 두 가지를 먼저 확정해야 합니다 — 이 룰은 어느 장비에 붙어 있고, 어느 방향의 트래픽을 심사하는가.
TIP
용어 정리 ingress = 인바운드, egress = 아웃바운드. 클라우드 콘솔과 쿠버네티스 문서는 ingress/egress를 쓰고, 일반 문서는 inbound/outbound를 씁니다. 같은 개념입니다.
2. 패킷의 방향과 연결의 방향은 다르다
방향을 이야기할 때 실제로는 두 층위가 섞여 있습니다. 이 둘을 분리해야 뒤의 모든 내용이 풀립니다.
| 패킷의 방향 | 연결의 방향 | |
|---|---|---|
| 정의 | 개별 패킷이 인터페이스를 지나는 방향 | 누가 연결을 시작했는가 (TCP SYN) |
| 연결 하나당 | 양방향으로 다수 | 하나로 고정 |
| 바뀌는가 | 패킷마다 다름 | 연결이 끝날 때까지 불변 |
TCP 연결 하나에는 양방향 패킷이 흐릅니다. 유저가 서버에 요청을 보내고 응답을 받는 동안, 유저→서버 패킷(요청, ACK)과 서버→유저 패킷(응답, ACK)이 계속 오갑니다. 패킷 단위로 보면 방향이 수없이 바뀝니다.
하지만 연결의 방향은 하나입니다. 첫 SYN 패킷을 누가 보냈는가 — 유저가 보냈으면 이 연결은 끝까지 "유저가 시작한 연결"입니다. 서버가 응답 패킷을 아무리 보내도 연결의 방향은 안 바뀝니다.
방화벽 룰에서 말하는 인바운드/아웃바운드는 문맥에 따라 이 둘 중 하나를 가리키는데, 현대 방화벽에서 "인바운드 허용"은 대부분 연결 기준입니다.
"서버에 인바운드 443 허용"
= 외부에서 시작하는(외부발 SYN) 443 연결을 받아준다
≠ 들어오는 방향의 패킷을 하나하나 심사한다그래서 서버에 443 인바운드 룰 하나만 있으면, 그 연결로 나가는 응답 패킷(패킷 기준 아웃바운드)을 위한 별도 룰이 필요 없습니다. 연결 단위로 허용됐기 때문입니다.
일상 사례로 확인
이 구분이 잡히면 평소 경험이 설명됩니다.
- 브라우저로 웹 서핑할 때 집 공유기에 아무 포트도 안 열었는데 잘 된다 — 웹 서핑은 전부 내가 시작한 아웃바운드 연결이기 때문입니다. 응답 패킷은 들어오지만, 신규 인바운드 연결은 하나도 없습니다.
- 집에서 게임 서버나 NAS를 호스팅하면 포트포워딩이 필요하다 — 외부인이 접속하는 건 외부발 인바운드 연결이라, 공유기가 기본으로 막는 것을 명시적으로 열어야 합니다.
같은 공유기, 같은 유저인데 한쪽은 설정이 필요 없고 한쪽은 필요합니다. 차이는 딱 하나 — 연결을 누가 시작하는가입니다.
3. 방향에 따라 기본 정책이 다르다
방화벽의 기본 정책은 거의 항상 이렇습니다.
| 방향 | 기본 정책 | 이유 |
|---|---|---|
| 인바운드 | default-deny — 전부 차단, 허용한 것만 통과 | 외부의 누구든 시도할 수 있는 공격 표면 |
| 아웃바운드 | default-allow — 전부 허용 | 내부에서 시작한 요청은 신뢰된 주체의 행동으로 간주 |
인바운드가 깐깐한 이유는 단순합니다. 서버의 공인 IP는 전 세계 어디서든 도달 가능하고, 실제로 포트 스캔이 상시로 들어옵니다. 열려 있는 포트는 곧 공격 표면이라, 필요한 것만 명시적으로 여는 화이트리스트 방식이 기본이 됩니다. 전형적인 서버의 인바운드 룰은 이 정도로 짧습니다.
allow tcp/22 from <운영자 IP> # SSH — 그나마도 소스 제한
allow tcp/443 from any # 서비스 포트만 전체 공개
deny all # 나머지 전부 거절반대로 아웃바운드는 "내 장비 위의 프로세스가 밖으로 나가는 것"이라 일단 신뢰하고 허용합니다. 서버만이 아니라 개인 PC도 같습니다 — Windows 방화벽도 인바운드는 기본 차단, 아웃바운드는 기본 허용입니다. 이 기본값이 항상 옳은 건 아닌데, 그 이야기는 6장에서 합니다.
가정용 공유기의 NAT도 결과적으로 같은 방향성을 만듭니다. 나가는 연결은 설정 없이 되고, 들어오는 연결은 포트포워딩을 미리 정의해야 합니다. 방화벽은 정책 때문에, NAT는 주소 변환 구조 때문에 — 원인은 다른데 "나가는 건 쉽고 들어오는 건 어렵다"는 같은 결과가 나옵니다.
4. 그런데 응답은 어떻게 들어오나 — connection tracking
여기서 당연히 나와야 하는 질문이 있습니다.
아웃바운드로 요청을 보내면 응답은 인바운드로 돌아온다. 인바운드는 기본 차단이라면서, 왜
curl은 응답을 받는가?
이 질문에 답을 못 하면 방화벽 모델이 아직 안 잡힌 것입니다. 답은 2장의 구분에 이미 절반쯤 나와 있습니다 — 응답 패킷은 방향이 인바운드일 뿐, 인바운드 연결이 아닙니다. 그리고 이 구분을 실제로 구현하는 메커니즘이 stateful 방화벽의 connection tracking입니다.
방화벽은 연결을 기억한다
stateful 방화벽은 패킷을 개별로 심사하지 않고, 연결 단위로 상태 테이블을 유지합니다.
① 내부 → 외부 요청 (아웃바운드 연결, 허용)
방화벽이 상태 테이블에 기록:
(src 10.0.0.5:49512, dst 93.184.216.34:443, tcp, ESTABLISHED)
② 외부 → 내부 응답 (패킷 방향은 인바운드지만)
테이블에 있는 연결의 응답 → 통과 ← ESTABLISHED
③ 외부 → 내부 신규 연결 (테이블에 없음)
인바운드 룰 심사 → 매칭 없으면 차단 ← NEW즉 "인바운드 차단"의 정확한 의미는 들어오는 패킷 전부 차단이 아니라 **"내부에서 시작하지 않은 연결(NEW)의 차단"**입니다. 이미 수립된 연결(ESTABLISHED)의 응답 패킷은 방향이 인바운드여도 통과합니다.
리눅스에서 이 상태 테이블을 담당하는 게 conntrack이고, iptables 룰로는 이렇게 표현됩니다.
# 거의 모든 서버 방화벽의 첫 줄
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT # 신규 연결은 22번만
iptables -A INPUT -j DROP # 나머지 신규 연결 전부 차단첫 줄이 없으면 아웃바운드 요청의 응답까지 전부 막혀서, 서버가 apt update 하나 못 하는 상태가 됩니다.
임시 포트 — 응답이 돌아올 주소
②에서 응답이 정확히 어디로 돌아오는지도 짚어둘 만합니다. 클라이언트가 요청을 보낼 때 OS가 임시 포트(ephemeral port, 보통 32768–60999 범위) 를 하나 골라 소스 포트로 씁니다. 응답은 그 포트로 돌아오고, 방화벽은 상태 테이블에서 (목적지 IP, 임시 포트) 조합을 찾아 통과시킵니다.
stateless 방화벽에서는 이게 문제가 됩니다. AWS의 NACL이 대표적인 stateless인데, 연결을 기억하지 않으니 응답 트래픽용으로 임시 포트 범위 전체를 인바운드 허용해줘야 합니다. 보안 그룹(stateful)에서는 필요 없던 룰입니다.
| 보안 그룹 (stateful) | NACL (stateless) | |
|---|---|---|
| 아웃바운드 요청의 응답 | 자동 통과 | 임시 포트 범위 인바운드 허용 필요 |
| 룰 심사 단위 | 연결 | 패킷 개별 |
같은 "방화벽"이라는 이름이라도 stateful인지 stateless인지에 따라 써야 하는 룰이 다릅니다.
5. 방화벽은 한 겹이 아니다
"포트를 열었는데 왜 안 되지"의 답은 대부분 여기가 아닌 다른 겹에 있습니다. 패킷이 서버의 프로세스에 닿으려면 보통 세 겹을 통과합니다.
인터넷
│
▼ ① 경계 방화벽 (회사 방화벽 / GCP firewall rule / AWS Security Group)
│ 서버에 닿기 전, 인프라 계층에서 거름
▼ ② OS 방화벽 (ufw / iptables / firewalld)
│ 서버 안에서 거름
▼ ③ 프로세스 바인딩 (0.0.0.0 vs 127.0.0.1)
│ 방화벽이 아니지만 결과는 같음 — 루프백 바인딩이면 외부에서 도달 불가
▼
프로세스1장의 경로가 그대로 이 구조입니다. 회사 방화벽이 ①, 서버의 OS 방화벽이 ②, nginx가 어디에 바인딩돼 있느냐가 ③입니다.
③은 방화벽이 아닌데도 자주 범인입니다. ①②를 다 열어도 앱이 127.0.0.1에만 바인딩돼 있으면 외부에서 못 닿습니다. 프로세스가 "어느 인터페이스로 들어온 연결까지 받을 것인가"를 바인딩 주소로 정하기 때문입니다 — 127.0.0.1은 루프백으로 들어온 것만, 0.0.0.0은 모든 인터페이스로 들어온 것을 받습니다.
디버깅 순서도 이 겹 순서를 그대로 따르면 됩니다.
| 순서 | 확인 | 명령 |
|---|---|---|
| ③ 바인딩 | 프로세스가 어디에 리스닝 중인가 | 서버에서 ss -tlnp |
| ② OS 방화벽 | 서버 안 룰이 막고 있나 | sudo ufw status / sudo iptables -L -n |
| ① 경계 | 인프라 룰이 막고 있나 | 콘솔 / gcloud compute firewall-rules list |
안쪽(③)부터 확인하는 이유는 명령 한 줄로 즉시 판별되기 때문입니다. ss -tlnp에서 127.0.0.1:3100이 보이면 바깥 두 겹은 볼 필요도 없습니다.
TIP
서버 안에서 되는데 밖에서 안 되면
curl localhost:3100은 되는데 외부에서 안 되는 상황은 ③이 아니라 ①②의 문제입니다. 루프백 요청은 방화벽을 안 거치기 때문에, 이 두 결과의 차이가 그대로 "어느 겹이 막는가"를 좁혀줍니다.
여기에 축이 하나 더 얹힙니다. 각 겹의 인바운드 룰은 (소스 IP × 목적지 포트)의 2차원이라, "IP는 허용됐는데 포트가 스코프 밖"인 경우가 있습니다. 겹(어느 방화벽인가) × 축(IP인가 포트인가)을 둘 다 봐야 전체 그림이 나옵니다.
6. 아웃바운드도 막는 경우 — egress filtering
3장의 default-allow는 어디까지나 기본값입니다. 보안 수준이 높은 환경 — 기업 내부망, 금융망, 망분리 환경 — 은 아웃바운드도 화이트리스트로 운영합니다.
이유는 인바운드와 다릅니다. 인바운드 차단이 침입을 막는 것이라면, egress filtering은 침입 이후의 피해를 막는 것입니다.
- 탈취된 서버가 내부 데이터를 외부로 전송하는 것 차단
- 악성코드가 C2 서버와 통신하는 것 차단
- 내부 장비가 허가되지 않은 외부 서비스에 의존하는 것 차단
개발자 입장에서 이게 체감되는 순간은 명확합니다. 사내망에서는 되던 외부 API 호출이 서버에 올리면 안 되는 경우, 혹은 그 반대. npm install이 프록시 없이는 안 되는 환경. 이때 "내 코드 문제인가"부터 의심하면 오래 헤맵니다. 아웃바운드가 기본 허용이 아닌 환경인지부터 확인하는 게 빠릅니다.
# 아웃바운드가 열려 있는지 가장 빠른 확인
curl -sS -m 5 https://example.com -o /dev/null -w "%{http_code}\n"
# 타임아웃이면 egress 차단을 의심7. 정리
- 인바운드/아웃바운드는 기준점의 문제다. 패킷 헤더에 방향은 없다. 같은 패킷이 보내는 쪽에선 아웃바운드, 받는 쪽에선 인바운드. 기준점은 룰이 붙은 장비고, 기준점 없이 말하면 서버(지키는 쪽) 기준이 디폴트입니다.
- 패킷의 방향과 연결의 방향은 다르다. 연결 하나에 양방향 패킷이 흐르지만, 연결의 방향은 첫 SYN을 누가 보냈는가로 고정됩니다. 방화벽 룰의 "인바운드 허용"은 대부분 연결 기준입니다.
- 기본 정책은 비대칭이다. 인바운드 default-deny, 아웃바운드 default-allow. 인바운드는 공격 표면이고 아웃바운드는 신뢰된 주체의 행동으로 간주되기 때문입니다.
- "인바운드 차단"은 신규 연결의 차단이다. stateful 방화벽은 연결을 상태 테이블에 기억하고, 내부에서 시작한 연결의 응답은 패킷 방향이 인바운드여도 통과시킵니다.
- 방화벽은 세 겹이다. 경계 방화벽 → OS 방화벽 → 프로세스 바인딩. "포트 열었는데 안 됨"은 겹 하나를 빼먹은 경우가 대부분이고, 안쪽부터
ss -tlnp로 확인하는 게 빠릅니다. - 아웃바운드 차단도 존재한다. egress filtering은 침입 이후의 유출을 막는 정책이라, 사내망·서버 환경에서 외부 호출이 안 될 때는 코드보다 환경부터 의심할 것.
Comments