신규 서비스 'order-api' 배포 D-1, 개발팀에서 급하게 연락이 왔습니다. "WAS에서 DB로 연결이 안 돼요. 로컬에서는 됐는데 서버에 올리니 타임아웃이 납니다." 원인을 추적하니 방화벽 정책이 없었습니다. 네트워크팀에 요청해야 하는데 어떤 정보를 줘야 하는지, 확인은 어떻게 해야 하는지 막막합니다.
이 모듈은 방화벽 오픈 요청서를 정확하게 작성하고, 오픈 후 직접 테스트하는 실무 절차를 익히는 것이 목표입니다.
- 1Inbound/Outbound 방화벽 정책의 방향과 기준을 설명할 수 있다
- 2방화벽 오픈 요청서 5가지 필수 항목을 빠짐없이 작성할 수 있다
- 3telnet, nc, curl로 방화벽 오픈 여부를 직접 테스트할 수 있다
- 4firewalld, iptables로 OS 레벨 방화벽 상태를 확인하고 설정할 수 있다
- 5포트는 열렸는데 연결이 안 되는 상황에서 바인딩 문제를 진단할 수 있다
방화벽 기본 개념
Stateful vs Stateless, 그리고 Zone 기반 정책
방화벽을 처음 다룰 때 가장 많이 혼란스러운 것이 방향입니다. "이 포트를 열어달라"고 요청할 때 Inbound인지 Outbound인지 틀리면 네트워크팀이 잘못된 정책을 설정합니다. 방향을 결정하기 전에 방화벽이 어떻게 동작하는지부터 이해해야 합니다.
확대
확대
Stateful vs Stateless:
| 구분 | 동작 | 실무 활용 |
|---|---|---|
| Stateful | TCP 연결 상태를 추적해 응답 트래픽 자동 허용 | 대부분의 상용 방화벽 (AWS Security Group, 방화벽 어플라이언스) |
| Stateless | 패킷 개별 검사, 응답도 별도 규칙 필요 | iptables 기본 설정, 라우터 ACL |
Stateful 방화벽은 Inbound 정책을 열면 그에 대한 응답(Outbound)은 자동으로 허용됩니다. AWS Security Group이 대표적입니다. 반면 iptables를 Stateless로 사용하면 양방향 모두 규칙을 추가해야 합니다.
Zone 기반 정책 (온프레미스 표준):
[인터넷]
↓
[외부 방화벽] — Untrust Zone (외부)
↓
[DMZ] — 공개 서비스 (웹서버, Reverse Proxy)
↓
[내부 방화벽] — Trust Zone (내부)
↓
[내부망] — WAS, DB, 배치 서버
Zone 간 트래픽은 기본 차단, 명시적 허용 규칙만 통과합니다. 방화벽 오픈 요청은 이 Zone 간 이동 경로를 기준으로 작성합니다.
Inbound vs Outbound — 실무 구분 기준
네트워크팀에 방화벽 오픈을 요청했는데 "방향이 잘못됐다"는 답변이 돌아왔습니다. WAS에서 DB로 연결해야 하는데 Inbound로 요청했는지 Outbound로 요청했는지 헷갈립니다. 방향을 틀리면 네트워크팀도 정책을 잘못 설정하고, 배포 당일에 "연결 안 된다"는 장애로 이어집니다.
방화벽 정책의 방향은 항상 "기준 서버(또는 방화벽) 관점에서" 정의합니다.
| 방향 | 트래픽 흐름 | 실무 예시 |
|---|---|---|
| Inbound | 외부 → 서버 (들어오는 트래픽) | 사용자 브라우저 → 웹서버 80/443, WAS → DB 3306 |
| Outbound | 서버 → 외부 (나가는 트래픽) | WAS → 외부 결제 API, 서버 → yum 저장소 80 |
[WAS 서버 10.10.1.5] → TCP 3306 → [DB 서버 10.10.3.10]
DB 서버 방화벽 관점:
Inbound: 출발지 10.10.1.5, 목적지 10.10.3.10:3306 — 허용 필요
WAS 서버 방화벽 관점:
Outbound: 출발지 10.10.1.5, 목적지 10.10.3.10:3306 — 허용 필요
Stateful 방화벽이라면 DB 서버의 Inbound 정책만 오픈하면 응답은 자동 허용됩니다. 온프레미스 환경에서는 보통 양방향 모두 명시적으로 요청해야 하는 경우가 많으므로, 요청서에 두 방향을 모두 기재하는 것이 안전합니다.
방화벽 오픈 요청서 작성
5가지 필수 항목과 실제 요청서 예시
"DB 서버 방화벽 열어주세요"라고 슬랙에 메시지를 보냈더니 "출발지 IP가 어디예요? 포트는요? 만료일은요?" 질문이 이어집니다. 필요한 정보를 빠뜨리면 추가 문의가 오가며 시간이 낭비되고, 오픈 범위가 불명확하면 보안 감사에서 지적됩니다. 요청서 작성 포맷을 익혀두면 이 왕복을 없앨 수 있습니다.
방화벽 오픈 요청서는 네트워크팀이 정책을 설정하는 기준 문서입니다. 5가지 항목이 빠지면 추가 문의가 오거나, 너무 넓은 범위로 설정되어 보안 문제가 생깁니다.
필수 5가지 항목:
| 항목 | 예시 | 주의사항 |
|---|---|---|
| 출발지 IP/대역 | 10.10.1.0/24 (WAS 서버 대역) | /32 단일 IP보다 대역(/24)으로 명시하면 서버 증설 시 추가 요청 불필요 |
| 목적지 IP/대역 | 10.10.3.10 (DB 서버 단일) | DB는 IP 고정이므로 /32 단일 IP 명시 |
| 포트/프로토콜 | TCP 3306 (MySQL) | UDP가 필요한 경우 별도 명시 (DNS=53/UDP, NTP=123/UDP) |
| 서비스 목적 | 신규 서비스 'order-api' DB 연결 | 구체적일수록 감사(Audit) 시 유리 |
| 요청 기한/만료일 | 2026-05-30 요청 / 만료: 없음(상시) | 임시 연결은 만료일 필수, 상시는 명시 |
실제 방화벽 오픈 요청서 예시:
방화벽 오픈 요청 — 요청자: 홍길동(order-api 개발팀), 요청일: 2026-05-30
| 항목 | 정책 1: WAS → DB | 정책 2: 모니터링 → WAS |
|---|---|---|
| 방향 | Inbound on DB / Outbound on WAS | Inbound on WAS |
| 출발지 | 10.10.1.0/24 (WAS 대역) | 10.10.5.20/32 (Prometheus) |
| 목적지 | 10.10.3.10/32 (MySQL Primary) | 10.10.1.0/24 (WAS 대역) |
| 포트 | TCP 3306 | TCP 8080 |
| 목적 | order-api DB 연결 | 메트릭 수집(/actuator/prometheus) |
| 만료 | 없음 (상시) | 없음 (상시) |
자주 사용하는 포트 레퍼런스:
| 포트 | 프로토콜 | 서비스 |
|---|---|---|
| 22 | TCP | SSH |
| 80 | TCP | HTTP |
| 443 | TCP | HTTPS |
| 8080 | TCP | HTTP Alt (WAS) |
| 3306 | TCP | MySQL |
| 5432 | TCP | PostgreSQL |
| 6379 | TCP | Redis |
| 27017 | TCP | MongoDB |
| 5672 | TCP | RabbitMQ |
| 9200 | TCP | Elasticsearch |
| 9092 | TCP | Kafka |
| 53 | UDP/TCP | DNS |
| 123 | UDP | NTP |
방화벽 테스트 방법
방화벽 오픈 요청을 보낸 후, 실제로 트래픽이 통하는지 직접 검증해야 합니다. nc(netcat)와 telnet은 TCP 연결 가능 여부를 빠르게 테스트하는 도구입니다. 방화벽 오픈 직후 첫 확인에 씁니다.
# nc (netcat) — 권장. -z: 연결만 테스트, -v: 상세 출력
nc -zv 10.10.3.10 3306
# 성공 시 출력:
# Connection to 10.10.3.10 3306 port [tcp/mysql] succeeded!
# 실패 시 출력:
# nc: connect to 10.10.3.10 port 3306 (tcp) failed: Connection refused
# 또는
# nc: connect to 10.10.3.10 port 3306 (tcp) timed out ← 방화벽 차단
# telnet — 구형 서버에도 대부분 설치됨
telnet 10.10.3.10 3306
# 성공: Connected to 10.10.3.10. / 실패: Connection refused 또는 타임아웃
# 여러 포트 한 번에 확인
for port in 3306 5432 6379; do
nc -zv 10.10.3.10 $port 2>&1 | grep -E "succeeded|failed|timed"
done
# HTTP 서비스 확인은 curl
curl -m 5 http://10.10.1.10:8080/health
# -m 5: 5초 타임아웃 (방화벽 차단이면 타임아웃으로 5초 기다림)
Connection refused vs 타임아웃 — 의미 차이:
| 결과 | 의미 |
|---|---|
| Connection refused | 포트에 도달했으나 서비스가 없음 (방화벽은 통과) |
| Connection timed out | 방화벽이 패킷을 DROP (응답 없음) |
| Connected / succeeded | 방화벽 통과 + 서비스 바인딩 확인 |
nc -zv 10.10.3.10 3306- nc -zv IP 포트 실행 후 출력 메시지 먼저 확인, 그 다음 HTTP 서비스는 curl -m 5 로 응답 코드 추가 확인 — nc가 TCP 연결만 확인하므로 HTTP 서비스는 curl로 애플리케이션 레벨까지 검증 필요
- nc 결과 기준: "succeeded"=TCP 포트 열림(방화벽 통과, 서비스 기동 중), "Connection refused"=방화벽은 통과했지만 서비스 미기동, 응답 없이 타임아웃=방화벽 차단 또는 호스트 자체 미응답 — 이 세 가지를 구분해야 조치 방향이 달라짐
- nc succeeded인데 애플리케이션 연결 실패하면 → 서비스는 포트를 열었지만 내부 에러 상태 — curl -v 로 HTTP 응답 코드 확인, 5xx이면 서비스 로그에서 에러 원인 추적
네트워크 방화벽(어플라이언스)과 별개로, 서버 OS에도 방화벽이 있습니다. 네트워크 방화벽을 열었어도 OS 방화벽이 차단하면 연결이 안 됩니다.
# -- firewalld (CentOS/RHEL 7+ 기본) --
# 현재 상태 확인
sudo firewall-cmd --list-all
# 출력 예시:
# public (active)
# target: default
# interfaces: eth0
# services: dhcpv6-client ssh
# ports: 8080/tcp
# 포트 영구 오픈 (--permanent 없으면 재시작 시 초기화)
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
# 포트 제거
sudo firewall-cmd --zone=public --remove-port=8080/tcp --permanent
sudo firewall-cmd --reload
# -- iptables (구형 서버, Ubuntu 등) --
# 현재 규칙 확인
sudo iptables -L -n --line-numbers
# INPUT 체인에 포트 허용 추가
sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
# 규칙 저장 (재시작 후에도 유지)
sudo service iptables save
# 또는
sudo iptables-save > /etc/sysconfig/iptables
# -- ufw (Ubuntu 기본) --
sudo ufw status verbose
sudo ufw allow 8080/tcp
sudo ufw deny 23/tcp
sudo firewall-cmd --list-all- firewall-cmd --list-all 로 ports 항목 먼저 확인, 그 다음 nc -zv 로 실제 연결 테스트 — firewall-cmd 출력에 포트가 있어도 firewall-cmd --reload 를 하지 않았으면 반영 안 됨
- --permanent 적용 기준: --permanent 없이 추가하면 현재 세션에만 적용(재시작 시 사라짐), --permanent 후 --reload 없으면 다음 재시작 시부터 적용 — 즉시 + 영구 적용하려면 두 옵션 모두 사용
- firewall-cmd에 포트가 있고 nc succeeded인데 재부팅 후 연결 안 되면 → --permanent 없이 추가한 것 — firewall-cmd --list-all --permanent 로 영구 설정 확인 후 누락이면 다시 --permanent로 추가
트러블슈팅
원인: nc는 TCP 포트 오픈만 확인합니다. DB가 127.0.0.1에만 바인딩되어 있으면 로컬에서는 접속되지만, 외부 IP로 오는 연결은 거부합니다.
# DB 서버에서 바인딩 주소 확인
sudo ss -tlnp | grep 3306
# 출력 예시 (문제 상황):
# LISTEN 0 128 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",...))
# ↑ 로컬호스트만 바인딩 — 외부 연결 불가
# 정상 상태:
# LISTEN 0 128 0.0.0.0:3306 0.0.0.0:* users:(("mysqld",...))
# MySQL 바인딩 주소 변경
sudo vim /etc/my.cnf
# [mysqld] 섹션에서:
# bind-address = 127.0.0.1 → bind-address = 0.0.0.0 으로 변경
sudo systemctl restart mysqld
# 변경 후 재확인
sudo ss -tlnp | grep 3306
DB를 0.0.0.0으로 열면 DB 서버의 모든 인터페이스에서 접근 가능해집니다. 반드시 DB 레벨 계정 권한(GRANT)도 함께 확인해 특정 WAS IP에서만 접속하도록 제한해야 합니다.
원인: 네트워크 방화벽 외에 OS 레벨 iptables/firewalld가 추가로 차단하고 있거나, SELinux 컨텍스트가 네트워크 접근을 제한하는 경우입니다.
# 1. OS 방화벽 상태 확인
sudo systemctl status firewalld
sudo firewall-cmd --list-all
# firewalld가 활성화되어 있다면 해당 포트 오픈 여부 확인
sudo firewall-cmd --query-port=3306/tcp
# no → 포트가 차단 중
# 2. iptables 직접 확인 (firewalld 우회해 iptables 규칙 직접 추가된 경우)
sudo iptables -L INPUT -n --line-numbers | grep 3306
# 3. SELinux 확인 (CentOS/RHEL)
sestatus
# SELinux status: enabled 이면 컨텍스트 확인
sudo ausearch -m avc -ts recent | grep 3306
# AVC denied 로그가 있으면 SELinux가 차단
# SELinux 포트 매핑 확인
sudo semanage port -l | grep mysqld_port_t
# permissive 전환으로 우회하지 말고 AVC 원인과 컨텍스트를 먼저 확인
sudo ausearch -m avc -ts recent | audit2why
sudo semanage port -l | grep mysqld_port_t
# 3306이 mysqld_port_t에 없다면 현재 다른 타입에 등록됐는지도 확인합니다.
sudo semanage port -l | grep -E '(^|[ ,])3306([ ,]|$)'
# 어떤 타입에도 없는 승인된 새 포트만 -a로 추가합니다. 아래 값은 실제 포트로 바꿉니다.
sudo semanage port -a -t mysqld_port_t -p tcp <승인된-새-포트>
# 이미 다른 타입에 등록된 포트를 옮겨야 한다면 영향 분석과 승인 후 -m을 사용합니다.
# sudo semanage port -m -t mysqld_port_t -p tcp <승인된-기존-포트>
# 적용 후 AVC 로그와 연결을 다시 검증합니다.
nc -zv target-ip 3306
심화 — 열려 있는데 끊긴다: 상태추적 테이블의 한계
심화: Stateful 방화벽의 속살 — conntrack 테이블이라는 유한 자원
Stateful 방화벽이 "정방향 한 줄만 열면 응답은 자동 허용"이라고 했습니다. 그 편리함이 공짜가 아니라는 것을 아는 순간이 장애 대응의 갈림길입니다. 방화벽은 통과시킨 모든 연결을 어딘가에 적어두기 때문에 응답을 알아볼 수 있고, 그 장부에는 크기 제한이 있습니다.
- 무엇을 기억하나: 리눅스·어플라이언스는 연결을 5-튜플(출발지 IP·포트, 목적지 IP·포트, 프로토콜)로 식별해 상태 테이블(리눅스는 conntrack)에 한 줄씩 올립니다. 응답 패킷이 오면 이 줄과 대조해 established로 인식하고 통과시킵니다.
- 각 줄은 타임아웃까지 남는다: 연결이 끝나도 즉시 지워지지 않고 상태별 타임아웃(established는 길게, TIME_WAIT류는 짧게)까지 테이블을 차지합니다. 그래서 짧은 연결이라도 폭증하면 순간 점유가 커집니다.
- 테이블은 유한하다: 리눅스는
nf_conntrack_max가 상한입니다. 여기에 닿으면 규칙이 아무리 허용이어도 새 연결을 위한 줄을 못 만들어 그냥 드랍하고 커널 로그에nf_conntrack: table full, dropping packet을 남깁니다. "규칙은 맞는데 간헐적으로만 끊기는" 전형적 원인입니다. - 규칙은 첫 매칭: 상태추적과 별개로, 정책은 위에서 아래로 첫 매칭에서 결정됩니다. 넓은 allow가 위에 있으면 아래의 좁은 deny가 영영 평가되지 않습니다 — 상태추적이 정상이어도 규칙 순서가 구멍을 만듭니다.
그래서 성숙한 운영자는 방화벽을 "규칙 목록"이 아니라 "규칙 + 상태 테이블"로 봅니다. 대량 단명 연결을 쏟아내는 헬스체크·스캔·배치는 규칙을 하나도 안 건드리고도 방화벽을 무력화할 수 있습니다.
상황: 정책 변경이 없었는데 트래픽이 몰리는 시간대에만 신규 접속이 산발적으로 timeout 납니다. 이미 맺어진 연결은 문제없이 돌아가고, 한산해지면 증상이 사라집니다. 규칙을 아무리 들여다봐도 허용은 정확합니다.
원인: 방화벽·게이트웨이의 연결 상태 테이블(conntrack)이 nf_conntrack_max 상한에 닿았습니다. 헬스체크·짧은 API 호출·포트스캔 같은 대량 단명 연결이 테이블을 채우면, 각 줄이 타임아웃까지 남아 있어 established뿐 아니라 TIME_WAIT 상태의 줄까지 자리를 차지합니다. 새 연결에 배정할 줄이 없으면 규칙이 허용이어도 커널이 패킷을 드랍합니다.
진단: 커널 로그에서 dmesg | grep -i conntrack로 table full 여부를 봅니다. sysctl net.netfilter.nf_conntrack_count와 net.netfilter.nf_conntrack_max를 비교해 사용량이 상한에 붙었는지, cat /proc/net/nf_conntrack | awk '{print $4}' | sort | uniq -c로 어떤 상태(특히 TIME_WAIT)가 많은지 분포를 확인합니다.
해결: 급하게는 nf_conntrack_max를 메모리 여유만큼 상향하고 TIME_WAIT 타임아웃(nf_conntrack_tcp_timeout_time_wait)을 줄여 회수를 빠르게 합니다. 근본 처방은 단명 연결 자체를 줄이는 것 — 클라이언트에 keep-alive·커넥션 풀을 적용해 연결을 재사용하고, 헬스체크 주기를 과하지 않게 잡습니다. 상태추적이 불필요한 대량 트래픽은 raw 테이블 NOTRACK으로 추적에서 제외하는 것도 방법입니다. 재발 방지로 conntrack 사용률을 모니터링 지표로 걸어 상한 도달 전에 감지합니다.
실제 업무에서 이 지식이 쓰이는 상황:
방화벽 오픈 요청은 단발성 작업이 아닙니다. 신규 서비스 배포, 외부 기관 API 연동, 모니터링 에이전트 추가, DR 환경 구성 등 매번 반복됩니다. 실무에서 이 절차를 빠르게 처리하는 사람이 배포 속도를 결정합니다.
전체 절차 요약 (현장에서 쓰는 루틴):
# 1. 요청서 작성 → 네트워크팀 전달 (슬랙/티켓)
# [필수] 출발지, 목적지, 포트/프로토콜, 목적, 만료일
# 2. 오픈 완료 확인 후 — 즉시 테스트
nc -zv db-server 3306
curl -m 5 http://was-server:8080/health
# 3. OS 방화벽도 확인 (네트워크 방화벽과 별개)
sudo firewall-cmd --list-all
sudo iptables -L -n | grep 3306
# 4. 애플리케이션 연결 테스트
# DB 연결이라면 직접 mysql client로 확인
mysql -h db-server -u appuser -p -e "SELECT 1"
자주 발생하는 커뮤니케이션 실수:
- 포트 번호 없이 "DB 서버 열어주세요" 요청 → 어떤 포트인지 모르므로 다시 문의
- 출발지를 단일 IP로 요청했다가 서버 증설 시 추가 요청 발생 → 대역(/24)으로 요청
- 만료일 없이 임시 연결 요청 → 감사 시 불필요한 정책으로 지적
명령어·단축키 빠른 참조
이 모듈에서 방화벽 오픈 확인과 OS 레벨 방화벽·바인딩 진단에 쓴 명령을 모았습니다. "예" 열의 조합을 그대로 써도 됩니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
nc -zv | TCP 포트 오픈 여부(방화벽 통과) 확인 | nc -zv 10.10.3.10 3306 (succeeded/refused/timeout 구분) |
telnet | 구형 서버에서 포트 연결 테스트 | telnet 10.10.3.10 3306 (Connected=통과) |
curl -m | HTTP 서비스까지 응답 확인(타임아웃 지정) | curl -m 5 http://was:8080/health |
firewall-cmd --list-all | firewalld 현재 존·포트 상태 확인 | sudo firewall-cmd --list-all |
firewall-cmd --add-port | 포트 영구 오픈 후 리로드 | --add-port=8080/tcp --permanent 뒤 --reload |
firewall-cmd --query-port | 특정 포트 오픈 여부 조회 | sudo firewall-cmd --query-port=3306/tcp |
iptables -L -n | iptables 규칙 확인(번호 포함) | sudo iptables -L INPUT -n --line-numbers | grep 3306 |
ufw | Ubuntu 방화벽 상태·허용·차단 | ufw status verbose / ufw allow 8080/tcp |
ss -tlnp | 서비스 바인딩 주소 확인(127.0.0.1 vs 0.0.0.0) | sudo ss -tlnp | grep 3306 |
ausearch / sestatus | SELinux 차단 여부 진단 | sudo ausearch -m avc -ts recent | grep 3306 |
semanage port -a | SELinux 포트 허용 등록 | semanage port -a -t mysqld_port_t -p tcp 3306 |
sysctl net.netfilter.nf_conntrack_* | conntrack 사용량·상한 비교(table full 진단) | sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max |
mysql -h | 방화벽 통과 후 DB 앱 연결 검증 | mysql -h db-server -u appuser -p -e "SELECT 1" |
관련 모듈로 더 깊이:
- Proxy/NAT 구조와 폐쇄망 외부 통신 설정 — 방화벽 너머 폐쇄망에서 Proxy/NAT로 외부 통신을 열어주는 구조
- L4/L7 로드밸런서와 VIP 기반 이중화 구성 — LB VIP로 들어오는 트래픽에 대한 방화벽 정책을 어떻게 설계하는지
- 계정 권한 관리, 보안 헤더, TLS 강화 실무 — 방화벽 정책을 보안 운영 관점에서 최소 권한으로 관리하는 법
다음 모듈에서는 폐쇄망 환경에서 Proxy와 NAT를 이용해 외부 기관과 통신하는 구조를 다룹니다.