OSI 모델 & Web Server
분류: Layer 2 - 인프라 기초 | 작성일: 2026-03-22
1. 한 줄 정의
섹션 제목: “1. 한 줄 정의”OSI 모델은 네트워크 통신을 7개 계층으로 나눈 개념 모델이고, Web Server(Nginx 등)는 HTTP 요청을 받아 정적 파일을 제공하거나 애플리케이션 서버로 요청을 전달하는 소프트웨어다.
2. 왜 중요한가
섹션 제목: “2. 왜 중요한가”네트워크 문제가 생겼을 때 “어느 계층에서 문제인가”를 판단하는 기준이 OSI 모델이다. Web Server는 Nest.js 앞에 Nginx가 붙어 있는 구성에서 요청이 어떻게 흘러들어오는지 이해하는 데 필요하다. “80포트로 들어온 요청이 3000번 포트의 Node.js 서버에 도달하는가”의 답이 여기에 있다.
2.5 선행 기술의 한계 — OSI · Web Server가 풀려던 문제
섹션 제목: “2.5 선행 기술의 한계 — OSI · Web Server가 풀려던 문제”이 토픽은 서로 다른 두 궤적의 추상화가 한 문서에 묶여 있다. “왜 이 모델이 필요했는가”는 그 이전 세계가 무엇 때문에 못 돌아갔는지를 봐야 답이 나온다. 단순 역사 메모가 아니라, 두 추상화가 지금 사라지면 무엇이 깨지는지를 동시에 짚는다.
OSI 7계층 — proprietary protocol incompatibility의 해결
1970년대 중반 네트워킹은 벤더 잠금이 기본값이었다. IBM SNA(1974), DEC DECnet(1975), Xerox XNS, Burroughs BNA 모두 자체 계층 구조를 갖고 있었으나 서로 호환되지 않았다 — 멀티벤더 환경에서 두 네트워크를 연결하려면 게이트웨이를 매번 작성해야 했다 (Wikipedia: Protocol Wars). ISO는 1977년 시드니 회의에서 표준화를 시작해 1984년 OSI 7계층 모델을 공식 발표했다 (Wikipedia: OSI model). 실제 인터넷 표준 전쟁은 TCP/IP가 승리했지만, 계층 인터페이스를 표준화해 벤더 종속을 끊는다는 OSI의 어휘 자체가 살아남아 오늘날 진단 도구가 됐다.
선행 토픽 L1/http-basics는 7계층에서 끝난다 — HTTP만 알면 4xx/5xx는 보이지만 그 아래 TCP/IP/TLS는 블랙박스다. OSI 모델은 이 블랙박스를 4·6·3계층으로 분해해 “HTTPS 인증서 만료(L6)“와 “Security Group 차단(L4)“를 같은 단어로 부르지 않게 한다.
→ 사라지면 깨지는 것: 멀티벤더·멀티프로토콜 환경의 문제 분류 공통어가 사라져 oncall이 진단 범위를 좁히지 못한다.
Web Server — Apache prefork의 C10K wall 해결
Apache 1.x(1995~)는 prefork MPM이었다 — 부모 프로세스가 자식 프로세스를 미리 띄워두고 연결마다 하나씩 점유한다. mod_php·CGI 시절엔 요청마다 새 PHP 인터프리터 프로세스를 spawn했다 (Apache mod_cgi 공식). 1999년 Dan Kegel의 “The C10K problem”이 이 모델의 정량 한계를 못박았다: 32-bit Linux는 thread당 기본 2MB 스택을 잡으므로 가상 메모리가 ~512 thread에서 고갈된다. 하드웨어가 받쳐줘도 OS가 동시 1만 연결 전에 무너졌다.
해결책은 하나의 thread가 다수 fd를 비동기로 감시하는 방향이었다 — Linux 2.6의 epoll, FreeBSD/macOS의 kqueue. Igor Sysoev가 2002년 rambler.ru 트래픽을 받아내려고 작성한 Nginx는 이 두 syscall을 기본 channel로 채택했다. 3절의 “Nginx 50MB vs Apache Prefork 2.5GB”(keepalive 10K) 비교가 이 차이의 결과다 — 연결당 메모리 비용이 prefork는 선형, event-loop는 거의 평탄.
→ 사라지면 깨지는 것: 단일 코어로 수만 연결을 처리하는 가정이 무너진다. 3절에 나오는 Node.js libuv, Redis, HAProxy/Envoy 전부 동일 모델 위에 있으므로 모던 reverse proxy 생태계 전체가 재설계 대상이 된다.
언제 이 lineage 자체가 깨지는가 (Inversion)
두 추상화 모두 만능이 아니다. 도입 후 이상 신호로 감지해야 할 silent failure에 해당한다.
- OSI 7계층의 모호 영역 — QUIC/HTTP3: QUIC은 UDP(L4) 위에 TLS(L6)와 connection 관리(L5)를 함께 패킹한다. 같은 패킷이 transport·session·presentation 의미를 동시에 갖는다. 감지법:
tcpdump -i any 'udp port 443'에서 트래픽이 보이는데ss -tan(TCP만)에선 안 보이면 QUIC 흐름 — 4계층 진단 도구가 무력화된 상태다. - event-loop의 CPU bound 함정: 이미지 인코딩·동기 JSON 직렬화·정규식 백트래킹이 들어오면 이벤트 루프가 점유되어 동시 연결 모델이 prefork보다 못해진다. 감지법: Node.js
process.eventLoopUtilization()≥ 0.9가 1초 이상 지속되면 워커 풀(worker_threads) 오프로드 또는 별도 서비스 분리 필요. Nginx도 동일 —worker_processes auto로 코어 수만큼 늘려도 CPU 바운드면 평탄해진다.
3. 핵심 개념
섹션 제목: “3. 핵심 개념”비유로 시작 — “국제 택배”
편지(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계층이 유용하다. “어느 계층에서 문제인가”를 좁히면 원인을 빠르게 찾을 수 있다.
| 계층 | 이름 | 실무 관련 예시 | 문제 증상 |
|---|---|---|---|
| 7 | Application | HTTP, HTTPS, DNS | 4xx/5xx 에러, API 응답 이상 |
| 6 | Presentation | TLS/SSL 암호화 | 인증서 오류, HTTPS 핸드셰이크 실패 |
| 5 | Session | 세션 연결 유지 | 연결이 갑자기 끊김 |
| 4 | Transport | TCP/UDP, 포트 번호 | 포트 미오픈, 방화벽 차단 |
| 3 | Network | IP 주소, 라우팅 | IP 충돌, VPC 라우팅 오류 |
| 2 | Data Link | MAC 주소 | (실무에서 거의 안 만남) |
| 1 | Physical | 케이블, Wi-Fi | (클라우드 환경에서 AWS 책임) |
실무에서 주로 보는 계층: 7(HTTP 에러) → 4(포트/방화벽) → 3(IP/VPC/Security Group) → 6(TLS 인증서)
왜 7계층으로 나누는가 — Layered Abstraction Principle
각 계층이 독립적으로 설계된 덕분에 상위 계층은 하위 계층이 어떻게 동작하는지 몰라도 된다. 예를 들어, HTTP는 TCP 위에서 동작하지만 HTTP 코드를 작성할 때 TCP 3-way handshake를 신경 쓸 필요가 없다. Nginx도 마찬가지로 TLS 핸드셰이크(6계층)를 처리하면서 내부 Nest.js 서버에는 평문 HTTP(7계층)로 전달한다. 이 분리가 리버스 프록시의 핵심 원리다.
이 원리는 OSI 전용이 아니다. 상위 layer가 하위 layer를 구현 세부 없이 호출할 수 있도록 안정된 인터페이스를 두는 패턴은 컴퓨팅 전반에서 반복된다.
- OS 커널 ↔ 유저 공간:
read()시스템 콜은 디스크 컨트롤러 명령을 추상화한다. 디스크가 NVMe든 SATA든 호출자는 같은 API를 쓴다. - React 컴포넌트 트리: 상위 컴포넌트는 자식 컴포넌트의 내부 state·렌더 알고리즘을 모른 채 props라는 안정된 인터페이스로만 협력한다.
- DB 드라이버 ↔ 와이어 프로토콜: 애플리케이션은 SQL만 쓰고, 드라이버가 MySQL 바이너리 프로토콜로 변환한다. 프로토콜이 5.7 → 8.0으로 바뀌어도 앱 코드는 영향 없다.
진단에 활용하는 법: 문제가 어느 layer에 속하는지 확정하면 그 위·아래 layer는 일단 의심 목록에서 제외할 수 있다. OSI 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.com5. 서버 응답 → 브라우저 수신프론트엔드 → 플랫폼 브릿지: 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로 이 과정을 직접 볼 수 있다.
# 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가 리버스 프록시로 동작할 때의 핵심은 이벤트 기반 비동기 아키텍처다. 더 구체적으로는 I/O Multiplexing 원리 — 커널이 제공하는 epoll(Linux) / kqueue(BSD·macOS) / IOCP(Windows) 시스템 콜을 통해 하나의 스레드가 수많은 소켓의 준비 이벤트를 한 번에 감시하고, 준비된 것만 골라 처리하는 패턴이다. Apache의 prefork처럼 요청마다 프로세스/스레드를 생성하지 않으므로 컨텍스트 스위치 비용과 메모리 사용량이 연결 수에 거의 비례하지 않는다. C10K(동시 1만 연결) 문제는 이 원리로 해결되었다.
이 원리는 Nginx 전용이 아니다. 하나의 실행 흐름이 다중 I/O 핸들을 비동기로 감시·처리한다는 공식은 여러 도메인에서 반복된다.
- Node.js의 libuv: 단일 이벤트 루프가
epoll/kqueue로 수천 개의 fd를 감시하고 콜백을 디스패치한다. Nginx 워커 1개와 거의 동일한 모델이다. - Redis 단일 스레드: 마찬가지로
epoll기반 이벤트 루프로 10만+ QPS를 단일 코어에서 처리한다. CPU bound가 아니라 I/O bound라는 가정이 같다. - HAProxy·Envoy의 데이터 플레인: 둘 다 같은 비동기 I/O 모델 위에 빌드되어 있다.
정량 비교 — 메모리 footprint (출처: Nginx vs Apache 벤치마크): 동시 keepalive 연결 10,000개를 유지할 때 Nginx는 약 50MB, Apache Prefork는 약 2.5GB, Apache Event MPM은 약 200MB를 사용한다. Prefork는 연결당 ~2MB의 프로세스 메모리가 선형으로 누적되는 반면, Nginx는 연결당 메모리 비용이 거의 평탄하다.
출처: Nginx vs Apache: 10x More Connections (2026) — 동시 keepalive 10K 기준 메모리 사용량 비교
조합의 이유: Nginx(앞단)도 Node.js(뒷단)도 같은 I/O Multiplexing 원리 위에서 동작하므로 “어느 한쪽이 스레드를 블로킹해서 다른 쪽이 멈춘다” 같은 모델 불일치가 없다. 단, CPU 바운드 작업(이미지 인코딩, 무거운 JSON 직렬화)은 이벤트 루프를 점유하므로 이 모델이 깨진다 — 그 시점부터는 워커 프로세스 수를 늘리거나 별도 워커 풀로 오프로드해야 한다.
클라이언트 ──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 GroupNest.js 컨테이너 (Fargate Task)
※ Fargate 환경에서는 ALB가 Nginx 역할을 대신함※ Security Group으로 ALB → 컨테이너 포트만 허용※ ALB는 경로 기반 라우팅 가능: /api/* → Nest.js, /static/* → S34. 실무에서 어디에 쓰이나
섹션 제목: “4. 실무에서 어디에 쓰이나”- 네트워크 장애 발생 시 계층별 원인 찾아내기 (“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계층 문제)
5. 현재 내 업무와 연결점
섹션 제목: “5. 현재 내 업무와 연결점”- 서비스 앞에 Nginx가 있는지, ALB만 있는지 파악
- HTTPS 인증서가 어디서 처리되는지 이해
- 네트워크 에러 발생 시 계층 기반으로 원인 찾아내기
6. 자주 헷갈리는 개념 비교
섹션 제목: “6. 자주 헷갈리는 개념 비교”| 개념 A | 개념 B | 차이점 |
|---|---|---|
| Web Server | WAS(Web Application Server) | Web Server는 정적 파일/프록시, WAS는 비즈니스 로직 실행 (Node.js는 WAS) |
| 리버스 프록시 | 포워드 프록시 | 리버스는 서버 앞에서 요청 분배, 포워드는 클라이언트 대신 요청 |
| Nginx | Apache | 둘 다 Web Server. Nginx는 비동기/논블로킹으로 고성능, Apache는 프로세스 기반 |
| ALB | Nginx | ALB는 AWS 관리형 로드밸런서, Nginx는 직접 운영하는 Web Server |
| HTTP/1.1 | HTTP/2 | HTTP/1.1은 요청 순차 처리, HTTP/2는 하나의 연결로 동시 처리(멀티플렉싱) |
6.5 선택 매트릭스 — Nginx vs ALB vs Envoy
섹션 제목: “6.5 선택 매트릭스 — Nginx vs ALB vs Envoy”세 가지 모두 리버스 프록시/로드밸런서 역할을 할 수 있지만 적합한 상황이 다르다. “다 가능하다”가 아니라 어떤 조건이면 무엇이 최선인가를 판단해야 한다.
| 판단 기준 | Nginx | ALB (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, TCP | HTTP/1.1, HTTP/2, WebSocket | HTTP/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 기반 서비스 메시) → 단, 학습곡선과 운영 복잡도를 감당할 팀 역량이 전제각 선택지가 “깨지는” 조건 — 도입 전 반드시 평가
“다 가능하다”는 답이 가장 위험하다. 각 옵션이 어떤 정량적 조건에서 실패하는지 알고 도입해야 한다.
Nginx가 깨지는 조건
- 인적 SPOF: 설정·인증서 갱신·커널 튜닝을 아는 사람이 1명이면 그 사람이 휴가일 때 변경이 멈춘다. ALB와 달리 컨트롤 플레인이 사람이다.
- 단일 upstream에서 passive health check 무력화 (silent failure):
upstream블록에 server 1개만 두면max_fails·fail_timeout·slow_start파라미터가 공식적으로 무시된다 (Nginx 공식 docs). “장애 시 자동 제외되겠지”라고 가정하면 에러 로그에 경고 없이 그냥 무시된 채 돌아간다. - TLS 인증서 자동 갱신 미설치: cron + certbot 또는 acme.sh를 직접 운영해야 한다. 자동화가 빠지면 만료일에 전체 서비스가 6계층(TLS) 에러로 다운된다.
- OS 커널 한계:
worker_connections × worker_processes가ulimit -n(파일 디스크립터 한도)을 넘으면 신규 연결이accept() failed (24: Too many open files)로 거부된다.
ALB가 깨지는 조건 — LCU 임계값 정량
ALB는 4가지 차원 중 최대값으로 LCU가 산정되므로, 한 차원만 폭증해도 비용이 튄다. 1 LCU의 허용량 (AWS 공식 가격 페이지):
| 차원 | 1 LCU 허용량 | 폭증 트리거 |
|---|---|---|
| 신규 연결 | 25 conn/s | keepalive 미사용 클라이언트 다수 (모바일) |
| 활성 연결 | 3,000 active conn/min | 장기 WebSocket 또는 SSE |
| 처리 바이트 | 1 GB/h (Lambda는 0.4GB/h) | 대용량 파일 응답, 이미지·비디오 프록시 |
| 룰 평가 | 1,000/s | 리스너 룰 수 × RPS — 룰 50개 × 100 RPS = 5K/s = 5 LCU |
- 기준 비용: ALB 시간당 $0.0225 + LCU당 $0.008 = 최소 ~$16/월
- 폭증 예시: 100K WebSocket 클라이언트 상시 연결 시 활성 연결 차원만으로 100,000 / 3,000 ≈ 33 LCU × $0.008 × 730h ≈ 월 $193 (이 차원만)
- 다른 깨짐: ALB는 L7 circuit breaker가 내장되어 있지 않다. Target 단위 health check로만 격리하므로 “느린 응답”은 못 막는다(아래 6.6 Cascading Failure 섹션 참조). gRPC unary는 가능하지만 양방향 스트리밍은 NLB가 필요하다.
Envoy가 깨지는 조건
- 운영 인건비 폭증: 컨트롤 플레인(Istio·Consul)이 별도 시스템이다. 사이드카 패턴은 Pod마다
50MB 추가 메모리 + 0.52ms latency를 더한다. 마이크로서비스가 5개 미만이면 ROI 음수. - xDS(컨트롤 플레인 API) 학습 곡선: Listener·Cluster·Route·Endpoint 4축의 동적 구성을 다루어야 한다. Nginx의
nginx -s reload같은 단순 모델이 없다. - 버전 호환: Istio · Envoy · Kubernetes 3개의 버전 매트릭스를 동시에 맞춰야 한다. 한쪽 EOL이 다가오면 다른 두 개도 같이 끌어올려야 한다.
의사결정 예시 (사례 적용): 트래픽 200 RPS, EC2 1대, 팀원 1명이 Nginx를 운영한다고 가정. 위 기준으로 점검하면 (1) 인적 SPOF — 그 사람이 휴가 가면 위험, (2) 단일 upstream — passive health check 무력화, (3) cert 자동화 — certbot 설치 여부 미확인. → 결정: ALB로 이전. 월 비용 $15~25 증가하지만 (1)(2)(3) 위험이 사라지고, 이후 트래픽이 1K RPS 이상으로 늘어도 LCU 폭증 차원은 룰 수 10개 이내·keepalive 사용으로 통제 가능.
📖 참고: AWS ALB 공식 문서 - Features | Envoy 공식 문서 - What is Envoy
6.6 트러블슈팅
섹션 제목: “6.6 트러블슈팅”🔧 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) 서버가 응답을 못하는 상태. 주요 원인:
- Node.js 프로세스가 죽어있음
proxy_pass에 설정한 포트가 틀림 (3000 vs 8080 등)- Node.js가 아직 기동 중 (헬스체크 실패)
해결:
# 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 에러 로그:
upstream sent too big header while reading response header from upstream원인: Nginx는 업스트림 응답을 버퍼에 담아 처리하는데, 응답 헤더가 기본 버퍼 크기(4k 또는 8k)를 초과하면 처리하지 못하고 502를 반환한다. JWT 토큰이 길거나 Set-Cookie 헤더가 많은 경우 흔히 발생한다.
해결 — 추측이 아니라 측정 후 설정
기본값 4k/8k를 무작정 128k로 늘리면 워커당 메모리가 4×128k = 512k씩 누적되어 동시 연결 수가 많을 때 메모리가 튄다. 실제 헤더 크기를 먼저 측정한 뒤 그보다 약간 큰 값으로 설정한다.
# 1단계 — 업스트림 응답 헤더의 실제 크기 측정 (Nginx를 거치지 않고 직접 호출)curl -s -w '%{size_header}\n' -o /dev/null http://localhost:3000/api/heavy# 예상 출력:# 6231 ← 헤더 크기 ~6KB → 기본 4k에서 502, 8k에선 통과# 12480 ← 헤더 크기 ~12KB → 8k 기본값도 부족, 16k 이상 필요
# 2단계 — 어떤 응답에서 큰지 좁히기 (인증된 요청 vs 비인증, Set-Cookie 많은 응답)curl -s -w 'header=%{size_header}\n' -o /dev/null \ -H 'Authorization: Bearer eyJhbG...(실제 JWT)' \ http://localhost:3000/api/me# Set-Cookie 헤더 여러 개 + 긴 JWT가 원인인 경우가 가장 흔하다.# 3단계 — 측정값에 맞춰 설정 (proxy_buffer_size = 측정값을 4KB 경계로 올림한 값)location / { proxy_pass http://localhost:3000; proxy_buffer_size 16k; # 첫 헤더 청크 크기 (기본 4k 또는 8k) proxy_buffers 4 16k; # 본문 버퍼 개수 × 크기 proxy_busy_buffers_size 16k; # 클라이언트로 전송 중인 버퍼 한도}# 4단계 — 변형 실험으로 동작 확인 (한 번에 한 변수만 바꿔야 인과를 좁힐 수 있음)sudo nginx -t && sudo nginx -s reloadcurl -I http://your-nginx/api/me # 502 → 200으로 바뀌면 버퍼 크기가 원인 확정tail -f /var/log/nginx/error.log # "upstream sent too big header" 재현 여부 모니터링
# 5단계 — proxy_buffering off로 비교 (직관과 달리 이 설정만으로는 해결되지 않음)# location / { proxy_buffering off; } 만 추가하고 reload하면 → 여전히 502 가능# proxy_buffer_size는 buffering off 상태에서도 헤더 저장에 쓰이기 때문왜 측정 후 설정해야 하는가: proxy_buffer_size는 워커 메모리에서 연결당 할당된다. 1만 동시 연결 × 128k = 1.3GB. 16k로 충분한데 128k를 쓰면 1.1GB가 낭비된다.
📖 더 보기: Tuning proxy_buffer_size in NGINX - GetPageSpeed — 측정 기반 튜닝 가이드 | 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)
해결:
# 인증서 정보 확인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” 상태 지속
원인:
- Health Check 경로(
/health)가 Nest.js에 구현되어 있지 않음 - Health Check 포트/프로토콜 설정이 실제 앱과 다름
- Nest.js 앱이 기동 완료 전에 ALB가 체크를 시작 (Startup 지연)
해결:
// Nest.js에 헬스체크 엔드포인트 추가@Controller()export class HealthController { @Get("/health") health() { return { status: "ok" }; // → ALB가 이 엔드포인트를 호출해 200 OK이면 Healthy 판정 }}# 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 콘솔에서 태스크는 실행 중으로 보임
원인:
- ALB Target Group에 등록된 ECS Task가 없음 (태스크 배포 직후 공백 구간)
- 모든 Target이 Unhealthy 상태 (Health Check 실패 상태)
- Security Group 설정으로 ALB → ECS 컨테이너 포트가 막혀 있음
해결:
# 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는 모두 타임아웃 상태다.
- DB 응답 지연 → Nest.js 워커 스레드/이벤트루프 점유 증가
- ALB Health Check는
/health(DB 미조회)만 보므로 Target = Healthy 유지 - ALB가 계속 새 요청을 전송 → Nest.js 커넥션 풀 포화
- L4 TCP 연결은 유지되지만 L7 HTTP 응답이 없음 → 클라이언트 전체 타임아웃
- 클라이언트가 재시도 → 부하 가중 → 전체 서비스 다운 (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; server localhost:3001 max_fails=3 fail_timeout=30s backup; # 최소 2개 이상 등록 # 30초 내 3번 실패 시 해당 서버를 30초간 제외}
location / { proxy_pass http://backend; proxy_read_timeout 10s; # 너무 길면 연결이 쌓임 — DB timeout보다 짧게 설정 proxy_connect_timeout 3s;}⚠️ silent failure 주의 — 단일 server에서는 max_fails가 무시된다
Nginx 공식 docs 인용: “If there is only a single server in a group,
max_fails,fail_timeoutandslow_startparameters are ignored, and such a server will never be considered unavailable.” (공식 출처)즉
upstream backend { server localhost:3000 max_fails=3 fail_timeout=30s; }처럼 server를 1개만 등록하면 위 설정은 에러 없이 무시되고 실패해도 그대로 요청이 전달된다. backup server를 추가하거나, Nginx Plus의 active health check를 쓰거나, 앱 레벨 circuit breaker를 별도로 구현해야 한다.
변형 실험으로 max_fails 동작 직접 관찰
# 1단계 — upstream에 server 2개를 두고 한쪽을 의도적으로 죽임# nginx.conf: upstream backend { server 127.0.0.1:3000; server 127.0.0.1:3001; }sudo nginx -t && sudo nginx -s reloadkill -9 $(lsof -ti:3000) # 3000번 서버 강제 종료
# 2단계 — 요청 반복하며 에러 로그에서 제외 시점 확인for i in {1..20}; do curl -s -o /dev/null -w "%{http_code} " http://localhost/; done; echo# 예상 출력 (3번 실패 후 3000번 서버가 30초간 제외되고 200으로 안정):# 502 502 502 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200 200
# 3단계 — 동일 시나리오를 server 1개 upstream에서 재현 (max_fails 무시 확인)# nginx.conf: upstream backend { server 127.0.0.1:3000 max_fails=3 fail_timeout=30s; }# → kill 후 같은 루프 실행 시 20번 모두 502가 나온다 (passive health check 작동 안 함)
# 4단계 — 에러 로그에서 "upstream server temporarily disabled" 메시지로 제외 시점 확인tail -f /var/log/nginx/error.log | grep "temporarily disabled"# server 2개 케이스에서만 이 메시지가 보임. 단일 server에서는 영원히 안 보임 (silent)정리: L4(TCP) 연결이 살아있다고 L7(HTTP) 요청이 정상 처리되는 것이 아니다. 느린 업스트림은 빠른 업스트림 다운보다 더 위험하다 — Health Check를 통과한 채로 요청을 계속 받기 때문이다. 또한 “안전망”이라 생각한 max_fails 설정도 토폴로지(server 개수)에 따라 silent 무력화될 수 있으므로 설정 후 반드시 변형 실험으로 검증한다.
7. 체크리스트
섹션 제목: “7. 체크리스트”- OSI 7계층을 외우지 않아도 “이 에러는 몇 계층 문제”인지 판단할 수 있다
- 리버스 프록시가 뭔지, 왜 쓰는지 설명할 수 있다
- 80포트로 들어온 요청이 Node.js 3000포트까지 도달하는 과정을 설명할 수 있다
- 팀 서비스 앞에 Web Server가 있는지, ALB만 있는지 확인했다
- 502와 504 에러의 차이를 설명할 수 있다
8. 추가 학습 키워드
섹션 제목: “8. 추가 학습 키워드”TCP 3-way handshake, TLS handshake, Nginx upstream, upstream timeout, CDN, DNS round-robin, ALB Target Group, Health Check, HTTP/2 multiplexing, keepalive_timeout
8.5 추천 리소스
섹션 제목: “8.5 추천 리소스”- 📖 NGINX Reverse Proxy 공식 문서 — proxy_pass, 업스트림 설정 공식 가이드 (입문)
- 📖 Understanding OSI & TCP/IP with Real-World Examples - Medium — 실제 요청 흐름으로 OSI 계층 설명, 비유 이해 쉬움 (입문)
- 📖 502 Bad Gateway Nginx Fix - CloudPanel — 502 원인별 진단과 해결 방법 정리 (중급)
- 📖 AWS ALB 공식 문서 - Target Groups — ECS Fargate 환경에서 ALB Target Group 설정 이해 (중급)
- 📖 Ultimate Nginx Performance Tuning Checklist - WeHaveServers — HTTP/2, TLS, 캐싱 포함 Nginx 프로덕션 튜닝 가이드 (중급)
9. 내가 직접 확인해볼 것
섹션 제목: “9. 내가 직접 확인해볼 것”- 팀 서비스의 트래픽 진입점 확인 (ALB → Nginx → Node? ALB → Node?)
# 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 설정 확인
# 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 핸드셰이크 과정 확인
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 상태 확인
# 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 포트 허용 여부 순서로 확인10. 핵심 요약
섹션 제목: “10. 핵심 요약”| 항목 | 핵심 내용 |
|---|---|
| OSI 7계층 | 문제 진단 기준 — 7(HTTP) → 4(포트/방화벽) → 3(IP/VPC) → 6(TLS) 순으로 확인 |
| 리버스 프록시 | 클라이언트 앞에서 SSL 처리 + 내부 서버로 요청 전달 (포트 숨김, 로드밸런싱) |
| Nginx vs ALB | Nginx는 직접 운영, ALB는 AWS 관리형 — ECS Fargate에서는 ALB가 대신함 |
| 502 vs 504 | 502: 업스트림 연결 실패(Node.js 다운), 504: 응답 시간 초과(타임아웃) |
| Health Check | ALB가 /health를 주기적으로 체크 — 실패하면 Unhealthy로 태스크 교체 |
5줄 핵심
- OSI 7계층은 네트워크 문제를 계층별로 나눠서 진단하는 기준이다
- 실무에서 주로 마주치는 계층은 7(HTTP), 4(TCP/포트), 3(IP), 6(TLS)이다
- Web Server(Nginx)는 클라이언트 요청을 가장 먼저 받아 내부 서버로 전달하며 SSL 처리, 로드밸런싱을 담당한다
- 502(업스트림 연결 실패)와 504(응답 시간 초과)는 원인과 해결 방향이 다르다
- ECS Fargate 환경에서는 ALB가 Nginx 역할을 대신하고, Security Group이 포트 접근을 제어한다
11. 다음 학습 경로
섹션 제목: “11. 다음 학습 경로”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