콘텐츠로 이동

OSI 모델 & Web Server

분류: Layer 2 - 인프라 기초 | 작성일: 2026-03-22

OSI 모델은 네트워크 통신을 7개 계층으로 나눈 개념 모델이고, Web Server(Nginx 등)는 HTTP 요청을 받아 정적 파일을 제공하거나 애플리케이션 서버로 요청을 전달하는 소프트웨어다.

네트워크 문제가 생겼을 때 “어느 계층에서 문제인가”를 판단하는 기준이 OSI 모델이다. Web Server는 Nest.js 앞에 Nginx가 붙어 있는 구성에서 요청이 어떻게 흘러들어오는지 이해하는 데 필요하다. “80포트로 들어온 요청이 3000번 포트의 Node.js 서버에 도달하는가”의 답이 여기에 있다.

비유로 시작 — “국제 택배”

편지(HTTP 요청)를 해외로 보낼 때 여러 단계가 있다: 편지 내용 작성(Application Layer) → 암호화 봉투(Presentation/TLS) → 주소 라벨 붙이기(Network/IP) → 택배 차량 탑재(Transport/TCP) → 실제 도로 이동(Physical). 각 단계는 독립적이라 상위 단계는 하위 단계가 어떻게 동작하는지 몰라도 된다.

OSI 7계층 — 원리와 실무 관점

📖 더 보기: Understanding OSI & TCP/IP with Real-World Examples - Medium — 실제 요청 흐름으로 OSI 계층 비유 설명

실제 네트워크 통신은 OSI보다 단순한 TCP/IP 4계층을 사용하지만, 문제 진단 시 OSI 7계층이 유용하다. “어느 계층에서 문제인가”를 좁히면 원인을 빠르게 찾을 수 있다.

계층이름실무 관련 예시문제 증상
7ApplicationHTTP, HTTPS, DNS4xx/5xx 에러, API 응답 이상
6PresentationTLS/SSL 암호화인증서 오류, HTTPS 핸드셰이크 실패
5Session세션 연결 유지연결이 갑자기 끊김
4TransportTCP/UDP, 포트 번호포트 미오픈, 방화벽 차단
3NetworkIP 주소, 라우팅IP 충돌, VPC 라우팅 오류
2Data LinkMAC 주소(실무에서 거의 안 만남)
1Physical케이블, Wi-Fi(클라우드 환경에서 AWS 책임)

실무에서 주로 보는 계층: 7(HTTP 에러) → 4(포트/방화벽) → 3(IP/VPC/Security Group) → 6(TLS 인증서)

왜 7계층으로 나누는가 — 설계 원리

각 계층이 독립적으로 설계된 덕분에 상위 계층은 하위 계층이 어떻게 동작하는지 몰라도 된다. 예를 들어, HTTP는 TCP 위에서 동작하지만 HTTP 코드를 작성할 때 TCP 3-way handshake를 신경 쓸 필요가 없다. Nginx도 마찬가지로 TLS 핸드셰이크(6계층)를 처리하면서 내부 Nest.js 서버에는 평문 HTTP(7계층)로 전달한다. 이 분리가 리버스 프록시의 핵심 원리다.

실제 HTTPS 요청의 전체 흐름

브라우저에서 https://api.example.com/users를 호출할 때 내부적으로:

1. DNS 조회: api.example.com → IP 주소 (예: 52.68.1.100) [7계층]
2. TCP 3-way Handshake [4계층]
클라이언트 → SYN → 서버
클라이언트 ← SYN-ACK ← 서버
클라이언트 → ACK → 서버
(연결 완료)
3. TLS Handshake [6계층]
→ 인증서 교환, 암호화 키 협상 (약 1~2 RTT 추가 지연)
4. HTTP 요청 전송 [7계층]
GET /users HTTP/1.1
Host: api.example.com
5. 서버 응답 → 브라우저 수신

프론트엔드 → 플랫폼 브릿지: fetch() 호출이 OSI 레이어를 어떻게 통과하는가

프론트엔드 코드에서 fetch('https://api.example.com/users')를 호출하면, 브라우저 내부에서 OSI 7계층을 모두 거치게 된다. 개발자는 JavaScript 레벨(7계층)만 보지만, 실제로는 아래처럼 모든 계층이 협력한다.

