@extends('layouts.app') @section('title', 'Nginx') @push('styles') @endpush @section('content')
기초부터 실전까지 — SAM 프로젝트의 Nginx 설정을 완전 해부하는 교육 가이드
비유: 24시간 접수 창구
웹 서버는 24시간 열려 있는 접수 창구다. 손님(클라이언트)이 "이 서류 주세요"라고 요청하면, 접수 담당자(웹 서버)가 서류 창고에서 꺼내주거나, 다른 부서(PHP-FPM)에 처리를 맡기거나, 수상한 손님을 돌려보낸다.
웹 서버의 3가지 역할: 정적 파일 서빙, 동적 요청 전달, 보안 검사
정적 파일 서빙
HTML, CSS, JS, 이미지 등
서류 창고에서 바로 꺼내줌
동적 요청 전달
PHP-FPM에 처리 위임
"다른 부서에 맡기겠습니다"
보안 검사
수상한 요청 차단
"출입 금지 명단" 확인
프로세스 기반(Apache) vs 이벤트 기반(Nginx)
| 항목 | Apache | Nginx |
|---|---|---|
| 처리 방식 | 프로세스/스레드 기반 | 이벤트 기반 (비동기) |
| 동시 접속 | 접속마다 프로세스 생성 | 하나의 워커가 수천 개 처리 |
| 메모리 사용 | 높음 (접속 비례 증가) | 낮음 (거의 일정) |
| 정적 파일 | 보통 | 매우 빠름 |
| 리버스 프록시 | 가능 (추가 모듈) | 기본 내장, 뛰어남 |
SAM이 Nginx를 선택한 이유
SAM은 5개 도메인(React, API, MNG, Sales, 5130)을 하나의 서버에서 운영한다. Nginx의 리버스 프록시와 낮은 메모리 사용량이 이 구조에 최적이다. 2코어 3.8GB RAM 서버에서도 안정적으로 동작한다.
Nginx(엔진엑스)는 2004년 러시아 개발자 Igor Sysoev가 만들었다. 당시 웹 서버들은 C10K 문제(동시 1만 개 연결 처리)를 해결하지 못했다. Apache는 접속마다 프로세스를 생성하여 메모리가 폭발했고, Nginx는 이벤트 기반 아키텍처로 이 문제를 해결했다.
비유: 식당 주문 방식의 차이
Apache: 손님마다 전담 웨이터를 배정. 100명이 오면 웨이터 100명 필요.
Nginx: 웨이터 1명이 번호표를 나눠주고, 주문이 준비되면 호출. 수천 명도 처리 가능.
2004
최초 공개
34%
글로벌 시장 점유율
C10K
해결한 핵심 문제
이벤트
비동기 처리 방식
마트료시카처럼 중첩되는 설정 블록
비유: 건물 → 층 → 방
Nginx 설정은 마트료시카 인형처럼 중첩된다. 건물 전체 규칙(main) 안에 층별 규칙(http), 층 안에 방별 규칙(server), 방 안에 공간별 규칙(location)이 있다.
계층 정리
main → events / http → server → location
상위 설정은 하위로 상속된다. 하위에서 같은 설정을 하면 덮어쓴다.
비유: 안내 데스크
Forward Proxy: 회사 직원이 외부 인터넷에 접속할 때 거치는 "사내 인터넷 관문". "나 대신 밖에 나가서 가져와줘."
Reverse Proxy: 외부 손님이 회사 내부 서비스에 접근할 때 거치는 "안내 데스크". "손님을 적절한 담당자에게 연결해드리겠습니다."
Forward Proxy는 클라이언트 대리, Reverse Proxy는 서버 대리
| 항목 | Forward Proxy | Reverse Proxy |
|---|---|---|
| 위치 | 클라이언트 쪽 | 서버 쪽 |
| 목적 | 클라이언트 익명성 | 서버 보호/로드 밸런싱 |
| 예시 | 회사 프록시, VPN | Nginx, Cloudflare |
| SAM 사용 | 사용 안 함 | 외부 Nginx가 이 역할 |
외부 Nginx가 도메인별로 각 서비스로 분배하는 구조
SAM의 외부 Nginx는 *.sam.kr 와일드카드 SSL 인증서를 사용하여 5개 도메인을 하나의 서버에서 처리한다.
각 도메인은 server_name 디렉티브로 구분되어 서로 다른 내부 서비스로 전달된다.
| 도메인 | 서비스 | 전달 방식 | 설명 |
|---|---|---|---|
| dev.sam.kr | React (Next.js) | proxy_pass http://react:3000 | 프론트엔드 SPA |
| api.sam.kr | API (Laravel) | fastcgi_pass api:9000 | REST API 서버 |
| mng.sam.kr | MNG (Laravel) | fastcgi_pass mng:9000 | 관리자 웹 |
| sales.sam.kr | Sales (PHP) | proxy_pass http://sales:80 | 영업 시스템 |
| 5130.sam.kr | 레거시 (PHP 7.3) | proxy_pass http://php73:80 | 기존 5130 시스템 |
리버스 프록시는 클라이언트의 원래 정보를 백엔드 서버에 전달해야 한다. 그렇지 않으면 백엔드는 모든 요청이 "Nginx에서 온 것"으로만 보인다.
비유: 택배 송장
안내 데스크(Nginx)가 택배를 내부 담당자에게 전달할 때, "원래 보낸 사람 이름과 주소"를 송장에 적어 붙여야 한다.
그래야 담당자가 "이 택배가 어디서 왔는지" 알 수 있다.
Host는 수신자, X-Real-IP는 발송자 주소에 해당한다.
비유: 건물 정문 경비실 + 각 층 접수 창구
SAM은 Nginx를 2단계로 사용한다. 1단계(외부 Nginx)는 건물 정문 경비실로, 어떤 손님이 어느 층으로 가야 하는지 안내한다. 2단계(내부 Nginx)는 각 층의 접수 창구로, 해당 층의 PHP-FPM에게 실제 업무를 맡긴다.
외부 Nginx(도메인 라우팅 + SSL) → 내부 Nginx(PHP-FPM 통신)
── SAM 요청 흐름 ──
사용자 → 외부 Nginx (SSL 종료, 도메인 라우팅)
├→ react:3000 (proxy_pass) → Next.js
├→ api:9000 (fastcgi_pass) → PHP-FPM
├→ mng:80 → 내부 Nginx → mng:9000 (fastcgi_pass)
├→ sales:80 → 내부 Nginx → sales:9000 (fastcgi_pass)
└→ php73:80 → 내부 Nginx → 5130:9000 (fastcgi_pass)
docker/nginx/nginx.conf — 외부 Nginx는 SSL 인증서를 관리하고 5개 도메인을 각 서비스로 라우팅한다.
글로벌 설정
React 도메인 (dev.sam.kr) — WebSocket 지원
MNG 도메인 (mng.sam.kr) — 정적 캐싱 + FastCGI
핵심 디렉티브 해설
try_files $uri $uri/ /index.php?$query_string — 파일이 있으면 서빙, 없으면 Laravel 라우터로 전달
fastcgi_pass mng:9000 — PHP-FPM 프로세스에 요청 전달 (FastCGI 프로토콜)
proxy_pass http://react:3000 — HTTP 프록시로 요청 전달 (일반 HTTP)
^~ /tenant-storage/ — 정규식보다 우선 매칭 (prefix match)
각 컨테이너 내부의 Nginx는 단순하다. listen 80으로 외부 Nginx의 요청을 받고,
fastcgi_pass 127.0.0.1:9000으로 같은 컨테이너의 PHP-FPM에 전달한다.
MNG 내부 Nginx (특수: 테넌트 스토리지)
Sales / 5130 내부 Nginx (공통 패턴)
왜 2계층인가?
Docker 컨테이너는 격리된 환경이다. 외부 Nginx가 직접 컨테이너 안의 PHP-FPM에 접근하려면
복잡한 네트워크 설정이 필요하다. 내부 Nginx를 두면 각 컨테이너가 자체적으로 요청을 처리할 수 있어 관리가 쉽다.
MNG의 테넌트 스토리지 alias처럼 컨테이너 고유의 설정도 독립적으로 관리할 수 있다.
편지를 봉투에 넣고 자물쇠로 잠그는 것 = SSL
비유: 편지 봉투와 자물쇠
HTTP는 엽서처럼 내용이 공개된 채 전달된다. 누구나 중간에 읽을 수 있다.
HTTPS는 편지를 봉투에 넣고 자물쇠(SSL)로 잠근 것이다. 열쇠를 가진 수신자만 열 수 있다.
SAM의 SSL 구조
*.sam.kr 와일드카드 인증서 하나로 모든 서브도메인을 커버한다.
SSL 종료(termination)는 외부 Nginx에서만 이루어지고, 내부 통신은 평문 HTTP를 사용한다.
Docker 내부 네트워크는 격리되어 있으므로 안전하다.
수상한 요청은 경비원이 차단
경로 트래버설 & 민감 파일 차단 (API 서버)
../ 경로 트래버설
상위 디렉토리 접근 시도
.env 환경 파일
DB 비밀번호 등 노출 위험
.git 소스 코드
전체 코드 히스토리 노출
etc/passwd 시스템
서버 사용자 정보 노출
해킹 도구 User-Agent 차단
숨김 파일 차단 (Sales/5130 내부 Nginx)
SAM의 Sales 서버에는 다음 보안 헤더가 적용되어 있다. 브라우저에게 "이렇게 동작하라"고 지시하는 보안 규칙이다.
| 헤더 | 역할 | 비유 |
|---|---|---|
| HSTS | 항상 HTTPS 강제 | "이 건물은 정문으로만 출입" |
| X-Frame-Options | iframe 삽입 방지 | "내 간판을 남의 가게에 걸지 마" |
| X-Content-Type | MIME 타입 고정 | "라벨 대로만 내용물 취급" |
| X-XSS-Protection | XSS 공격 차단 | "수상한 스크립트 자동 격리" |
캐시는 자주 쓰는 서류를 책상 위에 두는 것
비유: 냉장고에 자주 먹는 반찬 보관
매번 마트에 가서 반찬을 사오면 시간이 오래 걸린다(서버에서 매번 전송).
자주 먹는 반찬은 냉장고(브라우저 캐시)에 넣어두면 바로 꺼내먹을 수 있다.
expires 30d는 "이 반찬은 30일간 신선하다"는 유통기한 표시다.
immutable이란?
immutable은 "이 파일은 절대 변하지 않는다"는 선언이다.
브라우저가 "혹시 바뀌었나요?"라는 재검증 요청조차 보내지 않아 더 빠르다.
테넌트 파일(로고, 첨부파일)은 업로드 후 변경되지 않으므로 적합하다.
서비스마다 처리 시간이 다르므로 타임아웃도 다르게 설정한다. 타임아웃이 너무 짧으면 정상 요청도 끊기고, 너무 길면 오류 상황에서 자원이 낭비된다.
| 서비스 | 설정 | 값 | 이유 |
|---|---|---|---|
| React | proxy_read_timeout | 86400s (24시간) | HMR WebSocket 연결 유지 |
| MNG | fastcgi_read_timeout | 300s (5분) | 대량 데이터 처리, 엑셀 내보내기 |
| Sales | proxy_read_timeout | 60s (1분) | 일반 웹 페이지 응답 |
| 5130 | - | 기본값 | 레거시 (별도 설정 없음) |
WebSocket과 HTTP/2
WebSocket: React(HMR)과 Sales에서 사용. Upgrade: websocket 헤더로 HTTP 연결을 WebSocket으로 전환한다.
HTTP/2: Sales와 5130에서 활성화 (http2 on). 하나의 TCP 연결에 여러 요청을 동시에 전송하는 "한 통화에 여러 주문" 방식이다.
Nginx에서 가장 흔한 4가지 에러와 원인
502 Bad Gateway
PHP-FPM이 응답하지 않음
원인
해결
docker restart sam-api-1
504 Gateway Timeout
처리 시간 초과
원인
해결
fastcgi_read_timeout 값 확인
403 Forbidden
접근 권한 없음
원인
해결
파일 권한 확인: ls -la
413 Request Entity Too Large
업로드 크기 초과
원인
client_max_body_size 초과해결
client_max_body_size 200M;
로그 읽는 법
access_log: IP - 시간 "요청" 상태코드 바이트 "Referer" "User-Agent"
error_log: 시간 [레벨] PID#TID: *연결번호 에러메시지, client: IP, server: 도메인
레벨: emerg > alert > crit > error > warn > notice > info
1. docker ps로 컨테이너 실행 상태 확인
2. docker logs sam-api-1로 PHP-FPM 에러 확인
3. PHP-FPM이 죽었다면 docker restart sam-api-1
4. 반복된다면 PHP 메모리 제한(memory_limit) 확인
1. 브라우저 개발자 도구 → Network 탭에서 이미지 URL과 응답 코드 확인
2. 403이면 파일 권한 문제 → ls -la로 확인
3. 404면 경로 문제 → storage:link 심볼릭 링크 확인
4. MNG 테넌트 이미지면 /tenant-storage/ 프록시 설정 확인
1. 인증서 만료 확인: openssl x509 -in sam.kr.crt -noout -dates
2. 와일드카드 인증서가 해당 도메인을 커버하는지 확인
3. 인증서 파일 경로가 /etc/nginx/ssl/에 올바르게 마운트되었는지 확인
4. nginx -t로 설정 문법 오류 확인
1. Nginx client_max_body_size 확인 (SAM 기본값: 100M)
2. PHP upload_max_filesize와 post_max_size 확인
3. 외부 Nginx와 내부 Nginx 모두에서 크기 제한을 확인해야 함
4. Laravel의 validation 규칙에서도 파일 크기 제한 확인
1. nginx -t로 문법 오류 확인 (오류 시 리로드 실패)
2. nginx -s reload로 설정 리로드
3. Docker 볼륨 마운트 확인 (docker-compose.yml에서 설정 파일 경로)
4. 브라우저 캐시 때문에 안 보일 수 있음 → Ctrl+Shift+R (강력 새로고침)
Nginx의 6가지 핵심 역할 — SAM 프로젝트의 수문장
Nginx는 SAM 프로젝트의 "수문장"이다.
외부에서 들어오는 모든 요청을 받아 SSL 암호화를 처리하고,
5개 도메인을 각각의 서비스로 안내하며,
수상한 요청을 걸러내고,
정적 파일을 빠르게 서빙한다.
문제가 생기면 nginx -t로 설정을 검사하고,
error.log를 확인하는 것이 첫 단계다.