1230 lines
86 KiB
PHP
1230 lines
86 KiB
PHP
@extends('layouts.app')
|
|
|
|
@section('title', 'Nginx')
|
|
|
|
@push('styles')
|
|
<style>
|
|
/* 이미지 기본 스타일 */
|
|
.academy-img-hover {
|
|
transition: box-shadow 0.2s ease;
|
|
}
|
|
.academy-img-hover:hover {
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
.academy-img-wrap {
|
|
overflow: hidden;
|
|
border-radius: 0.75rem;
|
|
}
|
|
|
|
/* hover 프리뷰 오버레이 */
|
|
#hover-preview {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 45;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
backdrop-filter: blur(3px);
|
|
pointer-events: none;
|
|
}
|
|
#hover-preview.is-active {
|
|
display: flex;
|
|
pointer-events: none;
|
|
}
|
|
#hover-preview img {
|
|
max-height: 80vh;
|
|
max-width: 85vw;
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
|
|
opacity: 0;
|
|
transform: scale(0.3);
|
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
}
|
|
#hover-preview.is-active img {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
#hover-preview .hover-caption {
|
|
position: absolute;
|
|
bottom: 2rem;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: rgba(255,255,255,0.85);
|
|
font-size: 0.8rem;
|
|
background: rgba(0,0,0,0.5);
|
|
padding: 0.4rem 1rem;
|
|
border-radius: 2rem;
|
|
white-space: nowrap;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease 0.15s;
|
|
}
|
|
#hover-preview.is-active .hover-caption {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* 클릭 라이트박스 */
|
|
#lightbox {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 50;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
#lightbox.is-open {
|
|
display: flex;
|
|
}
|
|
#lightbox img {
|
|
max-height: 90vh;
|
|
max-width: 90vw;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
/* Nginx 설정 코드 블록 */
|
|
.nginx-code {
|
|
background: #111827;
|
|
color: #4ade80;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
font-size: 0.75rem;
|
|
line-height: 1.6;
|
|
padding: 1.25rem;
|
|
border-radius: 0.75rem;
|
|
overflow-x: auto;
|
|
border: 1px solid #1f2937;
|
|
}
|
|
.nginx-code .comment { color: #6b7280; }
|
|
.nginx-code .directive { color: #22d3ee; }
|
|
.nginx-code .value { color: #fbbf24; }
|
|
.nginx-code .block { color: #a78bfa; }
|
|
</style>
|
|
@endpush
|
|
|
|
@section('content')
|
|
<div class="max-w-6xl mx-auto">
|
|
|
|
<!-- 히어로 배너 -->
|
|
<div class="rounded-2xl overflow-hidden mb-8 shadow-lg" style="background: linear-gradient(135deg, #0e3c4a 0%, #0f172a 100%);">
|
|
<div class="flex items-center" style="flex-wrap: wrap;">
|
|
<div style="flex: 1 1 300px; padding: 2rem 2.5rem;">
|
|
<div class="flex items-center gap-2 text-sm mb-2" style="color: #22d3ee;">
|
|
<span>아카데미</span>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" /></svg>
|
|
<span style="color: #ffffff;">Nginx</span>
|
|
</div>
|
|
<h1 class="text-3xl font-bold mb-2" style="color: #ffffff;">Nginx</h1>
|
|
<p class="text-sm" style="color: #cbd5e1;">기초부터 실전까지 — SAM 프로젝트의 Nginx 설정을 완전 해부하는 교육 가이드</p>
|
|
</div>
|
|
<div class="shrink-0" style="width: 240px; padding: 1.5rem;">
|
|
<div class="overflow-hidden rounded-xl">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/1.svg') }}" alt="Nginx 히어로"
|
|
class="w-full rounded-xl cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex gap-8">
|
|
<!-- 좌측 고정 목차 (TOC) -->
|
|
<nav class="hidden lg:block shrink-0" style="width: 220px;">
|
|
<div class="sticky top-24">
|
|
<div class="bg-cyan-50 border border-cyan-200 rounded-xl p-5">
|
|
<h2 class="font-semibold text-cyan-800 mb-3 flex items-center gap-2 text-sm">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
|
목차
|
|
</h2>
|
|
<div class="space-y-0.5 text-xs">
|
|
<a href="#webserver" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium">1. 웹 서버란 무엇인가</a>
|
|
<a href="#webserver-role" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">웹 서버의 3가지 역할</a>
|
|
<a href="#webserver-compare" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">Apache vs Nginx</a>
|
|
|
|
<a href="#nginx" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium mt-2">2. Nginx란 무엇인가</a>
|
|
<a href="#nginx-history" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">탄생 배경</a>
|
|
<a href="#nginx-config" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">설정 파일 계층</a>
|
|
|
|
<a href="#reverse-proxy" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium mt-2">3. 리버스 프록시</a>
|
|
<a href="#reverse-proxy-compare" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">Forward vs Reverse</a>
|
|
<a href="#reverse-proxy-sam" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">SAM 도메인 라우팅</a>
|
|
<a href="#reverse-proxy-headers" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">프록시 헤더 해설</a>
|
|
|
|
<a href="#sam-config" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium mt-2">4. SAM 설정 해부</a>
|
|
<a href="#sam-config-arch" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">2계층 아키텍처</a>
|
|
<a href="#sam-config-external" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">외부 Nginx 분석</a>
|
|
<a href="#sam-config-internal" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">내부 Nginx 분석</a>
|
|
|
|
<a href="#security" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium mt-2">5. 보안 설정</a>
|
|
<a href="#security-ssl" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">SSL/TLS</a>
|
|
<a href="#security-filter" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">보안 필터링</a>
|
|
<a href="#security-headers" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">보안 헤더</a>
|
|
|
|
<a href="#performance" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium mt-2">6. 성능 최적화</a>
|
|
<a href="#performance-cache" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">정적 자산 캐싱</a>
|
|
<a href="#performance-timeout" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">타임아웃 설정</a>
|
|
|
|
<a href="#troubleshoot" class="block text-cyan-700 hover:text-cyan-900 py-1 font-medium mt-2">7. 트러블슈팅</a>
|
|
<a href="#troubleshoot-errors" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">4대 에러</a>
|
|
<a href="#troubleshoot-commands" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">진단 명령어</a>
|
|
<a href="#troubleshoot-faq" class="block text-cyan-600 hover:text-cyan-800 py-0.5 pl-3">FAQ</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- 우측 콘텐츠 -->
|
|
<div class="flex-1 min-w-0 space-y-10">
|
|
|
|
<!-- 모바일 목차 -->
|
|
<div class="lg:hidden bg-cyan-50 border border-cyan-200 rounded-xl p-4 mb-6">
|
|
<details>
|
|
<summary class="font-semibold text-cyan-800 text-sm cursor-pointer flex items-center gap-2">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16" /></svg>
|
|
목차 보기
|
|
</summary>
|
|
<nav class="mt-3 space-y-1 text-sm">
|
|
<a href="#webserver" class="block text-cyan-700 hover:text-cyan-900 py-1">1. 웹 서버란 무엇인가</a>
|
|
<a href="#nginx" class="block text-cyan-700 hover:text-cyan-900 py-1">2. Nginx란 무엇인가</a>
|
|
<a href="#reverse-proxy" class="block text-cyan-700 hover:text-cyan-900 py-1">3. 리버스 프록시</a>
|
|
<a href="#sam-config" class="block text-cyan-700 hover:text-cyan-900 py-1">4. SAM 설정 해부</a>
|
|
<a href="#security" class="block text-cyan-700 hover:text-cyan-900 py-1">5. 보안 설정</a>
|
|
<a href="#performance" class="block text-cyan-700 hover:text-cyan-900 py-1">6. 성능 최적화</a>
|
|
<a href="#troubleshoot" class="block text-cyan-700 hover:text-cyan-900 py-1">7. 트러블슈팅</a>
|
|
</nav>
|
|
</details>
|
|
</div>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 1. 웹 서버란 무엇인가 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="webserver" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">1</span>
|
|
웹 서버란 무엇인가
|
|
</h2>
|
|
|
|
<!-- 1-1. 웹 서버의 3가지 역할 -->
|
|
<div id="webserver-role" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
웹 서버의 3가지 역할
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 24시간 접수 창구</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
웹 서버는 <strong>24시간 열려 있는 접수 창구</strong>다. 손님(클라이언트)이 "이 서류 주세요"라고 요청하면,
|
|
접수 담당자(웹 서버)가 서류 창고에서 꺼내주거나, 다른 부서(PHP-FPM)에 처리를 맡기거나, 수상한 손님을 돌려보낸다.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/2.svg') }}" alt="웹 서버 = 접수 창구 비유"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">웹 서버의 3가지 역할: 정적 파일 서빙, 동적 요청 전달, 보안 검사</p>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 text-xs">
|
|
<div class="bg-cyan-50 rounded-lg p-3 border border-cyan-100 text-center">
|
|
<p class="font-bold text-cyan-800 mb-1">정적 파일 서빙</p>
|
|
<p class="text-cyan-700">HTML, CSS, JS, 이미지 등<br>서류 창고에서 바로 꺼내줌</p>
|
|
</div>
|
|
<div class="bg-cyan-50 rounded-lg p-3 border border-cyan-100 text-center">
|
|
<p class="font-bold text-cyan-800 mb-1">동적 요청 전달</p>
|
|
<p class="text-cyan-700">PHP-FPM에 처리 위임<br>"다른 부서에 맡기겠습니다"</p>
|
|
</div>
|
|
<div class="bg-cyan-50 rounded-lg p-3 border border-cyan-100 text-center">
|
|
<p class="font-bold text-cyan-800 mb-1">보안 검사</p>
|
|
<p class="text-cyan-700">수상한 요청 차단<br>"출입 금지 명단" 확인</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 1-2. Apache vs Nginx 비교 -->
|
|
<div id="webserver-compare" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
Apache vs Nginx 비교
|
|
</h3>
|
|
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/3.svg') }}" alt="Apache vs Nginx 비교"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">프로세스 기반(Apache) vs 이벤트 기반(Nginx)</p>
|
|
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left font-semibold text-gray-700 border-b">항목</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-orange-700 border-b">Apache</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-700 border-b">Nginx</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="border-b"><td class="px-3 py-2 font-medium">처리 방식</td><td class="px-3 py-2 text-orange-700">프로세스/스레드 기반</td><td class="px-3 py-2 text-cyan-700">이벤트 기반 (비동기)</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-medium">동시 접속</td><td class="px-3 py-2 text-orange-700">접속마다 프로세스 생성</td><td class="px-3 py-2 text-cyan-700">하나의 워커가 수천 개 처리</td></tr>
|
|
<tr class="border-b"><td class="px-3 py-2 font-medium">메모리 사용</td><td class="px-3 py-2 text-orange-700">높음 (접속 비례 증가)</td><td class="px-3 py-2 text-cyan-700">낮음 (거의 일정)</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-medium">정적 파일</td><td class="px-3 py-2 text-orange-700">보통</td><td class="px-3 py-2 text-cyan-700">매우 빠름</td></tr>
|
|
<tr><td class="px-3 py-2 font-medium">리버스 프록시</td><td class="px-3 py-2 text-orange-700">가능 (추가 모듈)</td><td class="px-3 py-2 text-cyan-700">기본 내장, 뛰어남</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="bg-cyan-50 rounded-lg p-4 border border-cyan-100">
|
|
<p class="font-semibold text-cyan-800 mb-1 text-xs">SAM이 Nginx를 선택한 이유</p>
|
|
<p class="text-xs text-cyan-700 leading-relaxed">
|
|
SAM은 5개 도메인(React, API, MNG, Sales, 5130)을 하나의 서버에서 운영한다.
|
|
Nginx의 <strong>리버스 프록시</strong>와 <strong>낮은 메모리 사용량</strong>이 이 구조에 최적이다.
|
|
2코어 3.8GB RAM 서버에서도 안정적으로 동작한다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 2. Nginx란 무엇인가 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="nginx" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">2</span>
|
|
Nginx란 무엇인가
|
|
</h2>
|
|
|
|
<!-- 2-1. 탄생 배경 -->
|
|
<div id="nginx-history" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
탄생 배경: C10K 문제
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<div class="bg-cyan-50 rounded-lg p-5 border border-cyan-100">
|
|
<p class="text-xs text-cyan-900 leading-relaxed">
|
|
<strong>Nginx</strong>(엔진엑스)는 2004년 러시아 개발자 <strong>Igor Sysoev</strong>가 만들었다.
|
|
당시 웹 서버들은 <strong>C10K 문제</strong>(동시 1만 개 연결 처리)를 해결하지 못했다.
|
|
Apache는 접속마다 프로세스를 생성하여 메모리가 폭발했고, Nginx는 이벤트 기반 아키텍처로 이 문제를 해결했다.
|
|
</p>
|
|
</div>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 식당 주문 방식의 차이</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
<strong>Apache</strong>: 손님마다 전담 웨이터를 배정. 100명이 오면 웨이터 100명 필요.<br>
|
|
<strong>Nginx</strong>: 웨이터 1명이 번호표를 나눠주고, 주문이 준비되면 호출. 수천 명도 처리 가능.
|
|
</p>
|
|
</div>
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 text-xs">
|
|
<div class="bg-gray-50 rounded-lg p-3 border text-center">
|
|
<p class="font-bold text-cyan-700 text-lg mb-1">2004</p>
|
|
<p class="text-gray-600">최초 공개</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border text-center">
|
|
<p class="font-bold text-cyan-700 text-lg mb-1">34%</p>
|
|
<p class="text-gray-600">글로벌 시장 점유율</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border text-center">
|
|
<p class="font-bold text-cyan-700 text-lg mb-1">C10K</p>
|
|
<p class="text-gray-600">해결한 핵심 문제</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border text-center">
|
|
<p class="font-bold text-cyan-700 text-lg mb-1">이벤트</p>
|
|
<p class="text-gray-600">비동기 처리 방식</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2-2. 설정 파일 계층 구조 -->
|
|
<div id="nginx-config" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
설정 파일 계층 구조
|
|
</h3>
|
|
|
|
<div class="flex flex-col lg:flex-row gap-5 items-start">
|
|
<div class="shrink-0 bg-gray-50 rounded-xl p-3 border academy-img-wrap" style="width: 280px;">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/4.svg') }}" alt="Nginx 설정 계층 구조"
|
|
class="w-full rounded-lg cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
<p class="text-xs text-gray-400 mt-2 text-center">마트료시카처럼 중첩되는 설정 블록</p>
|
|
</div>
|
|
<div class="flex-1 min-w-0 text-sm text-gray-700 space-y-3">
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 건물 → 층 → 방</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
Nginx 설정은 <strong>마트료시카 인형</strong>처럼 중첩된다.
|
|
건물 전체 규칙(main) 안에 층별 규칙(http), 층 안에 방별 규칙(server), 방 안에 공간별 규칙(location)이 있다.
|
|
</p>
|
|
</div>
|
|
<div class="nginx-code">
|
|
<span class="comment"># main 컨텍스트 (건물 전체)</span>
|
|
<span class="directive">worker_processes</span> <span class="value">auto</span>;
|
|
|
|
<span class="block">events</span> { <span class="comment"># 연결 관리 (관리사무소)</span>
|
|
<span class="directive">worker_connections</span> <span class="value">1024</span>;
|
|
}
|
|
|
|
<span class="block">http</span> { <span class="comment"># HTTP 설정 (건물 1층~)</span>
|
|
<span class="block">server</span> { <span class="comment"># 도메인별 설정 (각 방)</span>
|
|
<span class="directive">listen</span> <span class="value">443 ssl</span>;
|
|
<span class="directive">server_name</span> <span class="value">mng.sam.kr</span>;
|
|
|
|
<span class="block">location</span> <span class="value">/</span> { <span class="comment"># 경로별 설정 (방 안 공간)</span>
|
|
<span class="directive">try_files</span> <span class="value">$uri /index.php</span>;
|
|
}
|
|
}
|
|
}</div>
|
|
<div class="bg-gray-50 rounded-lg p-3 border text-xs">
|
|
<p class="font-semibold text-gray-700 mb-1">계층 정리</p>
|
|
<p class="text-gray-600"><code class="text-purple-600">main</code> → <code class="text-cyan-600">events</code> / <code class="text-cyan-600">http</code> → <code class="text-green-600">server</code> → <code class="text-amber-600">location</code></p>
|
|
<p class="text-gray-500 mt-1">상위 설정은 하위로 상속된다. 하위에서 같은 설정을 하면 덮어쓴다.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 3. 리버스 프록시 이해하기 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="reverse-proxy" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">3</span>
|
|
리버스 프록시 이해하기
|
|
</h2>
|
|
|
|
<!-- 3-1. Forward vs Reverse Proxy -->
|
|
<div id="reverse-proxy-compare" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
Forward Proxy vs Reverse Proxy
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 안내 데스크</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
<strong>Forward Proxy</strong>: 회사 직원이 외부 인터넷에 접속할 때 거치는 <strong>"사내 인터넷 관문"</strong>. "나 대신 밖에 나가서 가져와줘."<br>
|
|
<strong>Reverse Proxy</strong>: 외부 손님이 회사 내부 서비스에 접근할 때 거치는 <strong>"안내 데스크"</strong>. "손님을 적절한 담당자에게 연결해드리겠습니다."
|
|
</p>
|
|
</div>
|
|
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/5.svg') }}" alt="Forward vs Reverse Proxy 비교"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">Forward Proxy는 클라이언트 대리, Reverse Proxy는 서버 대리</p>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left font-semibold text-gray-700 border-b">항목</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-orange-700 border-b">Forward Proxy</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-700 border-b">Reverse Proxy</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="border-b"><td class="px-3 py-2 font-medium">위치</td><td class="px-3 py-2">클라이언트 쪽</td><td class="px-3 py-2">서버 쪽</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-medium">목적</td><td class="px-3 py-2">클라이언트 익명성</td><td class="px-3 py-2">서버 보호/로드 밸런싱</td></tr>
|
|
<tr class="border-b"><td class="px-3 py-2 font-medium">예시</td><td class="px-3 py-2">회사 프록시, VPN</td><td class="px-3 py-2">Nginx, Cloudflare</td></tr>
|
|
<tr><td class="px-3 py-2 font-medium">SAM 사용</td><td class="px-3 py-2 text-gray-400">사용 안 함</td><td class="px-3 py-2 text-cyan-700 font-semibold">외부 Nginx가 이 역할</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3-2. SAM 5개 도메인 라우팅 -->
|
|
<div id="reverse-proxy-sam" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
SAM 5개 도메인 라우팅
|
|
</h3>
|
|
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/6.svg') }}" alt="SAM 5개 도메인 라우팅 맵"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">외부 Nginx가 도메인별로 각 서비스로 분배하는 구조</p>
|
|
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs leading-relaxed">
|
|
SAM의 외부 Nginx는 <strong>*.sam.kr</strong> 와일드카드 SSL 인증서를 사용하여 5개 도메인을 하나의 서버에서 처리한다.
|
|
각 도메인은 <code class="text-cyan-700">server_name</code> 디렉티브로 구분되어 서로 다른 내부 서비스로 전달된다.
|
|
</p>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-cyan-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">도메인</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">서비스</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">전달 방식</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">설명</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="border-b"><td class="px-3 py-2 font-mono text-blue-600">dev.sam.kr</td><td class="px-3 py-2">React (Next.js)</td><td class="px-3 py-2"><code>proxy_pass http://react:3000</code></td><td class="px-3 py-2">프론트엔드 SPA</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-mono text-green-600">api.sam.kr</td><td class="px-3 py-2">API (Laravel)</td><td class="px-3 py-2"><code>fastcgi_pass api:9000</code></td><td class="px-3 py-2">REST API 서버</td></tr>
|
|
<tr class="border-b"><td class="px-3 py-2 font-mono text-purple-600">mng.sam.kr</td><td class="px-3 py-2">MNG (Laravel)</td><td class="px-3 py-2"><code>fastcgi_pass mng:9000</code></td><td class="px-3 py-2">관리자 웹</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-mono text-amber-600">sales.sam.kr</td><td class="px-3 py-2">Sales (PHP)</td><td class="px-3 py-2"><code>proxy_pass http://sales:80</code></td><td class="px-3 py-2">영업 시스템</td></tr>
|
|
<tr><td class="px-3 py-2 font-mono text-red-600">5130.sam.kr</td><td class="px-3 py-2">레거시 (PHP 7.3)</td><td class="px-3 py-2"><code>proxy_pass http://php73:80</code></td><td class="px-3 py-2">기존 5130 시스템</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3-3. proxy_set_header 해설 -->
|
|
<div id="reverse-proxy-headers" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
프록시 헤더 해설
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs leading-relaxed">
|
|
리버스 프록시는 클라이언트의 원래 정보를 백엔드 서버에 전달해야 한다. 그렇지 않으면 백엔드는 모든 요청이 "Nginx에서 온 것"으로만 보인다.
|
|
</p>
|
|
<div class="nginx-code">
|
|
<span class="directive">proxy_set_header</span> <span class="value">Host $host</span>; <span class="comment"># 원래 도메인명 전달</span>
|
|
<span class="directive">proxy_set_header</span> <span class="value">X-Real-IP $remote_addr</span>; <span class="comment"># 클라이언트 실제 IP</span>
|
|
<span class="directive">proxy_set_header</span> <span class="value">X-Forwarded-For $proxy_add_x_forwarded_for</span>; <span class="comment"># 거쳐온 IP 목록</span>
|
|
<span class="directive">proxy_set_header</span> <span class="value">X-Forwarded-Proto $scheme</span>; <span class="comment"># HTTP or HTTPS</span></div>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 택배 송장</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
안내 데스크(Nginx)가 택배를 내부 담당자에게 전달할 때, <strong>"원래 보낸 사람 이름과 주소"</strong>를 송장에 적어 붙여야 한다.
|
|
그래야 담당자가 "이 택배가 어디서 왔는지" 알 수 있다.
|
|
<code class="text-amber-700">Host</code>는 수신자, <code class="text-amber-700">X-Real-IP</code>는 발송자 주소에 해당한다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 4. SAM Nginx 설정 완전 해부 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="sam-config" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">4</span>
|
|
SAM Nginx 설정 완전 해부
|
|
</h2>
|
|
|
|
<!-- 4-1. 2계층 Nginx 아키텍처 -->
|
|
<div id="sam-config-arch" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
2계층 Nginx 아키텍처
|
|
</h3>
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100 mb-4">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 건물 정문 경비실 + 각 층 접수 창구</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
SAM은 Nginx를 <strong>2단계</strong>로 사용한다.
|
|
<strong>1단계(외부 Nginx)</strong>는 건물 정문 경비실로, 어떤 손님이 어느 층으로 가야 하는지 안내한다.
|
|
<strong>2단계(내부 Nginx)</strong>는 각 층의 접수 창구로, 해당 층의 PHP-FPM에게 실제 업무를 맡긴다.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/7.svg') }}" alt="2계층 Nginx 아키텍처"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">외부 Nginx(도메인 라우팅 + SSL) → 내부 Nginx(PHP-FPM 통신)</p>
|
|
|
|
<div class="bg-gray-900 rounded-xl p-4 text-xs font-mono text-gray-300 leading-relaxed mb-3">
|
|
<p class="text-cyan-400 mb-2">── SAM 요청 흐름 ──</p>
|
|
<p><span class="text-amber-400">사용자</span> → <span class="text-cyan-400">외부 Nginx</span> (SSL 종료, 도메인 라우팅)</p>
|
|
<p class="pl-8">├→ <span class="text-blue-400">react:3000</span> (proxy_pass) → Next.js</p>
|
|
<p class="pl-8">├→ <span class="text-green-400">api:9000</span> (fastcgi_pass) → PHP-FPM</p>
|
|
<p class="pl-8">├→ <span class="text-purple-400">mng:80</span> → <span class="text-purple-300">내부 Nginx</span> → mng:9000 (fastcgi_pass)</p>
|
|
<p class="pl-8">├→ <span class="text-amber-400">sales:80</span> → <span class="text-amber-300">내부 Nginx</span> → sales:9000 (fastcgi_pass)</p>
|
|
<p class="pl-8">└→ <span class="text-red-400">php73:80</span> → <span class="text-red-300">내부 Nginx</span> → 5130:9000 (fastcgi_pass)</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 4-2. 외부 Nginx 분석 -->
|
|
<div id="sam-config-external" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
외부 Nginx 설정 분석
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs leading-relaxed">
|
|
<code class="text-cyan-700">docker/nginx/nginx.conf</code> — 외부 Nginx는 SSL 인증서를 관리하고 5개 도메인을 각 서비스로 라우팅한다.
|
|
</p>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-4 mb-2">글로벌 설정</p>
|
|
<div class="nginx-code">
|
|
<span class="directive">client_max_body_size</span> <span class="value">100M</span>; <span class="comment"># 최대 업로드 크기 100MB</span>
|
|
|
|
<span class="comment"># SSL 공통 설정 (*.sam.kr 와일드카드 인증서)</span>
|
|
<span class="directive">ssl_certificate</span> <span class="value">/etc/nginx/ssl/sam.kr.crt</span>;
|
|
<span class="directive">ssl_certificate_key</span> <span class="value">/etc/nginx/ssl/sam.kr.key</span>;
|
|
<span class="directive">ssl_protocols</span> <span class="value">TLSv1.2 TLSv1.3</span>;</div>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-4 mb-2">React 도메인 (dev.sam.kr) — WebSocket 지원</p>
|
|
<div class="nginx-code">
|
|
<span class="block">server</span> {
|
|
<span class="directive">listen</span> <span class="value">443 ssl</span>;
|
|
<span class="directive">server_name</span> <span class="value">dev.sam.kr</span>;
|
|
|
|
<span class="block">location</span> <span class="value">/</span> {
|
|
<span class="directive">proxy_pass</span> <span class="value">http://react:3000</span>;
|
|
<span class="directive">proxy_http_version</span> <span class="value">1.1</span>;
|
|
<span class="directive">proxy_set_header</span> <span class="value">Upgrade $http_upgrade</span>; <span class="comment"># WebSocket (HMR용)</span>
|
|
<span class="directive">proxy_set_header</span> <span class="value">Connection "upgrade"</span>;
|
|
<span class="directive">proxy_read_timeout</span> <span class="value">86400</span>; <span class="comment"># 24시간 (개발 HMR)</span>
|
|
}
|
|
}</div>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-4 mb-2">MNG 도메인 (mng.sam.kr) — 정적 캐싱 + FastCGI</p>
|
|
<div class="nginx-code">
|
|
<span class="block">server</span> {
|
|
<span class="directive">listen</span> <span class="value">443 ssl</span>;
|
|
<span class="directive">server_name</span> <span class="value">mng.sam.kr</span>;
|
|
|
|
<span class="comment"># 테넌트 스토리지 이중 프록시</span>
|
|
<span class="block">location</span> <span class="value">^~ /tenant-storage/</span> {
|
|
<span class="directive">proxy_pass</span> <span class="value">http://mng:80/tenant-storage/</span>;
|
|
<span class="directive">expires</span> <span class="value">7d</span>;
|
|
<span class="directive">add_header</span> <span class="value">Cache-Control "public, immutable"</span>;
|
|
}
|
|
|
|
<span class="comment"># 정적 자산 30일 캐싱</span>
|
|
<span class="block">location</span> <span class="value">~* \.(js|css|png|jpg|svg|ico|woff2?)$</span> {
|
|
<span class="directive">expires</span> <span class="value">30d</span>;
|
|
<span class="directive">add_header</span> <span class="value">Cache-Control "public"</span>;
|
|
<span class="directive">access_log</span> <span class="value">off</span>;
|
|
}
|
|
|
|
<span class="comment"># PHP 처리</span>
|
|
<span class="block">location</span> <span class="value">~ \.php$</span> {
|
|
<span class="directive">fastcgi_pass</span> <span class="value">mng:9000</span>;
|
|
<span class="directive">fastcgi_read_timeout</span> <span class="value">300</span>; <span class="comment"># 5분 (장시간 작업)</span>
|
|
}
|
|
}</div>
|
|
|
|
<div class="bg-cyan-50 rounded-lg p-4 border border-cyan-100">
|
|
<p class="font-semibold text-cyan-800 mb-1 text-xs">핵심 디렉티브 해설</p>
|
|
<div class="text-xs text-cyan-700 space-y-1">
|
|
<p><code class="text-cyan-900">try_files $uri $uri/ /index.php?$query_string</code> — 파일이 있으면 서빙, 없으면 Laravel 라우터로 전달</p>
|
|
<p><code class="text-cyan-900">fastcgi_pass mng:9000</code> — PHP-FPM 프로세스에 요청 전달 (FastCGI 프로토콜)</p>
|
|
<p><code class="text-cyan-900">proxy_pass http://react:3000</code> — HTTP 프록시로 요청 전달 (일반 HTTP)</p>
|
|
<p><code class="text-cyan-900">^~ /tenant-storage/</code> — 정규식보다 우선 매칭 (prefix match)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 4-3. 내부 Nginx 분석 -->
|
|
<div id="sam-config-internal" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
내부 Nginx 설정 분석
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs leading-relaxed">
|
|
각 컨테이너 내부의 Nginx는 단순하다. <code class="text-cyan-700">listen 80</code>으로 외부 Nginx의 요청을 받고,
|
|
<code class="text-cyan-700">fastcgi_pass 127.0.0.1:9000</code>으로 같은 컨테이너의 PHP-FPM에 전달한다.
|
|
</p>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-4 mb-2">MNG 내부 Nginx (특수: 테넌트 스토리지)</p>
|
|
<div class="nginx-code">
|
|
<span class="directive">disable_symlinks</span> <span class="value">off</span>; <span class="comment"># 심볼릭 링크 허용 (Laravel storage:link)</span>
|
|
|
|
<span class="comment"># 테넌트 파일 직접 서빙 (alias 사용)</span>
|
|
<span class="block">location</span> <span class="value">/tenant-storage/</span> {
|
|
<span class="directive">alias</span> <span class="value">/var/www/shared-storage/tenants/</span>;
|
|
<span class="directive">expires</span> <span class="value">7d</span>;
|
|
<span class="directive">add_header</span> <span class="value">Cache-Control "public, immutable"</span>;
|
|
}
|
|
|
|
<span class="block">location</span> <span class="value">~ \.php$</span> {
|
|
<span class="directive">fastcgi_pass</span> <span class="value">127.0.0.1:9000</span>;
|
|
<span class="directive">fastcgi_read_timeout</span> <span class="value">300</span>;
|
|
}</div>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-4 mb-2">Sales / 5130 내부 Nginx (공통 패턴)</p>
|
|
<div class="nginx-code">
|
|
<span class="block">server</span> {
|
|
<span class="directive">listen</span> <span class="value">80</span>;
|
|
<span class="directive">root</span> <span class="value">/var/www/sales</span>;
|
|
<span class="directive">client_max_body_size</span> <span class="value">100M</span>;
|
|
|
|
<span class="block">location</span> <span class="value">~ /\.</span> {
|
|
<span class="directive">deny</span> <span class="value">all</span>; <span class="comment"># 숨김 파일(.env, .git) 접근 차단</span>
|
|
}
|
|
}</div>
|
|
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
|
|
<p class="font-semibold text-amber-800 mb-2">왜 2계층인가?</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
Docker 컨테이너는 <strong>격리된 환경</strong>이다. 외부 Nginx가 직접 컨테이너 안의 PHP-FPM에 접근하려면
|
|
복잡한 네트워크 설정이 필요하다. 내부 Nginx를 두면 <strong>각 컨테이너가 자체적으로 요청을 처리</strong>할 수 있어 관리가 쉽다.
|
|
MNG의 테넌트 스토리지 <code class="text-amber-700">alias</code>처럼 컨테이너 고유의 설정도 독립적으로 관리할 수 있다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 5. 보안 설정 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="security" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">5</span>
|
|
보안 설정
|
|
</h2>
|
|
|
|
<!-- 5-1. SSL/TLS -->
|
|
<div id="security-ssl" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
SSL/TLS 암호화
|
|
</h3>
|
|
|
|
<div class="flex flex-col lg:flex-row gap-5 items-start">
|
|
<div class="shrink-0 bg-gray-50 rounded-xl p-3 border academy-img-wrap" style="width: 280px;">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/8.svg') }}" alt="SSL/TLS 봉투 자물쇠 비유"
|
|
class="w-full rounded-lg cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
<p class="text-xs text-gray-400 mt-2 text-center">편지를 봉투에 넣고 자물쇠로 잠그는 것 = SSL</p>
|
|
</div>
|
|
<div class="flex-1 min-w-0 text-sm text-gray-700 space-y-3">
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 편지 봉투와 자물쇠</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
<strong>HTTP</strong>는 엽서처럼 내용이 공개된 채 전달된다. 누구나 중간에 읽을 수 있다.<br>
|
|
<strong>HTTPS</strong>는 편지를 봉투에 넣고 자물쇠(SSL)로 잠근 것이다. 열쇠를 가진 수신자만 열 수 있다.
|
|
</p>
|
|
</div>
|
|
<div class="nginx-code">
|
|
<span class="comment"># SAM SSL 설정 (외부 Nginx)</span>
|
|
<span class="directive">ssl_certificate</span> <span class="value">/etc/nginx/ssl/sam.kr.crt</span>; <span class="comment"># 와일드카드 인증서</span>
|
|
<span class="directive">ssl_certificate_key</span> <span class="value">/etc/nginx/ssl/sam.kr.key</span>; <span class="comment"># 개인키</span>
|
|
<span class="directive">ssl_protocols</span> <span class="value">TLSv1.2 TLSv1.3</span>; <span class="comment"># 최신 프로토콜만</span>
|
|
<span class="directive">ssl_ciphers</span> <span class="value">HIGH:!aNULL:!MD5</span>; <span class="comment"># 강력한 암호화만</span></div>
|
|
<div class="bg-cyan-50 rounded-lg p-3 border border-cyan-100 text-xs">
|
|
<p class="font-semibold text-cyan-800 mb-1">SAM의 SSL 구조</p>
|
|
<p class="text-cyan-700">
|
|
<code>*.sam.kr</code> 와일드카드 인증서 하나로 모든 서브도메인을 커버한다.
|
|
SSL 종료(termination)는 <strong>외부 Nginx에서만</strong> 이루어지고, 내부 통신은 평문 HTTP를 사용한다.
|
|
Docker 내부 네트워크는 격리되어 있으므로 안전하다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 5-2. 보안 필터링 -->
|
|
<div id="security-filter" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
보안 필터링
|
|
</h3>
|
|
|
|
<div class="flex flex-col lg:flex-row-reverse gap-5 items-start">
|
|
<div class="shrink-0 bg-gray-50 rounded-xl p-3 border academy-img-wrap" style="width: 280px;">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/9.svg') }}" alt="보안 필터 입장 불가 명단"
|
|
class="w-full rounded-lg cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
<p class="text-xs text-gray-400 mt-2 text-center">수상한 요청은 경비원이 차단</p>
|
|
</div>
|
|
<div class="flex-1 min-w-0 text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs font-semibold text-gray-800 mb-2">경로 트래버설 & 민감 파일 차단 (API 서버)</p>
|
|
<div class="nginx-code">
|
|
<span class="comment"># 악의적 경로 접근 차단</span>
|
|
<span class="block">if</span> (<span class="value">$request_uri ~* "(\.\.\/|\.\.\\|etc\/passwd|\.env|\.git|\.htaccess|\.sql|@fs\/)"</span>) {
|
|
<span class="directive">return</span> <span class="value">403</span>;
|
|
}</div>
|
|
|
|
<div class="grid grid-cols-2 gap-2 text-xs">
|
|
<div class="bg-red-50 rounded-lg p-2 border border-red-100">
|
|
<p class="font-semibold text-red-700"><code>../</code> 경로 트래버설</p>
|
|
<p class="text-red-600">상위 디렉토리 접근 시도</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-2 border border-red-100">
|
|
<p class="font-semibold text-red-700"><code>.env</code> 환경 파일</p>
|
|
<p class="text-red-600">DB 비밀번호 등 노출 위험</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-2 border border-red-100">
|
|
<p class="font-semibold text-red-700"><code>.git</code> 소스 코드</p>
|
|
<p class="text-red-600">전체 코드 히스토리 노출</p>
|
|
</div>
|
|
<div class="bg-red-50 rounded-lg p-2 border border-red-100">
|
|
<p class="font-semibold text-red-700"><code>etc/passwd</code> 시스템</p>
|
|
<p class="text-red-600">서버 사용자 정보 노출</p>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-3 mb-2">해킹 도구 User-Agent 차단</p>
|
|
<div class="nginx-code">
|
|
<span class="comment"># 자동 스캔 도구 차단</span>
|
|
<span class="block">if</span> (<span class="value">$http_user_agent ~* "(sqlmap|nikto|nmap|masscan|metasploit|nessus)"</span>) {
|
|
<span class="directive">return</span> <span class="value">403</span>;
|
|
}</div>
|
|
|
|
<p class="text-xs font-semibold text-gray-800 mt-3 mb-2">숨김 파일 차단 (Sales/5130 내부 Nginx)</p>
|
|
<div class="nginx-code">
|
|
<span class="block">location</span> <span class="value">~ /\.</span> {
|
|
<span class="directive">deny</span> <span class="value">all</span>; <span class="comment"># .env, .git, .htaccess 등 모두 차단</span>
|
|
}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 5-3. 보안 헤더 -->
|
|
<div id="security-headers" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
보안 헤더
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs leading-relaxed">
|
|
SAM의 Sales 서버에는 다음 보안 헤더가 적용되어 있다. 브라우저에게 "이렇게 동작하라"고 지시하는 보안 규칙이다.
|
|
</p>
|
|
<div class="nginx-code">
|
|
<span class="directive">add_header</span> <span class="value">Strict-Transport-Security "max-age=31536000; includeSubDomains"</span>;
|
|
<span class="directive">add_header</span> <span class="value">X-Frame-Options SAMEORIGIN</span>;
|
|
<span class="directive">add_header</span> <span class="value">X-Content-Type-Options nosniff</span>;
|
|
<span class="directive">add_header</span> <span class="value">X-XSS-Protection "1; mode=block"</span>;</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left font-semibold text-gray-700 border-b">헤더</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-gray-700 border-b">역할</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-gray-700 border-b">비유</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="border-b"><td class="px-3 py-2 font-mono text-cyan-700">HSTS</td><td class="px-3 py-2">항상 HTTPS 강제</td><td class="px-3 py-2 text-gray-500">"이 건물은 정문으로만 출입"</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-mono text-cyan-700">X-Frame-Options</td><td class="px-3 py-2">iframe 삽입 방지</td><td class="px-3 py-2 text-gray-500">"내 간판을 남의 가게에 걸지 마"</td></tr>
|
|
<tr class="border-b"><td class="px-3 py-2 font-mono text-cyan-700">X-Content-Type</td><td class="px-3 py-2">MIME 타입 고정</td><td class="px-3 py-2 text-gray-500">"라벨 대로만 내용물 취급"</td></tr>
|
|
<tr><td class="px-3 py-2 font-mono text-cyan-700">X-XSS-Protection</td><td class="px-3 py-2">XSS 공격 차단</td><td class="px-3 py-2 text-gray-500">"수상한 스크립트 자동 격리"</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 6. 성능 최적화 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="performance" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">6</span>
|
|
성능 최적화
|
|
</h2>
|
|
|
|
<!-- 6-1. 정적 자산 캐싱 -->
|
|
<div id="performance-cache" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
정적 자산 캐싱
|
|
</h3>
|
|
|
|
<div class="flex flex-col lg:flex-row gap-5 items-start">
|
|
<div class="shrink-0 bg-gray-50 rounded-xl p-3 border academy-img-wrap" style="width: 280px;">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/10.svg') }}" alt="정적 자산 캐싱 비유"
|
|
class="w-full rounded-lg cursor-pointer academy-img-hover"
|
|
onclick="openLightbox(this)">
|
|
<p class="text-xs text-gray-400 mt-2 text-center">캐시는 자주 쓰는 서류를 책상 위에 두는 것</p>
|
|
</div>
|
|
<div class="flex-1 min-w-0 text-sm text-gray-700 space-y-3">
|
|
<div class="bg-amber-50 rounded-lg p-4 border border-amber-100">
|
|
<p class="font-semibold text-amber-800 mb-2">비유: 냉장고에 자주 먹는 반찬 보관</p>
|
|
<p class="text-xs text-amber-900 leading-relaxed">
|
|
매번 마트에 가서 반찬을 사오면 시간이 오래 걸린다(서버에서 매번 전송).
|
|
자주 먹는 반찬은 <strong>냉장고(브라우저 캐시)</strong>에 넣어두면 바로 꺼내먹을 수 있다.
|
|
<code class="text-amber-700">expires 30d</code>는 "이 반찬은 30일간 신선하다"는 유통기한 표시다.
|
|
</p>
|
|
</div>
|
|
<div class="nginx-code">
|
|
<span class="comment"># 정적 자산 30일 캐싱 (API, MNG 공통)</span>
|
|
<span class="block">location</span> <span class="value">~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|map)$</span> {
|
|
<span class="directive">expires</span> <span class="value">30d</span>;
|
|
<span class="directive">add_header</span> <span class="value">Cache-Control "public"</span>;
|
|
<span class="directive">access_log</span> <span class="value">off</span>; <span class="comment"># 정적 파일은 로그 불필요</span>
|
|
}
|
|
|
|
<span class="comment"># 테넌트 스토리지 7일 캐싱 (MNG)</span>
|
|
<span class="block">location</span> <span class="value">^~ /tenant-storage/</span> {
|
|
<span class="directive">expires</span> <span class="value">7d</span>;
|
|
<span class="directive">add_header</span> <span class="value">Cache-Control "public, immutable"</span>; <span class="comment"># 변경 안 됨 보장</span>
|
|
}</div>
|
|
<div class="bg-cyan-50 rounded-lg p-3 border border-cyan-100 text-xs">
|
|
<p class="font-semibold text-cyan-800 mb-1">immutable이란?</p>
|
|
<p class="text-cyan-700">
|
|
<code>immutable</code>은 "이 파일은 절대 변하지 않는다"는 선언이다.
|
|
브라우저가 "혹시 바뀌었나요?"라는 재검증 요청조차 보내지 않아 더 빠르다.
|
|
테넌트 파일(로고, 첨부파일)은 업로드 후 변경되지 않으므로 적합하다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 6-2. 타임아웃 설정 -->
|
|
<div id="performance-timeout" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
타임아웃 설정
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<p class="text-xs leading-relaxed">
|
|
서비스마다 처리 시간이 다르므로 타임아웃도 다르게 설정한다. 타임아웃이 너무 짧으면 정상 요청도 끊기고, 너무 길면 오류 상황에서 자원이 낭비된다.
|
|
</p>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-xs border border-gray-200 rounded-lg overflow-hidden">
|
|
<thead class="bg-cyan-50">
|
|
<tr>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">서비스</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">설정</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">값</th>
|
|
<th class="px-3 py-2 text-left font-semibold text-cyan-800 border-b">이유</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr class="border-b"><td class="px-3 py-2 font-medium">React</td><td class="px-3 py-2"><code>proxy_read_timeout</code></td><td class="px-3 py-2 font-mono text-cyan-700">86400s (24시간)</td><td class="px-3 py-2">HMR WebSocket 연결 유지</td></tr>
|
|
<tr class="border-b bg-gray-50"><td class="px-3 py-2 font-medium">MNG</td><td class="px-3 py-2"><code>fastcgi_read_timeout</code></td><td class="px-3 py-2 font-mono text-cyan-700">300s (5분)</td><td class="px-3 py-2">대량 데이터 처리, 엑셀 내보내기</td></tr>
|
|
<tr class="border-b"><td class="px-3 py-2 font-medium">Sales</td><td class="px-3 py-2"><code>proxy_read_timeout</code></td><td class="px-3 py-2 font-mono text-cyan-700">60s (1분)</td><td class="px-3 py-2">일반 웹 페이지 응답</td></tr>
|
|
<tr><td class="px-3 py-2 font-medium">5130</td><td class="px-3 py-2">-</td><td class="px-3 py-2 text-gray-400">기본값</td><td class="px-3 py-2">레거시 (별도 설정 없음)</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="bg-cyan-50 rounded-lg p-4 border border-cyan-100">
|
|
<p class="font-semibold text-cyan-800 mb-1 text-xs">WebSocket과 HTTP/2</p>
|
|
<p class="text-xs text-cyan-700 leading-relaxed">
|
|
<strong>WebSocket</strong>: React(HMR)과 Sales에서 사용. <code>Upgrade: websocket</code> 헤더로 HTTP 연결을 WebSocket으로 전환한다.<br>
|
|
<strong>HTTP/2</strong>: Sales와 5130에서 활성화 (<code>http2 on</code>). 하나의 TCP 연결에 여러 요청을 동시에 전송하는 "한 통화에 여러 주문" 방식이다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{-- ============================================================ --}}
|
|
{{-- 7. 트러블슈팅 & 명령어 사전 --}}
|
|
{{-- ============================================================ --}}
|
|
<section id="troubleshoot" class="scroll-mt-20">
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6">
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center gap-3">
|
|
<span class="w-8 h-8 bg-cyan-500 text-white rounded-lg flex items-center justify-center text-sm font-bold">7</span>
|
|
트러블슈팅 & 명령어 사전
|
|
</h2>
|
|
|
|
<!-- 7-1. 4대 Nginx 에러 -->
|
|
<div id="troubleshoot-errors" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
4대 Nginx 에러
|
|
</h3>
|
|
|
|
<div class="mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/11.svg') }}" alt="502/504/403/413 에러 카드"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">Nginx에서 가장 흔한 4가지 에러와 원인</p>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 text-xs">
|
|
<div class="bg-red-50 rounded-lg p-4 border border-red-200">
|
|
<p class="font-bold text-red-700 text-lg mb-1">502 Bad Gateway</p>
|
|
<p class="text-red-600 mb-2">PHP-FPM이 응답하지 않음</p>
|
|
<div class="bg-white rounded p-2 text-gray-600">
|
|
<p class="font-semibold mb-1">원인</p>
|
|
<ul class="list-disc pl-4 space-y-0.5">
|
|
<li>PHP-FPM 프로세스가 죽었거나 미시작</li>
|
|
<li>fastcgi_pass 주소/포트 불일치</li>
|
|
<li>PHP 코드에서 메모리 초과</li>
|
|
</ul>
|
|
<p class="font-semibold mt-2 mb-1">해결</p>
|
|
<p><code class="bg-gray-100 px-1 rounded">docker restart sam-api-1</code></p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-orange-50 rounded-lg p-4 border border-orange-200">
|
|
<p class="font-bold text-orange-700 text-lg mb-1">504 Gateway Timeout</p>
|
|
<p class="text-orange-600 mb-2">처리 시간 초과</p>
|
|
<div class="bg-white rounded p-2 text-gray-600">
|
|
<p class="font-semibold mb-1">원인</p>
|
|
<ul class="list-disc pl-4 space-y-0.5">
|
|
<li>PHP 스크립트가 너무 오래 실행</li>
|
|
<li>DB 쿼리 무한 대기</li>
|
|
<li>타임아웃 설정이 너무 짧음</li>
|
|
</ul>
|
|
<p class="font-semibold mt-2 mb-1">해결</p>
|
|
<p><code class="bg-gray-100 px-1 rounded">fastcgi_read_timeout</code> 값 확인</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-purple-50 rounded-lg p-4 border border-purple-200">
|
|
<p class="font-bold text-purple-700 text-lg mb-1">403 Forbidden</p>
|
|
<p class="text-purple-600 mb-2">접근 권한 없음</p>
|
|
<div class="bg-white rounded p-2 text-gray-600">
|
|
<p class="font-semibold mb-1">원인</p>
|
|
<ul class="list-disc pl-4 space-y-0.5">
|
|
<li>파일/디렉토리 권한 부족</li>
|
|
<li>보안 필터에 의해 차단</li>
|
|
<li>directory index 설정 없음</li>
|
|
</ul>
|
|
<p class="font-semibold mt-2 mb-1">해결</p>
|
|
<p>파일 권한 확인: <code class="bg-gray-100 px-1 rounded">ls -la</code></p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-cyan-50 rounded-lg p-4 border border-cyan-200">
|
|
<p class="font-bold text-cyan-700 text-lg mb-1">413 Request Entity Too Large</p>
|
|
<p class="text-cyan-600 mb-2">업로드 크기 초과</p>
|
|
<div class="bg-white rounded p-2 text-gray-600">
|
|
<p class="font-semibold mb-1">원인</p>
|
|
<ul class="list-disc pl-4 space-y-0.5">
|
|
<li>업로드 파일이 <code>client_max_body_size</code> 초과</li>
|
|
<li>SAM 기본값: 100MB</li>
|
|
</ul>
|
|
<p class="font-semibold mt-2 mb-1">해결</p>
|
|
<p><code class="bg-gray-100 px-1 rounded">client_max_body_size 200M;</code></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 7-2. 진단 명령어 -->
|
|
<div id="troubleshoot-commands" class="scroll-mt-20 mb-8">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
진단 명령어 사전
|
|
</h3>
|
|
<div class="text-sm text-gray-700 space-y-3">
|
|
<div class="nginx-code">
|
|
<span class="comment"># 설정 문법 검사 (가장 먼저 실행)</span>
|
|
<span class="directive">nginx -t</span> <span class="comment"># syntax ok / failed</span>
|
|
<span class="directive">docker exec sam-nginx-1 nginx -t</span> <span class="comment"># Docker 환경에서</span>
|
|
|
|
<span class="comment"># 전체 설정 출력 (include 파일 포함)</span>
|
|
<span class="directive">nginx -T</span>
|
|
|
|
<span class="comment"># 에러 로그 실시간 모니터링</span>
|
|
<span class="directive">tail -f /var/log/nginx/error.log</span>
|
|
<span class="directive">docker logs -f sam-nginx-1</span> <span class="comment"># Docker 환경에서</span>
|
|
|
|
<span class="comment"># 엑세스 로그 확인</span>
|
|
<span class="directive">tail -100 /var/log/nginx/access.log</span>
|
|
|
|
<span class="comment"># Nginx 프로세스 상태</span>
|
|
<span class="directive">ps aux | grep nginx</span>
|
|
|
|
<span class="comment"># 설정 리로드 (무중단)</span>
|
|
<span class="directive">nginx -s reload</span>
|
|
<span class="directive">docker exec sam-nginx-1 nginx -s reload</span> <span class="comment"># Docker 환경에서</span></div>
|
|
|
|
<div class="bg-cyan-50 rounded-lg p-4 border border-cyan-100">
|
|
<p class="font-semibold text-cyan-800 mb-1 text-xs">로그 읽는 법</p>
|
|
<div class="text-xs text-cyan-700 space-y-1">
|
|
<p><strong>access_log</strong>: <code>IP - 시간 "요청" 상태코드 바이트 "Referer" "User-Agent"</code></p>
|
|
<p><strong>error_log</strong>: <code>시간 [레벨] PID#TID: *연결번호 에러메시지, client: IP, server: 도메인</code></p>
|
|
<p class="mt-1 text-cyan-600">레벨: <code>emerg</code> > <code>alert</code> > <code>crit</code> > <code>error</code> > <code>warn</code> > <code>notice</code> > <code>info</code></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 7-3. FAQ -->
|
|
<div id="troubleshoot-faq" class="scroll-mt-20 mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-800 mb-3 flex items-center gap-2">
|
|
<span class="w-2 h-2 bg-cyan-400 rounded-full"></span>
|
|
FAQ
|
|
</h3>
|
|
<div class="space-y-2">
|
|
<details class="bg-gray-50 rounded-lg border">
|
|
<summary class="px-4 py-3 cursor-pointer text-sm font-medium text-gray-800 hover:bg-gray-100">
|
|
Q1. 502 Bad Gateway가 발생했다. 어떻게 해야 하나?
|
|
</summary>
|
|
<div class="px-4 pb-3 text-xs text-gray-600 space-y-2">
|
|
<p>1. <code class="bg-gray-200 px-1 rounded">docker ps</code>로 컨테이너 실행 상태 확인</p>
|
|
<p>2. <code class="bg-gray-200 px-1 rounded">docker logs sam-api-1</code>로 PHP-FPM 에러 확인</p>
|
|
<p>3. PHP-FPM이 죽었다면 <code class="bg-gray-200 px-1 rounded">docker restart sam-api-1</code></p>
|
|
<p>4. 반복된다면 PHP 메모리 제한(<code>memory_limit</code>) 확인</p>
|
|
</div>
|
|
</details>
|
|
<details class="bg-gray-50 rounded-lg border">
|
|
<summary class="px-4 py-3 cursor-pointer text-sm font-medium text-gray-800 hover:bg-gray-100">
|
|
Q2. 이미지가 표시되지 않는다. (깨진 이미지)
|
|
</summary>
|
|
<div class="px-4 pb-3 text-xs text-gray-600 space-y-2">
|
|
<p>1. 브라우저 개발자 도구 → Network 탭에서 이미지 URL과 응답 코드 확인</p>
|
|
<p>2. 403이면 파일 권한 문제 → <code class="bg-gray-200 px-1 rounded">ls -la</code>로 확인</p>
|
|
<p>3. 404면 경로 문제 → <code class="bg-gray-200 px-1 rounded">storage:link</code> 심볼릭 링크 확인</p>
|
|
<p>4. MNG 테넌트 이미지면 <code>/tenant-storage/</code> 프록시 설정 확인</p>
|
|
</div>
|
|
</details>
|
|
<details class="bg-gray-50 rounded-lg border">
|
|
<summary class="px-4 py-3 cursor-pointer text-sm font-medium text-gray-800 hover:bg-gray-100">
|
|
Q3. HTTPS 인증서 오류가 나타난다.
|
|
</summary>
|
|
<div class="px-4 pb-3 text-xs text-gray-600 space-y-2">
|
|
<p>1. 인증서 만료 확인: <code class="bg-gray-200 px-1 rounded">openssl x509 -in sam.kr.crt -noout -dates</code></p>
|
|
<p>2. 와일드카드 인증서가 해당 도메인을 커버하는지 확인</p>
|
|
<p>3. 인증서 파일 경로가 <code>/etc/nginx/ssl/</code>에 올바르게 마운트되었는지 확인</p>
|
|
<p>4. <code class="bg-gray-200 px-1 rounded">nginx -t</code>로 설정 문법 오류 확인</p>
|
|
</div>
|
|
</details>
|
|
<details class="bg-gray-50 rounded-lg border">
|
|
<summary class="px-4 py-3 cursor-pointer text-sm font-medium text-gray-800 hover:bg-gray-100">
|
|
Q4. 파일 업로드가 실패한다. (413 에러)
|
|
</summary>
|
|
<div class="px-4 pb-3 text-xs text-gray-600 space-y-2">
|
|
<p>1. Nginx <code>client_max_body_size</code> 확인 (SAM 기본값: 100M)</p>
|
|
<p>2. PHP <code>upload_max_filesize</code>와 <code>post_max_size</code> 확인</p>
|
|
<p>3. 외부 Nginx와 내부 Nginx <strong>모두</strong>에서 크기 제한을 확인해야 함</p>
|
|
<p>4. Laravel의 <code>validation</code> 규칙에서도 파일 크기 제한 확인</p>
|
|
</div>
|
|
</details>
|
|
<details class="bg-gray-50 rounded-lg border">
|
|
<summary class="px-4 py-3 cursor-pointer text-sm font-medium text-gray-800 hover:bg-gray-100">
|
|
Q5. Nginx 설정을 변경했는데 반영이 안 된다.
|
|
</summary>
|
|
<div class="px-4 pb-3 text-xs text-gray-600 space-y-2">
|
|
<p>1. <code class="bg-gray-200 px-1 rounded">nginx -t</code>로 문법 오류 확인 (오류 시 리로드 실패)</p>
|
|
<p>2. <code class="bg-gray-200 px-1 rounded">nginx -s reload</code>로 설정 리로드</p>
|
|
<p>3. Docker 볼륨 마운트 확인 (docker-compose.yml에서 설정 파일 경로)</p>
|
|
<p>4. 브라우저 캐시 때문에 안 보일 수 있음 → Ctrl+Shift+R (강력 새로고침)</p>
|
|
</div>
|
|
</details>
|
|
</div>
|
|
|
|
<!-- 핵심 정리 -->
|
|
<div class="mt-6 mb-5 bg-gray-50 rounded-xl p-4 border flex justify-center academy-img-wrap">
|
|
<img src="{{ asset('images/academy/nginx-encyclopedia/12.svg') }}" alt="Nginx 핵심 정리 인포그래픽"
|
|
class="rounded-lg cursor-pointer academy-img-hover"
|
|
style="max-height: 280px; width: auto;"
|
|
onclick="openLightbox(this)">
|
|
</div>
|
|
<p class="text-xs text-gray-400 mb-4 text-center">Nginx의 6가지 핵심 역할 — SAM 프로젝트의 수문장</p>
|
|
|
|
<div class="bg-cyan-50 rounded-lg p-5 border border-cyan-100">
|
|
<h4 class="font-semibold text-cyan-800 mb-2 text-sm">핵심 정리</h4>
|
|
<p class="text-xs text-cyan-700 leading-relaxed">
|
|
Nginx는 SAM 프로젝트의 <strong>"수문장"</strong>이다.
|
|
외부에서 들어오는 모든 요청을 받아 <strong>SSL 암호화</strong>를 처리하고,
|
|
<strong>5개 도메인</strong>을 각각의 서비스로 안내하며,
|
|
<strong>수상한 요청</strong>을 걸러내고,
|
|
<strong>정적 파일</strong>을 빠르게 서빙한다.
|
|
문제가 생기면 <code class="text-cyan-900">nginx -t</code>로 설정을 검사하고,
|
|
<code class="text-cyan-900">error.log</code>를 확인하는 것이 첫 단계다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- hover 프리뷰 오버레이 -->
|
|
<div id="hover-preview">
|
|
<img id="hover-preview-img" src="" alt="">
|
|
<span class="hover-caption" id="hover-preview-caption"></span>
|
|
</div>
|
|
|
|
<!-- 클릭 라이트박스 -->
|
|
<div id="lightbox" onclick="closeLightbox()">
|
|
<button onclick="closeLightbox()" style="position:absolute; top:1rem; right:1rem; background:none; border:none; cursor:pointer; color:rgba(255,255,255,0.8); padding:0.5rem;">
|
|
<svg style="width:2rem; height:2rem;" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
|
</button>
|
|
<img id="lightbox-img" onclick="event.stopPropagation()">
|
|
</div>
|
|
<script>
|
|
(function() {
|
|
var preview = document.getElementById('hover-preview');
|
|
var previewImg = document.getElementById('hover-preview-img');
|
|
var previewCaption = document.getElementById('hover-preview-caption');
|
|
var hoverTimer = null;
|
|
var isPreviewActive = false;
|
|
var HOVER_DELAY = 350;
|
|
|
|
document.querySelectorAll('.academy-img-hover').forEach(function(img) {
|
|
img.addEventListener('mouseenter', function() {
|
|
var el = this;
|
|
hoverTimer = setTimeout(function() {
|
|
showPreview(el);
|
|
}, HOVER_DELAY);
|
|
});
|
|
|
|
img.addEventListener('mouseleave', function() {
|
|
clearTimeout(hoverTimer);
|
|
if (isPreviewActive) {
|
|
hidePreview();
|
|
}
|
|
});
|
|
});
|
|
|
|
function showPreview(el) {
|
|
previewImg.src = el.src;
|
|
previewImg.alt = el.alt || '';
|
|
var caption = el.alt || '';
|
|
var nextP = el.parentElement && el.parentElement.querySelector('p');
|
|
if (nextP) caption = nextP.textContent;
|
|
previewCaption.textContent = caption;
|
|
|
|
preview.classList.add('is-active');
|
|
isPreviewActive = true;
|
|
}
|
|
|
|
function hidePreview() {
|
|
preview.classList.remove('is-active');
|
|
isPreviewActive = false;
|
|
}
|
|
|
|
window.openLightbox = function(el) {
|
|
var lb = document.getElementById('lightbox');
|
|
var img = document.getElementById('lightbox-img');
|
|
img.src = el.src;
|
|
img.alt = el.alt;
|
|
lb.classList.add('is-open');
|
|
document.body.style.overflow = 'hidden';
|
|
hidePreview();
|
|
};
|
|
|
|
window.closeLightbox = function() {
|
|
var lb = document.getElementById('lightbox');
|
|
lb.classList.remove('is-open');
|
|
document.body.style.overflow = '';
|
|
};
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
if (isPreviewActive) hidePreview();
|
|
closeLightbox();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
@include('components.academy-glossary', ['domain' => 'nginx-encyclopedia'])
|
|
@endsection
|