JavaScript 코드 (개발자가 작성)
fetch('https://api.example.com/users')
[Layer 7 — Application]
브라우저 HTTP 엔진: 요청 URL 파싱, 헤더 조립
GET /users HTTP/1.1
Host: api.example.com
Authorization: Bearer token...
[Layer 6 — Presentation / TLS]
TLS 라이브러리(BoringSSL): 요청 본문 암호화
인증서 검증, 세션 키 협상 (이미 연결이 있으면 세션 재사용)
[Layer 5 — Session]
브라우저 연결 풀: 같은 origin(api.example.com:443)에 대한
기존 TCP 연결이 있으면 재사용 (HTTP Keep-Alive)
→ 새 연결이면 3-way handshake 후 TLS handshake 수행
[Layer 4 — Transport / TCP]
TCP 세그먼트화: 큰 요청 데이터를 MSS(1460B) 단위로 분할
시퀀스 번호 부여, ACK 대기
Source Port: 55123 (브라우저가 임의 선택)
Dest Port: 443
[Layer 3 — Network / IP]
IP 패킷: Source IP(클라이언트), Dest IP(52.68.1.100)
라우터가 목적지 IP를 보고 다음 홉 결정
[Layer 2 — Data Link]
Ethernet 프레임: MAC 주소(게이트웨이/공유기)
[Layer 1 — Physical]
실제 신호: Wi-Fi 전파 또는 UTP 케이블 전기 신호

핵심 포인트: 개발자가 fetch()를 호출하고 await response.json()을 받는 사이에, 브라우저는 보이지 않는 곳에서 TCP 3-way handshake → TLS handshake → HTTP 요청 전송 → TCP 수신 확인 → TLS 복호화의 전체 과정을 수행한다. curl -v로 이 과정을 직접 볼 수 있다.

Terminal window
# fetch()와 동일한 요청을 curl로 시뮬레이션 (OSI 레이어 동작 관찰)
curl -v https://api.example.com/users
# 예상 출력 (각 줄이 어느 OSI 계층인지 주석 추가):
# * Trying 52.68.1.100:443... ← Layer 3: IP 주소로 연결 시도
# * Connected to api.example.com port 443 ← Layer 4: TCP 연결 완료 (3-way handshake)
# * TLSv1.3 (OUT), TLS handshake... ← Layer 6: TLS 협상 시작
# * SSL certificate verify ok. ← Layer 6: 인증서 검증 성공
# > GET /users HTTP/2 ← Layer 7: HTTP 요청 전송
# > Host: api.example.com
# > Authorization: Bearer ...
# < HTTP/2 200 ← Layer 7: 서버 응답 수신
# < content-type: application/json

📖 더 보기: What Happens When You Type a URL — Medium/HackerNoon — 브라우저 URL 입력부터 응답까지 OSI 레이어별 전체 흐름 설명 (입문)

TCP vs UDP

  • TCP: 연결 확인 후 전송, 순서 보장, 신뢰성 높음 → HTTP, DB 연결, SSH
  • UDP: 연결 없이 전송, 빠르지만 손실 가능 → DNS 쿼리(초기), 동영상 스트리밍, WebRTC

Web Server (Nginx) — 리버스 프록시 원리

📖 더 보기: NGINX Reverse Proxy 공식 문서 — proxy_pass, 업스트림 설정 공식 가이드

Nginx가 리버스 프록시로 동작할 때의 핵심은 이벤트 기반 비동기 아키텍처다. Apache처럼 요청마다 스레드를 생성하지 않고, 하나의 마스터 프로세스가 여러 워커 프로세스를 관리하며 각 워커가 수천 개의 연결을 동시에 처리한다. 이 설계 덕분에 메모리 효율이 높고 C10K(동시 1만 연결) 문제를 해결할 수 있다.

Node.js도 싱글 스레드 이벤트 루프로 동시 요청을 처리하는 것처럼, Nginx 워커 프로세스도 비슷한 원리로 수천 개의 연결을 비동기 처리한다. 차이는 Nginx는 정적 파일 서빙·SSL 처리·프록시 같은 네트워크 I/O에 특화되어 있고, Node.js는 JavaScript 코드 실행이 추가된다는 점이다. 두 컴포넌트 모두 “스레드를 블로킹하지 않는다”는 원칙 위에서 동작하므로, Nginx(앞단) + Node.js(뒷단) 조합이 고성능을 낼 수 있다.

클라이언트 ──HTTPS(443)──→ Nginx ──HTTP(3000)──→ Nest.js
├─ SSL 인증서 처리 (클라이언트 ↔ Nginx 구간만 암호화)
├─ 요청 헤더 추가: X-Real-IP, X-Forwarded-For
├─ 로드밸런싱: upstream 서버 여러 개로 분배
└─ 정적 파일은 직접 서빙 (Node.js 부하 감소)

Nginx 설정 핵심 예시:

server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/cert.pem; # TLS 인증서
ssl_certificate_key /etc/ssl/key.pem;
location / {
proxy_pass http://localhost:3000; # Nest.js로 전달
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 60s; # 업스트림 응답 대기 시간
}
# 정적 파일은 Nginx가 직접 처리 (Node.js 부하 감소)
location /static/ {
root /var/www;
expires 30d;
}
}

HTTP/2 — 성능 개선과 Nginx 설정

HTTP/1.1에서는 요청마다 새 TCP 연결을 열거나 순차적으로 처리해 헤드-오브-라인 블로킹이 발생한다. HTTP/2는 하나의 TCP 연결로 여러 요청을 동시에 처리(멀티플렉싱)하므로 지연 시간이 크게 줄어든다. Nginx에서 HTTP/2를 활성화하면 클라이언트 ↔ Nginx 구간에서 이 혜택을 받을 수 있다.

server {
# ✅ HTTP/2 활성화 — SSL과 함께 사용 필요
listen 443 ssl;
http2 on; # Nginx 1.25.1+ 방식 (구버전은 listen 443 ssl http2;)
# TLS 1.3 + HTTP/2 조합으로 최적 성능
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Keep-alive 설정 — 연결 재사용으로 오버헤드 감소
keepalive_timeout 65;
keepalive_requests 100;
location / {
proxy_pass http://localhost:3000;
# Nginx 1.29.4+ (2025.12)부터 upstream HTTP/2 프록시 지원
# gRPC 또는 소규모 응답이 많은 백엔드에서 멀티플렉싱 효과 있음
# 대부분의 REST API 환경에서는 HTTP/1.1로도 충분
proxy_http_version 1.1;
proxy_set_header Connection ""; # Keep-alive upstream
}
}

ALB → ECS Fargate 환경에서는 ALB가 이미 HTTP/2를 지원하므로 별도 Nginx 설정 없이 자동 적용된다. ALB Target Group 프로토콜 버전을 HTTP2로 설정하면 ALB → 컨테이너 구간도 HTTP/2로 동작한다.

ECS Fargate 환경의 실제 흐름

📖 더 보기: AWS ALB 공식 문서 - Target Groups — ECS Fargate 환경에서 ALB 설정 이해

인터넷
↓ HTTPS (443)
ALB (Application Load Balancer) ← Layer 7에서 동작 (HTTP 헤더 기반 라우팅 가능)
│ ← SSL 인증서 처리 (ACM 인증서)
│ ← Health Check로 정상 컨테이너만 라우팅
↓ HTTP (3000) — Target Group
Nest.js 컨테이너 (Fargate Task)
※ Fargate 환경에서는 ALB가 Nginx 역할을 대신함
※ Security Group으로 ALB → 컨테이너 포트만 허용
※ ALB는 경로 기반 라우팅 가능: /api/* → Nest.js, /static/* → S3
  • 네트워크 장애 발생 시 계층별 원인 찾아내기 (“3계층 문제인가, 7계층 문제인가”)
  • Nginx 설정 파일 읽기/수정 (리버스 프록시, 타임아웃 설정)
  • HTTPS 인증서 적용 위치 이해
  • 로컬 개발 환경에서 포트 충돌 디버깅
  • ALB Health Check 실패 원인 분석 및 /health 엔드포인트 구현

BackOps 실무 시나리오

  • 배포 후 ECS Task가 계속 재시작됨 → Health Check 실패 → /health 엔드포인트 없거나, Security Group이 ALB → 컨테이너 포트 막음
  • “HTTPS로 접속하는데 인증서 오류” → 6계층 문제, 인증서 도메인·만료일 확인 (openssl s_client)
  • 특정 API만 504 응답 → Nginx proxy_read_timeout 설정 확인, 근본적으로는 무거운 작업을 Queue로 분리
  • “로컬에서는 되는데 배포하면 안 됨” → Security Group이 해당 포트 열려있는지 확인 (4계층 문제)
  • 서비스 앞에 Nginx가 있는지, ALB만 있는지 파악
  • HTTPS 인증서가 어디서 처리되는지 이해
  • 네트워크 에러 발생 시 계층 기반으로 원인 찾아내기
개념 A개념 B차이점
Web ServerWAS(Web Application Server)Web Server는 정적 파일/프록시, WAS는 비즈니스 로직 실행 (Node.js는 WAS)
리버스 프록시포워드 프록시리버스는 서버 앞에서 요청 분배, 포워드는 클라이언트 대신 요청
NginxApache둘 다 Web Server. Nginx는 비동기/논블로킹으로 고성능, Apache는 프로세스 기반
ALBNginxALB는 AWS 관리형 로드밸런서, Nginx는 직접 운영하는 Web Server
HTTP/1.1HTTP/2HTTP/1.1은 요청 순차 처리, HTTP/2는 하나의 연결로 동시 처리(멀티플렉싱)

6.5 선택 매트릭스 — Nginx vs ALB vs Envoy

섹션 제목: “6.5 선택 매트릭스 — Nginx vs ALB vs Envoy”

세 가지 모두 리버스 프록시/로드밸런서 역할을 할 수 있지만 적합한 상황이 다르다. “다 가능하다”가 아니라 어떤 조건이면 무엇이 최선인가를 판단해야 한다.

판단 기준NginxALB (AWS)Envoy
운영 환경온프레미스 또는 EC2 직접 운영AWS 클라우드 (ECS/EKS/Lambda)마이크로서비스 메시 (Kubernetes)
관리 복잡도높음 (직접 설치·설정·업그레이드)낮음 (AWS 완전 관리)매우 높음 (Istio/콘트롤 플레인 필요)
트래픽 규모 기준소~중규모, 고정적 트래픽중~대규모, 급격한 트래픽 변동대규모 다중 서비스 간 내부 트래픽
핵심 강점정적 파일 서빙, 세밀한 설정 제어AWS 네이티브 통합, 자동 스케일링서비스 디스커버리, gRPC, 동적 구성
비용 구조서버 비용 + 엔지니어 운영 비용트래픽·LCU 기반 종량제인프라 비용 + 높은 운영 인건비
circuit breaker없음 (앱 레벨에서 별도 구현)없음 (Target Group health check으로 대체)내장 (outlier detection, retry)
프로토콜HTTP/1.1, HTTP/2, TCPHTTP/1.1, HTTP/2, WebSocketHTTP/1.1, HTTP/2, HTTP/3, gRPC, TCP

실무 의사결정 기준 (BackOps)

팀 규모 소~중, AWS ECS 환경
→ ALB 단독 사용이 기본값
→ Nginx를 추가하는 이유: 정적 파일 서빙, 세밀한 요청 재작성, ALB 뒤에서 부분 캐싱
트래픽 < 1,000 RPS & EC2 단일 인스턴스
→ Nginx 직접 운영이 ALB보다 저렴할 수 있음 (ALB 최소 비용 ~$15/월 + LCU)
마이크로서비스 10개 이상, 서비스 간 gRPC 통신 필수
→ Envoy 검토 (Istio 기반 서비스 메시)
→ 단, 학습곡선과 운영 복잡도를 감당할 팀 역량이 전제

📖 참고: AWS ALB 공식 문서 - Features | Envoy 공식 문서 - What is Envoy


🔧 502 Bad Gateway — Nginx가 업스트림 서버에 연결 실패

섹션 제목: “🔧 502 Bad Gateway — Nginx가 업스트림 서버에 연결 실패”

📖 더 보기: 502 Bad Gateway Nginx Fix - CloudPanel — 502 원인별 진단과 해결 방법 정리

증상: 브라우저에서 502 Bad Gateway 에러 발생. Nginx 에러 로그에 아래 메시지

connect() failed (111: Connection refused) while connecting to upstream

원인: Nginx는 정상이지만 업스트림(Nest.js) 서버가 응답을 못하는 상태. 주요 원인:

  1. Node.js 프로세스가 죽어있음
  2. proxy_pass에 설정한 포트가 틀림 (3000 vs 8080 등)
  3. Node.js가 아직 기동 중 (헬스체크 실패)

해결:

Terminal window
# 1. Node.js 프로세스 실행 여부 확인
ps aux | grep node
# 예상 출력 (정상):
# young 1234 1.2 2.3 node dist/main.js
# 결과 없으면 → 프로세스 재시작 필요
# 2. 해당 포트 리스닝 여부 확인
ss -tlnp | grep 3000
# 예상 출력 (정상):
# LISTEN 0 128 0.0.0.0:3000 users:(("node",pid=1234,fd=18))
# 3. Nginx에서 직접 업스트림 연결 테스트
curl -v http://localhost:3000/health
# → 200 OK이면 Nginx 설정 문제, 연결 실패면 Node.js 문제
# 4. Nginx 에러 로그 실시간 확인
tail -f /var/log/nginx/error.log
# → 에러 메시지로 원인 파악

🔧 504 Gateway Timeout — 업스트림 응답 시간 초과

섹션 제목: “🔧 504 Gateway Timeout — 업스트림 응답 시간 초과”

증상: 특정 API에서 504 Gateway Timeout 발생. 오래 걸리는 작업(대용량 CSV 처리, 복잡한 쿼리 등)에서 자주 발생 원인: Nginx의 proxy_read_timeout (기본 60초)보다 업스트림 응답이 늦음

해결:

# nginx.conf — 해당 location 블록에 타임아웃 늘리기
location /api/heavy-job {
proxy_pass http://localhost:3000;
proxy_read_timeout 300s; # 기본 60s → 필요에 따라 조정
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
}

근본 해결책은 타임아웃을 늘리는 게 아니라 무거운 작업을 Queue로 분리하고 즉시 응답하는 것


🔧 502 Bad Gateway — 응답 헤더/바디가 버퍼 크기를 초과

섹션 제목: “🔧 502 Bad Gateway — 응답 헤더/바디가 버퍼 크기를 초과”

증상: 특정 API만 502 Bad Gateway 발생. 특히 대용량 쿠키나 긴 Authorization 헤더를 사용하는 요청에서 발생. Nginx 에러 로그:

Terminal window
upstream sent too big header while reading response header from upstream

원인: Nginx는 업스트림 응답을 버퍼에 담아 처리하는데, 응답 헤더가 기본 버퍼 크기(4k 또는 8k)를 초과하면 처리하지 못하고 502를 반환한다. JWT 토큰이 길거나 Set-Cookie 헤더가 많은 경우 흔히 발생한다.

해결:

location / {
proxy_pass http://localhost:3000;
# 응답 헤더 버퍼 크기 증가 (기본값: 4k 또는 8k)
proxy_buffer_size 16k;
proxy_buffers 4 16k;
proxy_busy_buffers_size 16k;
}

📖 더 보기: Nginx 502 Bad Gateway Debugging Checklist - ZeonEdge — 502 원인별 체계적 진단 체크리스트 (중급)


🔧 HTTPS 접속 시 “인증서 오류” 또는 연결 실패

섹션 제목: “🔧 HTTPS 접속 시 “인증서 오류” 또는 연결 실패”

증상: 브라우저에서 ERR_CERT_COMMON_NAME_INVALID 또는 SSL_ERROR_RX_RECORD_TOO_LONG

원인 1: 인증서 도메인과 실제 접속 도메인 불일치 (www. 유무, 와일드카드 여부) 원인 2: HTTP 포트(80)로 HTTPS 요청을 보내는 경우 (ERR_SSL_PROTOCOL_ERROR)

해결:

Terminal window
# 인증서 정보 확인
openssl s_client -connect api.example.com:443 -showcerts 2>/dev/null | \
openssl x509 -noout -text | grep "Subject\|DNS"
# 예상 출력:
# Subject: CN=api.example.com
# DNS:api.example.com, DNS:*.example.com
# Nginx가 어느 포트에서 SSL을 처리하는지 확인
grep -n "listen\|ssl_certificate" /etc/nginx/conf.d/*.conf
# 예상 출력:
# /etc/nginx/conf.d/default.conf:2: listen 443 ssl;
# /etc/nginx/conf.d/default.conf:5: ssl_certificate /etc/ssl/cert.pem;

🔧 ALB Health Check 실패로 ECS Task가 계속 재시작됨

섹션 제목: “🔧 ALB Health Check 실패로 ECS Task가 계속 재시작됨”

증상: ECS Fargate에서 태스크가 시작되자마자 Unhealthy 판정을 받고 반복 재시작. ALB Target Group에서 “unhealthy” 상태 지속

원인:

  1. Health Check 경로(/health)가 Nest.js에 구현되어 있지 않음
  2. Health Check 포트/프로토콜 설정이 실제 앱과 다름
  3. Nest.js 앱이 기동 완료 전에 ALB가 체크를 시작 (Startup 지연)

해결:

// Nest.js에 헬스체크 엔드포인트 추가
@Controller()
export class HealthController {
@Get("/health")
health() {
return { status: "ok" };
// → ALB가 이 엔드포인트를 호출해 200 OK이면 Healthy 판정
}
}
Terminal window
# AWS 콘솔 또는 CLI로 Target Group Health Check 설정 확인
# 경로: EC2 → Load Balancers → Target Groups → [해당 그룹] → Health checks 탭
# 확인 항목:
# - Health check path: /health (앱에 있는 경로인지 확인)
# - Healthy threshold: 2 (2번 연속 성공 시 Healthy)
# - Unhealthy threshold: 3 (3번 연속 실패 시 Unhealthy)
# - Timeout: 5s (앱 응답이 이 시간 내에 와야 함)

🔧 503 Service Unavailable — ALB에 등록된 Target이 없거나 모두 Unhealthy

섹션 제목: “🔧 503 Service Unavailable — ALB에 등록된 Target이 없거나 모두 Unhealthy”

증상: ALB DNS로 요청 시 503 Service Unavailable 반환. ECS 콘솔에서 태스크는 실행 중으로 보임

원인:

  1. ALB Target Group에 등록된 ECS Task가 없음 (태스크 배포 직후 공백 구간)
  2. 모든 Target이 Unhealthy 상태 (Health Check 실패 상태)
  3. Security Group 설정으로 ALB → ECS 컨테이너 포트가 막혀 있음

해결:

Terminal window
# 1. Target Group의 Target 상태 확인
aws elbv2 describe-target-health \
--target-group-arn arn:aws:elasticloadbalancing:ap-northeast-2:123456789:targetgroup/my-tg/abc123
# 예상 출력 (정상):
# { "TargetHealthDescriptions": [
# { "Target": {"Id": "10.0.1.5", "Port": 3000},
# "TargetHealth": {"State": "healthy"} }
# ] }
# 예상 출력 (문제 있을 때):
# { "TargetHealth": {"State": "unhealthy", "Reason": "Target.FailedHealthChecks"} }
# 2. Security Group 규칙 확인 — ALB → ECS 컨테이너 포트 허용 여부
aws ec2 describe-security-groups --group-ids sg-xxxx \
--query 'SecurityGroups[].IpPermissions'
# → Inbound 규칙에 ALB의 Security Group에서 3000 포트 허용이 있어야 함
# 3. ECS 태스크 배포 직후 503이 발생한 경우
# → ALB deregistration_delay(기본 300초) 동안 기존 Target이 유지되므로
# → 신규 배포 시 "최소 정상 백분율"을 100%로 설정하여 공백 방지

🔧 Cascading Failure — L4 timeout이 L7 circuit breaker를 무력화하는 패턴

섹션 제목: “🔧 Cascading Failure — L4 timeout이 L7 circuit breaker를 무력화하는 패턴”

시나리오: DB 응답이 느려졌을 때 앱 전체가 멈추는 이유

[정상]
클라이언트 → ALB → Nest.js → DB (100ms)
[DB 느려짐 — L4 timeout 유발]
클라이언트 → ALB → Nest.js ──────────────────→ DB (30s 이상)
TCP 연결은 살아있음 (L4 레벨)
하지만 응답은 안 옴

핵심 문제: ALB Health Check는 HTTP 200을 기준으로 판단한다. DB가 느릴 때 /health 엔드포인트는 DB를 조회하지 않으면 여전히 200을 반환하므로 ALB는 Healthy로 판단하지만, 실제 API는 모두 타임아웃 상태다.

  1. DB 응답 지연 → Nest.js 워커 스레드/이벤트루프 점유 증가
  2. ALB Health Check는 /health(DB 미조회)만 보므로 Target = Healthy 유지
  3. ALB가 계속 새 요청을 전송 → Nest.js 커넥션 풀 포화
  4. L4 TCP 연결은 유지되지만 L7 HTTP 응답이 없음 → 클라이언트 전체 타임아웃
  5. 클라이언트가 재시도 → 부하 가중 → 전체 서비스 다운 (cascading failure)

circuit breaker가 없으면 왜 멈추는가: Nginx나 ALB에는 L7 circuit breaker가 내장되어 있지 않다. “업스트림이 n번 연속 실패하면 요청을 차단” 로직을 앱 레벨에서 별도로 구현하지 않으면, 느린 업스트림이 회복될 때까지 모든 요청이 쌓인다.

방어 패턴:

// 1. Health Check에 DB 연결 상태 포함 (shallow health check와 분리)
@Get('/health/deep')
async healthDeep() {
await this.dataSource.query('SELECT 1'); // DB 실제 조회
return { status: 'ok', db: 'connected' };
// ALB Health Check는 /health(빠른 응답), 모니터링은 /health/deep
}
# 2. Nginx: upstream 응답 실패 시 서버를 임시 제외 (passive health check)
upstream backend {
server localhost:3000 max_fails=3 fail_timeout=30s;
# 30초 내 3번 실패 시 해당 서버를 30초간 제외
}
location / {
proxy_pass http://backend;
proxy_read_timeout 10s; # 너무 길면 연결이 쌓임 — DB timeout보다 짧게 설정
proxy_connect_timeout 3s;
}

정리: L4(TCP) 연결이 살아있다고 L7(HTTP) 요청이 정상 처리되는 것이 아니다. 느린 업스트림은 빠른 업스트림 다운보다 더 위험하다 — Health Check를 통과한 채로 요청을 계속 받기 때문이다.


  • OSI 7계층을 외우지 않아도 “이 에러는 몇 계층 문제”인지 판단할 수 있다
  • 리버스 프록시가 뭔지, 왜 쓰는지 설명할 수 있다
  • 80포트로 들어온 요청이 Node.js 3000포트까지 도달하는 과정을 설명할 수 있다
  • 팀 서비스 앞에 Web Server가 있는지, ALB만 있는지 확인했다
  • 502와 504 에러의 차이를 설명할 수 있다

TCP 3-way handshake, TLS handshake, Nginx upstream, upstream timeout, CDN, DNS round-robin, ALB Target Group, Health Check, HTTP/2 multiplexing, keepalive_timeout

  • 팀 서비스의 트래픽 진입점 확인 (ALB → Nginx → Node? ALB → Node?)
Terminal window
# ALB DNS를 직접 curl로 호출해서 응답 헤더 확인
curl -I https://your-alb-dns.ap-northeast-2.elb.amazonaws.com/health
# 예상 출력:
# HTTP/1.1 200 OK
# Server: nginx/1.24.0 ← Nginx 있으면 표시됨
# Server: Cowboy ← Fastify(Node.js) 직접이면 이쪽
  • Nginx 설정 파일이 있다면 proxy_pass 설정 확인
Terminal window
# Nginx 설정 파일 위치 찾기
find /etc/nginx -name "*.conf" | xargs grep -l "proxy_pass"
# 설정 확인
cat /etc/nginx/conf.d/default.conf
# 예상 출력 (핵심 부분):
# location / {
# proxy_pass http://127.0.0.1:3000;
# proxy_set_header Host $host;
# }
  • curl -v https://서비스주소로 TLS 핸드셰이크 과정 확인
Terminal window
curl -v https://api.example.com/health 2>&1 | head -30
# 예상 출력 (주요 부분):
# * Trying 52.68.1.100:443...
# * Connected to api.example.com (52.68.1.100) port 443
# * TLSv1.3 (OUT), TLS handshake, Client hello
# * TLSv1.3 (IN), TLS handshake, Server hello
# * SSL certificate verify ok. ← 인증서 정상
# > GET /health HTTP/2
# < HTTP/2 200
  • ALB Target Group에서 ECS 태스크 Health 상태 확인
Terminal window
# AWS CLI로 Target Group Health 상태 조회
aws elbv2 describe-target-health \
--target-group-arn $(aws elbv2 describe-target-groups \
--query 'TargetGroups[0].TargetGroupArn' --output text)
# 예상 출력 (정상):
# { "TargetHealthDescriptions": [
# { "TargetHealth": { "State": "healthy" } }
# ] }
# 예상 출력 (문제 있을 때):
# { "TargetHealth": { "State": "unhealthy", "Reason": "Target.FailedHealthChecks",
# "Description": "Health checks failed" } }
# → /health 엔드포인트 구현 여부, Security Group 포트 허용 여부 순서로 확인
항목핵심 내용
OSI 7계층문제 진단 기준 — 7(HTTP) → 4(포트/방화벽) → 3(IP/VPC) → 6(TLS) 순으로 확인
리버스 프록시클라이언트 앞에서 SSL 처리 + 내부 서버로 요청 전달 (포트 숨김, 로드밸런싱)
Nginx vs ALBNginx는 직접 운영, ALB는 AWS 관리형 — ECS Fargate에서는 ALB가 대신함
502 vs 504502: 업스트림 연결 실패(Node.js 다운), 504: 응답 시간 초과(타임아웃)
Health CheckALB가 /health를 주기적으로 체크 — 실패하면 Unhealthy로 태스크 교체

5줄 핵심

  1. OSI 7계층은 네트워크 문제를 계층별로 나눠서 진단하는 기준이다
  2. 실무에서 주로 마주치는 계층은 7(HTTP), 4(TCP/포트), 3(IP), 6(TLS)이다
  3. Web Server(Nginx)는 클라이언트 요청을 가장 먼저 받아 내부 서버로 전달하며 SSL 처리, 로드밸런싱을 담당한다
  4. 502(업스트림 연결 실패)와 504(응답 시간 초과)는 원인과 해결 방향이 다르다
  5. ECS Fargate 환경에서는 ALB가 Nginx 역할을 대신하고, Security Group이 포트 접근을 제어한다
OSI 모델 & Web Server (지금 여기)
TCP/IP 심화 ← TCP 3-way handshake, Keep-Alive, TIME_WAIT 상태
HTTPS / TLS 심화 ← TLS 1.3 핸드셰이크, mTLS, 인증서 갱신 자동화 (ACM)
AWS 네트워킹 ← VPC, Subnet, Security Group, Route 53, ALB 라우팅 규칙
서비스 메시 / API Gateway ← Kong, AWS API Gateway, 트래픽 제어 패턴

인터뷰 대비 핵심 질문 (실제 자주 출제)

  • “HTTP와 HTTPS의 차이는? TLS Handshake는 어느 OSI 계층인가?”
  • “502와 504 에러의 차이는? 각각 어떻게 디버깅하는가?”
  • “리버스 프록시와 포워드 프록시의 차이는? 리버스 프록시를 쓰는 이유는?”
  • “Nginx가 Apache보다 고성능인 이유는? (이벤트 기반 vs 프로세스 기반)”
  • “ECS Fargate에서 ALB Health Check가 실패하는 주요 원인은?”

최종 수정: 2026-04-01