first commit

main
Dohyun Lim 2026-02-03 17:34:25 +09:00
commit ac3b5b3015
40 changed files with 5996 additions and 0 deletions

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# aio2o-infrakit
aio2o의 분산된 인프라 서비스 및 설정을 단일 프로젝트로 통합하여 구축한 범용 인프라 솔루션입니다.
## 기반 오픈소스
이 프로젝트는 임도현 책임연구원의 [devspoon-web](https://github.com/devspoons/devspoon-web) 오픈소스를 기반으로 개발되었습니다.
#

11
compose/.env Normal file
View File

@ -0,0 +1,11 @@
PROJECT_DIR=o2o-castad-backend
LOG_DRIVER=json-file
LOG_OPT_MAXF=5
LOG_OPT_MAXS=100m
CELERY_BROKER_URL=redis://redis:6379/3
FLOWER_ID=admin
FLOWER_PWD=admin
POSTGRES_DB=ado3_dev
POSTGRES_USER=ado3_dev_admin
POSTGRES_PASSWORD=ado31324
MYSQL_PASSWORD=ado31324

View File

@ -0,0 +1,94 @@
services:
webserver:
build: ../docker/nginx/
logging:
driver: "${LOG_DRIVER}"
options:
max-file: "${LOG_OPT_MAXF}"
max-size: "${LOG_OPT_MAXS}"
working_dir: /application
container_name: nginx-uvicorn-webserver
volumes:
- ../www:/www
- ../script/:/script/
- ../config/web-server/conf.d/:/etc/nginx/conf.d/
- ../config/web-server/nginx_conf/nginx.conf:/etc/nginx/nginx.conf
- ../config/web-server/proxy_params:/etc/nginx/proxy_params
- ./ssl/certs/:/etc/ssl/certs/
- ./ssl/letsencrypt/:/etc/letsencrypt/
- ../log/:/log/
- ../script/logrotate/nginx/nginx:/etc/logrotate.d/nginx
ports:
- 80:80
- 443:443
# - 8000:8000
environment:
TZ: "Asia/Seoul"
restart: always
depends_on:
# - frontend-app
- uvicorn-app
uvicorn-app:
build: ../docker/gunicorn/
logging:
driver: "${LOG_DRIVER}"
options:
max-file: "${LOG_OPT_MAXF}"
max-size: "${LOG_OPT_MAXS}"
working_dir: /www/${PROJECT_DIR}
container_name: uvicorn-app
ports:
- 8000:8000
volumes:
- ../www:/www
- ../log:/log
- ../config/app-server/:/uvicorn
- ../script/logrotate/uvicorn/uvicorn:/etc/logrotate.d/uvicorn
command: bash -c "uv pip install --system --no-cache . && playwright install-deps && playwright install && exec gunicorn -c /uvicorn/gunicorn_uvicorn.conf.py "
environment:
TZ: "Asia/Seoul"
restart: always
redis:
image: redis:latest
logging:
driver: "${LOG_DRIVER}"
options:
max-file: "${LOG_OPT_MAXF}"
max-size: "${LOG_OPT_MAXS}"
container_name: redis_db
ports:
- 6379:6379
volumes:
- ./redis/data:/data
- ./redis/conf/:/usr/local/etc/redis/
labels:
- "name=redis"
- "mode=standalone"
environment:
TZ: "Asia/Seoul"
restart: always
command: redis-server /usr/local/etc/redis/redis.conf
# mysql:
# image: percona/percona-server:latest
# logging:
# driver: "${LOG_DRIVER}"
# options:
# max-file: "${LOG_OPT_MAXF}"
# max-size: "${LOG_OPT_MAXS}"
# container_name: mysql
# environment:
# MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
# TZ: Asia/Seoul
# ports:
# - "3306:3306"
# volumes:
# - ./mysql:/var/lib/mysql
# - ../config/database/mysql/my.cnf:/etc/my.cnf
# # - ../config/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
# - ../log/mysql:/var/log/mysql
# restart: always
# profiles:
# - mysql

0
compose/mysql/.gitkeep Normal file
View File

0
compose/pgdata/.gitkeep Normal file
View File

File diff suppressed because it is too large Load Diff

View File

102
compose/set_mysql_permission.sh Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env bash
# ============================================================
# set_mysql_permission.sh
# MySQL, PostgreSQL, Redis 데이터 디렉터리 권한 설정 스크립트
# ============================================================
# 스크립트 실행 경로 기준
MYSQL_DIR="./mysql"
PGDATA_DIR="./pgdata"
REDIS_DIR="./redis"
# ============================================================
# MySQL (Percona) 권한 설정
# ============================================================
echo "[MySQL] 데이터 디렉터리 권한 설정을 시작합니다..."
echo "[MySQL] 대상 디렉터리: $MYSQL_DIR"
if [ ! -d "$MYSQL_DIR" ]; then
echo "[MySQL] $MYSQL_DIR 디렉터리가 존재하지 않아 새로 생성합니다."
sudo mkdir -p "$MYSQL_DIR"
fi
# Percona의 mysql 유저 UID는 보통 1001
MYSQL_UID=1001
MYSQL_GID=1001
echo "[MySQL] 소유권을 $MYSQL_UID:$MYSQL_GID 로 변경합니다..."
sudo chown -R ${MYSQL_UID}:${MYSQL_GID} "$MYSQL_DIR"
echo "[MySQL] 권한을 750 (rwxr-x---) 으로 설정합니다..."
sudo chmod -R 750 "$MYSQL_DIR"
echo "[MySQL] 적용 결과:"
ls -ld "$MYSQL_DIR"
# ============================================================
# PostgreSQL 권한 설정
# ============================================================
echo ""
echo "[PostgreSQL] 데이터 디렉터리 권한 설정을 시작합니다..."
echo "[PostgreSQL] 대상 디렉터리: $PGDATA_DIR"
if [ ! -d "$PGDATA_DIR" ]; then
echo "[PostgreSQL] $PGDATA_DIR 디렉터리가 존재하지 않아 새로 생성합니다."
sudo mkdir -p "$PGDATA_DIR"
fi
# PostgreSQL의 postgres 유저 UID는 보통 999 (공식 Docker 이미지 기준)
POSTGRES_UID=999
POSTGRES_GID=999
echo "[PostgreSQL] 소유권을 $POSTGRES_UID:$POSTGRES_GID 로 변경합니다..."
sudo chown -R ${POSTGRES_UID}:${POSTGRES_GID} "$PGDATA_DIR"
echo "[PostgreSQL] 권한을 700 (rwx------) 으로 설정합니다..."
sudo chmod -R 700 "$PGDATA_DIR"
echo "[PostgreSQL] 적용 결과:"
ls -ld "$PGDATA_DIR"
# ============================================================
# Redis 권한 설정
# ============================================================
echo ""
echo "[Redis] 데이터 디렉터리 권한 설정을 시작합니다..."
echo "[Redis] 대상 디렉터리: $REDIS_DIR"
if [ ! -d "$REDIS_DIR" ]; then
echo "[Redis] $REDIS_DIR 디렉터리가 존재하지 않아 새로 생성합니다."
sudo mkdir -p "$REDIS_DIR/data"
sudo mkdir -p "$REDIS_DIR/conf"
fi
if [ ! -d "$REDIS_DIR/data" ]; then
echo "[Redis] $REDIS_DIR/data 디렉터리가 존재하지 않아 새로 생성합니다."
sudo mkdir -p "$REDIS_DIR/data"
fi
if [ ! -d "$REDIS_DIR/conf" ]; then
echo "[Redis] $REDIS_DIR/conf 디렉터리가 존재하지 않아 새로 생성합니다."
sudo mkdir -p "$REDIS_DIR/conf"
fi
# Redis의 redis 유저 UID는 보통 999 (공식 Docker 이미지 기준)
REDIS_UID=999
REDIS_GID=999
echo "[Redis] 소유권을 $REDIS_UID:$REDIS_GID 로 변경합니다..."
sudo chown -R ${REDIS_UID}:${REDIS_GID} "$REDIS_DIR"
echo "[Redis] 권한을 750 (rwxr-x---) 으로 설정합니다..."
sudo chmod -R 750 "$REDIS_DIR"
echo "[Redis] 적용 결과:"
ls -ld "$REDIS_DIR"
ls -la "$REDIS_DIR"
# ============================================================
# 완료
# ============================================================
echo ""
echo "[완료] 모든 권한 설정이 완료되었습니다."

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,256 @@
"""
Gunicorn Configuration for Production FastAPI Service
=====================================================
환경: Production Level
서버 사양: 쿼드코어 CPU, 4GB RAM, ~50 req/s
백엔드: FastAPI REST API with Uvicorn Workers
프록시: Nginx (SSL/TLS, DDoS, Rate Limiting 처리)
=====================================================
"""
# ============================================================================
# 네트워크 바인딩 설정
# ============================================================================
# 바인딩 주소 및 포트
# 역할: Gunicorn이 수신할 네트워크 주소
# 0.0.0.0:8000 - 모든 네트워크 인터페이스에서 수신 (Docker/컨테이너 환경)
# 127.0.0.1:8000 - 로컬 전용 (Nginx와 동일 호스트, 보안 강화)
# Nginx 연동: Nginx가 외부 트래픽 처리, Gunicorn은 내부 전용
bind = "0.0.0.0:8000"
# ============================================================================
# Worker 프로세스 설정
# ============================================================================
# Worker 수
# 역할: 동시 요청 처리를 위한 프로세스 수
# 공식: I/O 집약적 작업 = (CPU 코어 * 2) + 1
# 계산: 쿼드코어(4) → 9개 권장, 하지만 4GB RAM 제약으로 4개 설정
# 각 워커 메모리: 200-500MB
# 4 워커 × 500MB = 2GB (시스템 예비 2GB 확보)
# 성능: ~50 req/s는 4개 워커로 충분 (워커당 ~12.5 req/s)
# 환경 변수: GUNICORN_WORKERS=6 으로 오버라이드 가능
# workers = multiprocessing.cpu_count() * 2 + 1
workers = 4
# Worker 클래스
# 역할: ASGI 애플리케이션(FastAPI) 처리를 위한 Worker 타입
# uvicorn.workers.UvicornWorker: 비동기 I/O, uvloop 이벤트 루프
# 기대 효과: sync 워커 대비 2-5배 높은 동시성, 메모리 효율적
worker_class = "uvicorn.workers.UvicornWorker"
# Worker 동시 연결 수 (주석 처리 - UvicornWorker는 이 설정 미사용)
# Nginx가 앞단에서 연결 관리하므로 불필요
# worker_connections = 1000
# ============================================================================
# 프로세스 관리 설정
# ============================================================================
# 데몬 모드
# 역할: 백그라운드 실행 여부
# False: systemd/Docker가 프로세스 관리 (현대적 방식)
# True: 수동 관리 시 사용 (pidfile 필수)
daemon = False
# PID 파일 (주석 처리 - systemd/Docker 사용 시 불필요)
# 역할: Master 프로세스 ID 저장
# 수동 관리 시 활성화: pidfile = '/var/run/gunicorn/gunicorn.pid'
# pidfile = '/tmp/gunicorn.pid'
# ============================================================================
# ASGI 애플리케이션 경로
# ============================================================================
# ASGI 애플리케이션 경로
# 역할: Gunicorn이 실행할 FastAPI 앱 지정
# 형식: "모듈경로:변수명"
# 예: config.asgi:application (Django 스타일)
# main:app (FastAPI 기본)
#wsgi_app = "main:app"
wsgi_app = "main:app"
# ============================================================================
# 타임아웃 설정
# ============================================================================
# Worker 타임아웃
# 역할: Worker가 요청 처리 최대 허용 시간 (초)
# 동작: 타임아웃 초과 시 Worker 강제 종료 후 재시작
# 계산: FastAPI REST API 평균 응답 1-5초
# 파일 업로드 고려 (100MB / 10Mbps = 80초)
# 비디오 생성, 이미지 처리 등 장기 실행 작업 고려
# Nginx 연동: proxy_read_timeout(300s)과 동일하게 설정
# 300초: 장기 실행 API (비디오 생성, 이미지 업로드 등) 지원
timeout = 300
# Keep-Alive 타임아웃
# 역할: HTTP Keep-Alive 연결 유지 시간 (초)
# 동작: 연결 재사용으로 핸드셰이크 오버헤드 감소
# Nginx 연동: Nginx keepalive_timeout(30s)보다 짧게 설정
# Nginx가 먼저 종료하도록 하여 리소스 효율화
# 2초: Nginx 앞단에서 연결 관리하므로 짧게 설정
keepalive = 2
# Graceful 종료 타임아웃
# 역할: Worker 재시작/종료 시 진행 중인 요청 완료 대기 시간 (초)
# 동작: SIGTERM 수신 후 새 요청 거부, 기존 요청 처리
# 타임아웃 초과 시 SIGKILL로 강제 종료
# 기대 효과: Graceful reload로 무중단 배포
# kill -HUP <pid> 또는 systemctl reload gunicorn
# timeout과 동일하게 설정
graceful_timeout = 300
# ============================================================================
# 프로세스 리소스 관리 (메모리 누수 방지)
# ============================================================================
# Worker 최대 요청 수
# 역할: Worker가 처리할 최대 요청 후 자동 재시작
# 목적: 메모리 누수 방어, 장기 운영 안정성
# 동작: max_requests 도달 시 Worker graceful 종료 후 재시작
# 계산: ~50 req/s 기준
# 1000으로 설정 시 워커당 20초마다 재시작 (1000/50)
# 메모리 누수 우려 시 유지, 안정적이면 5000-10000 증가 가능
# 모니터링: htop으로 워커 메모리 사용량 추이 확인
max_requests = 1000
# 최대 요청 수 Jitter
# 역할: max_requests에 랜덤성 추가
# 목적: 모든 Worker가 동시에 재시작하는 것 방지
# 동작: 실제 재시작 = max_requests ± random(0, jitter)
# 기대 효과: 재시작 부하 분산, 서비스 연속성 보장
# 권장: max_requests의 5-10%
max_requests_jitter = 50
# ============================================================================
# 애플리케이션 로딩 설정
# ============================================================================
# 애플리케이션 사전 로딩
# 역할: Worker fork 전 앱 로딩 방식 결정
# False: 각 Worker가 독립적으로 앱 로딩
# - 장점: Graceful reload 가능 (무중단 배포)
# - 단점: 메모리 중복 사용 (Worker 수만큼)
# True: Master 프로세스에서 앱 로딩 후 Worker fork
# - 장점: 메모리 20-40% 절감 (Copy-on-Write)
# - 단점: reload 시 전체 재시작 필요 (다운타임)
# 프로덕션 권장: False (무중단 배포 우선)
preload_app = False
# 코드 변경 감지 자동 재시작
# 역할: 파일 변경 시 Worker 자동 재시작
# 성능 영향: CPU 5-10% 오버헤드, 메모리 50-100MB 추가
# 보안: 예기치 않은 재시작으로 서비스 불안정
# **프로덕션에서는 반드시 False**
# 개발 환경에서만 True 사용
# 배포: CI/CD 파이프라인에서 명시적 재시작
reload = False
# ============================================================================
# 로깅 설정
# ============================================================================
# 애플리케이션 출력 캡처
# 역할: FastAPI의 print(), logging을 Gunicorn 로그로 리다이렉트
# False: 앱 로거가 독립적으로 관리 (권장)
# True: stdout/stderr를 errorlog로 통합
# FastAPI 권장: False (자체 로거 사용)
capture_output = False
# 로그 레벨
# 역할: 출력할 로그의 최소 수준
# 레벨: critical > error > warning > info > debug
# info: 일반 정보 + 에러 (프로덕션 권장)
# warning: 경고 이상만 (로그 양 감소)
# debug: 모든 세부 정보 (성능 저하, 개발용)
loglevel = "info"
# 액세스 로그 파일
# 역할: HTTP 요청 로그 저장
# 형식: 시간별 로그 파일 생성 (access_2025-01-05_14.log)
# 로테이션: 시간별 자동 분리, logrotate 추가 권장
# 성능: 버퍼링으로 I/O 최적화
# Docker/K8s: "-" 사용으로 stdout 출력 권장
accesslog = "/log/uvicorn/uvicorn_access.log"
# 에러 로그 파일
# 역할: Gunicorn 에러, Worker 크래시 로그
# 포함: Worker 타임아웃, 메모리 에러, 예외 등
# 모니터링: 장애 탐지를 위한 핵심 로그
errorlog = "/log/uvicorn/uvicorn_error.log"
# 액세스 로그 포맷 (선택사항, 필요 시 주석 해제)
# 역할: 요청 로그 형식 커스터마이징
# 기본 포맷으로도 충분, 상세 정보 필요 시 활성화
access_log_format = (
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s %(p)s'
)
# 워커 프로세스 이름 설정 (모니터링 시 유용)
proc_name = "fastapi_gunicorn"
# ==========================================
# 성능 최적화
# ==========================================
# - 최대 2048개의 연결이 Accept Queue에서 대기 가능
# - 동시에 많은 연결 요청이 들어와도 2048개까지는 거부되지 않음
# - 실제 적용값 = min(2048, 시스템 somaxconn)
# 낮은 트래픽 (기본값 충분)
# backlog = 2048 # 기본값 사용
# workers = 4
#
# 높은 트래픽 (증가 필요)
# backlog = 4096
# workers = 8
# OS 설정도 함께 조정 필요
# /etc/sysctl.conf
# net.core.somaxconn = 4096
#
# 증가해야 할 때:
# ss -lnt 명령으로 Send-Q가 계속 가득 찬 경우
# ss -lnt | grep :8000
# 순간적인 트래픽 급증이 예상되는 경우
# connection refused 에러가 자주 발생하는 경우
backlog = 2048
# 임시 파일 디렉토리 (업로드 처리 시 사용)
# 재부팅 시 자동 삭제
# Gunicorn 기본값
# worker_tmp_dir = None # /tmp 디렉토리 사용 (디스크 기반)
# 사용하는 경우 (권장):
# 파일 업로드가 많은 서비스
# 큰 요청/응답 처리
# 충분한 RAM이 있는 경우
# RAM이 부족한 경우
# worker_tmp_dir = "/tmp" # 디스크 사용
# 파일 업로드/다운로드가 거의 없는 경우
# worker_tmp_dir = None # 기본값 사용
# Docker 사용시 고려 필요
# shm_size: '2gb' # /dev/shm 크기 증가
# volumes:
# - /dev/shm:/dev/shm # 호스트 공유 (선택)
# worker_tmp_dir = "/dev/shm" # RAM 기반 tmpfs 사용으로 I/O 성능 향상
worker_tmp_dir = None
"""
배포 체크리스트:
[권장]
1. 로그 로테이션 설정 (logrotate)
2. 헬스체크 엔드포인트 구현 (/health)
3. 성능 테스트 (wrk, locust)
성능 모니터링:
# 프로세스 확인
ps aux | grep gunicorn
# 리소스 사용량
htop -p $(pgrep -d',' gunicorn)
# 로그 실시간 확인
tail -f /log/uvicorn/access_*.log
tail -f /log/uvicorn/error_*.log
설정 최적화 가이드:
- workers: CPU 사용률 80% 이하 유지
- timeout: 응답 시간 + 여유분 (평균 * 2)
- max_requests: 메모리 누수 없으면 5000-10000으로 증가
- keepalive: Nginx keepalive_timeout보다 짧게
"""

View File

@ -0,0 +1,5 @@
-- 1. admin 권한 계정 생성
CREATE USER IF NOT EXISTS 'devadmin'@'%' IDENTIFIED BY 'test!';
-- 2. 전체 권한 부여
GRANT ALL PRIVILEGES ON *.* TO 'devadmin'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

View File

@ -0,0 +1,546 @@
# ========================================================================
# Percona Server / MySQL 8.0 최적화 설정
# 하드웨어 사양: 4코어 CPU, 4GB RAM, SSD, 1GB LAN
# ========================================================================
[mysqld]
# ------------------------------------------------------------------------
# 기본 경로 설정
# ------------------------------------------------------------------------
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
pid-file=/var/run/mysqld/mysqld.pid
# ------------------------------------------------------------------------
# 네트워크 및 연결 설정
# ------------------------------------------------------------------------
# 바인드 주소
bind-address = 0.0.0.0
# 기본값: 127.0.0.1 (로컬만)
# 변경값: 0.0.0.0 (모든 IP)
# 목적: 원격 접속 허용
# 보안: 방화벽 설정 필수
# 포트
port = 3306
# 기본값: 3306
# MySQL 표준 포트
# 최대 연결 수
max_connections = 200
# 기본값: 151
# 변경값: 200
# 목적: 4GB RAM 환경에서 충분한 연결 수 제공
# 계산: 각 연결당 약 4-8MB 메모리 사용
# 200 연결 = 최대 1.6GB 메모리 (버퍼 포함)
# 참고: 연결 풀링(ProxySQL, MaxScale) 사용 시 더 효율적
max_connect_errors = 1000000
# 기본값: 100
# 변경값: 1000000
# 목적: 연결 오류로 인한 호스트 차단 방지
# 성능: 네트워크 이슈로 인한 불필요한 차단 감소
# 대기 시간 설정
wait_timeout = 600
# 기본값: 28800 (8시간)
# 변경값: 600 (10분)
# 목적: 유휴 연결 자동 정리
# 성능: 불필요한 연결 점유 방지
interactive_timeout = 600
# 기본값: 28800
# 변경값: 600
# 목적: 대화형 클라이언트 타임아웃
# 성능: 리소스 효율적 관리
connect_timeout = 10
# 기본값: 10
# 유지 이유: 연결 시도 타임아웃
# 스레드 캐시
thread_cache_size = 50
# 기본값: 8
# 변경값: 50
# 목적: 스레드 재사용으로 연결 생성 오버헤드 감소
# 성능: 연결 빈도가 높은 환경에서 효과적
# 계산: max_connections의 약 25%
# 백로그 큐
back_log = 512
# 기본값: 80
# 변경값: 512
# 목적: 대기 중인 연결 요청 큐 크기
# 성능: 트래픽 버스트 시 연결 손실 방지
# ------------------------------------------------------------------------
# InnoDB 버퍼 풀 설정 (가장 중요!)
# ------------------------------------------------------------------------
innodb_buffer_pool_size = 2G
# 기본값: 128MB
# 변경값: 2GB (전체 RAM 4GB의 50%)
# 목적: 데이터와 인덱스를 메모리에 캐싱
# 성능: 디스크 I/O를 크게 감소시키는 가장 중요한 설정
# 권장: 전용 서버는 RAM의 70-80%, 혼합 환경은 50-60%
# 계산: 2GB buffer pool + 1GB 연결/쿼리 + 1GB OS/기타
innodb_buffer_pool_instances = 4
# 기본값: 1 (또는 자동)
# 변경값: 4
# 목적: 버퍼 풀을 여러 인스턴스로 분할하여 동시성 향상
# 성능: 멀티 코어 환경에서 잠금 경합 감소
# 권장: CPU 코어 수와 동일하게 설정
# 참고: buffer_pool_size >= 1GB일 때만 효과적
innodb_buffer_pool_chunk_size = 128M
# 기본값: 128MB
# 유지 이유: buffer_pool_size가 instances × chunk_size의 배수여야 함
# 계산: 2GB = 4 instances × 4 chunks × 128MB
# ------------------------------------------------------------------------
# InnoDB 로그 설정
# ------------------------------------------------------------------------
innodb_log_file_size = 512M
# 기본값: 48MB
# 변경값: 512MB
# 목적: Redo 로그 파일 크기
# 성능: 쓰기 집약적 워크로드에서 체크포인트 빈도 감소
# 권장: buffer_pool_size의 25% 정도
# 주의: 너무 크면 크래시 복구 시간 증가
innodb_log_buffer_size = 32M
# 기본값: 16MB
# 변경값: 32MB
# 목적: Redo 로그 버퍼
# 성능: 디스크 쓰기 전 로그를 메모리에 버퍼링
# 권장: 대용량 트랜잭션이 많으면 증가
innodb_flush_log_at_trx_commit = 1
# 기본값: 1
# 유지 이유: ACID 보장 (데이터 무결성)
# 옵션:
# 0: 로그를 메모리에만 (속도↑, 안정성↓↓)
# 1: 매 커밋마다 디스크에 flush (속도↓, 안정성↑↑) ← 권장
# 2: OS 캐시까지만 (속도↑, 안정성↑)
# 주의: 성능을 위해 2로 변경 가능하나 크래시 시 1초 데이터 손실
innodb_flush_method = O_DIRECT
# 기본값: fsync (Linux)
# 변경값: O_DIRECT
# 목적: OS 파일 시스템 캐시 우회
# 성능: 이중 버퍼링 방지, SSD 환경에서 효과적
# 권장: SSD 사용 시 필수 설정
# ------------------------------------------------------------------------
# InnoDB I/O 설정 (SSD 최적화)
# ------------------------------------------------------------------------
innodb_io_capacity = 2000
# 기본값: 200 (HDD 기준)
# 변경값: 2000
# 목적: InnoDB가 초당 수행할 수 있는 I/O 작업 수
# 성능: SSD의 높은 IOPS 활용
# 권장: SSD는 2000-5000, NVMe는 10000+
# 측정: fio 벤치마크로 실제 IOPS 측정 후 70% 수준으로 설정
innodb_io_capacity_max = 4000
# 기본값: 2000
# 변경값: 4000 (io_capacity의 2배)
# 목적: 긴급 상황(체크포인트 등)에서 최대 I/O
# 성능: 버스트 상황에서 더 많은 I/O 허용
innodb_read_io_threads = 4
# 기본값: 4
# 유지 이유: CPU 코어 수와 일치
# 목적: 읽기 작업을 위한 I/O 스레드
innodb_write_io_threads = 4
# 기본값: 4
# 유지 이유: CPU 코어 수와 일치
# 목적: 쓰기 작업을 위한 I/O 스레드
innodb_flush_neighbors = 0
# 기본값: 1 (HDD 최적화)
# 변경값: 0
# 목적: 인접 페이지 flush 비활성화
# 성능: SSD는 랜덤 쓰기가 빠르므로 불필요
# 권장: SSD 환경에서는 반드시 0으로 설정
# ------------------------------------------------------------------------
# InnoDB 동시성 설정
# ------------------------------------------------------------------------
innodb_thread_concurrency = 0
# 기본값: 0 (무제한)
# 유지 이유: MySQL이 자동으로 최적화
# 목적: 동시 실행 스레드 수 제한
# 참고: 특정 워크로드에서 제한이 필요한 경우 CPU 코어 수 × 2
innodb_lock_wait_timeout = 50
# 기본값: 50
# 유지 이유: 락 대기 타임아웃 (초)
# 성능: 데드락 상황 빠른 감지
# ------------------------------------------------------------------------
# 테이블 및 파일 설정
# ------------------------------------------------------------------------
innodb_file_per_table = ON
# 기본값: ON (MySQL 5.6.6+)
# 유지 이유: 테이블별로 별도 파일 생성
# 성능: 테이블 삭제 시 공간 즉시 반환, 관리 용이
innodb_open_files = 2000
# 기본값: 300
# 변경값: 2000
# 목적: InnoDB가 동시에 열 수 있는 파일 수
# 성능: 많은 테이블이 있을 때 파일 열기 오버헤드 감소
table_open_cache = 4000
# 기본값: 2000
# 변경값: 4000
# 목적: 열린 테이블 캐시
# 성능: 테이블 열기/닫기 오버헤드 감소
# 계산: max_connections × 평균 조인 테이블 수
table_open_cache_instances = 16
# 기본값: 16
# 유지 이유: 캐시를 여러 인스턴스로 분할
# 성능: 동시성 향상
table_definition_cache = 2000
# 기본값: 400
# 변경값: 2000
# 목적: 테이블 정의 캐시 (.frm 파일)
# 성능: 테이블 메타데이터 접근 속도 향상
# ------------------------------------------------------------------------
# 쿼리 캐시 (MySQL 8.0에서는 제거됨)
# ------------------------------------------------------------------------
# MySQL 8.0에서는 쿼리 캐시가 제거되었습니다.
# 대신 애플리케이션 레벨 캐싱(Redis, Memcached) 사용 권장
# ------------------------------------------------------------------------
# 임시 테이블 설정
# ------------------------------------------------------------------------
tmp_table_size = 64M
# 기본값: 16MB
# 변경값: 64MB
# 목적: 메모리 내 임시 테이블 최대 크기
# 성능: 복잡한 쿼리의 임시 테이블을 메모리에 유지
# 주의: max_heap_table_size와 함께 설정
max_heap_table_size = 64M
# 기본값: 16MB
# 변경값: 64MB
# 목적: MEMORY 테이블 최대 크기
# 성능: tmp_table_size와 동일하게 설정
# ------------------------------------------------------------------------
# 정렬 및 조인 버퍼
# ------------------------------------------------------------------------
sort_buffer_size = 4M
# 기본값: 256KB
# 변경값: 4MB
# 목적: 정렬 작업에 사용되는 버퍼
# 성능: ORDER BY, GROUP BY 성능 향상
# 주의: 세션별로 할당되므로 너무 크면 메모리 부족
# 계산: 200 연결 × 4MB = 최대 800MB
read_buffer_size = 2M
# 기본값: 128KB
# 변경값: 2MB
# 목적: 순차 스캔 버퍼
# 성능: 전체 테이블 스캔 시 성능 향상
read_rnd_buffer_size = 4M
# 기본값: 256KB
# 변경값: 4MB
# 목적: 정렬 후 행 읽기 버퍼
# 성능: ORDER BY 후 행 검색 속도 향상
join_buffer_size = 4M
# 기본값: 256KB
# 변경값: 4MB
# 목적: 인덱스를 사용하지 않는 조인 버퍼
# 성능: 조인 성능 향상
# ------------------------------------------------------------------------
# 바이너리 로그 설정
# ------------------------------------------------------------------------
# 바이너리 로그 활성화 (복제 및 Point-in-Time 복구에 필수)
# log_bin = /var/lib/mysql/mysql-bin
# 목적: 데이터 변경 사항 기록
# 용도: 복제(Replication), 백업, 복구
# 참고: 복제를 사용하지 않으면 disable_log_bin 설정 가능
server_id = 1
# 기본값: 1
# 목적: 복제 환경에서 서버 식별자
# 참고: 각 서버마다 고유한 값 필요
binlog_format = ROW
# 기본값: ROW (MySQL 8.0+)
# 옵션:
# STATEMENT: SQL 문 저장 (크기↓, 안정성↓)
# ROW: 실제 행 변경 저장 (크기↑, 안정성↑) ← 권장
# MIXED: 자동 선택
# 권장: ROW (가장 안전하고 일관성 있음)
binlog_expire_logs_seconds = 604800
# 기본값: 2592000 (30일)
# 변경값: 604800 (7일)
# 목적: 오래된 바이너리 로그 자동 삭제
# 성능: 디스크 공간 관리
# 참고: 백업 주기에 따라 조정
max_binlog_size = 100M
# 기본값: 1GB
# 변경값: 100MB
# 목적: 단일 바이너리 로그 파일 최대 크기
# 성능: 작은 파일로 관리 용이성 향상
sync_binlog = 1
# 기본값: 1
# 유지 이유: 매 커밋마다 바이너리 로그를 디스크에 동기화
# 성능: 안정성 최우선 (크래시 시 데이터 손실 방지)
# 참고: 성능이 중요하면 0으로 설정 가능하나 권장하지 않음
# ------------------------------------------------------------------------
# 에러 로그 설정
# ------------------------------------------------------------------------
# log_error = /var/log/mysql/error.log
log_error = /var/log/mysqld.log
# 목적: 에러 로그 파일 위치
# 참고: 디렉토리가 존재하고 mysql 사용자가 쓰기 권한 필요
log_error_verbosity = 2
# 기본값: 2
# 옵션: 1 (오류만), 2 (오류+경고), 3 (오류+경고+정보)
# 권장: 2 (운영 환경)
# ------------------------------------------------------------------------
# 슬로우 쿼리 로그
# ------------------------------------------------------------------------
# slow_query_log = 1
# 기본값: 0 (비활성화)
# 변경값: 1 (활성화)
# 목적: 느린 쿼리 기록
# 성능: 쿼리 최적화에 필수
slow_query_log_file = /var/log/mysql/slow-query.log
# 목적: 슬로우 쿼리 로그 파일 위치
long_query_time = 2
# 기본값: 10
# 변경값: 2
# 목적: 2초 이상 걸리는 쿼리 기록
# 권장: 1-2초 (애플리케이션 특성에 따라 조정)
log_queries_not_using_indexes = 1
# 기본값: 0
# 변경값: 1
# 목적: 인덱스를 사용하지 않는 쿼리도 기록
# 성능: 인덱스 누락 쿼리 발견
# ------------------------------------------------------------------------
# 일반 쿼리 로그 (개발 환경에서만 사용)
# ------------------------------------------------------------------------
# 운영 환경에서는 비활성화 권장 (과도한 로그 생성)
# general_log = 0
# general_log_file = /var/log/mysql/general.log
# ------------------------------------------------------------------------
# 문자셋 및 콜레이션
# ------------------------------------------------------------------------
character_set_server = utf8mb4
# 기본값: utf8mb4 (MySQL 8.0+)
# 목적: 서버 기본 문자셋
# 참고: 이모지 등 4바이트 문자 지원
collation_server = utf8mb4_unicode_ci
# 기본값: utf8mb4_0900_ai_ci (MySQL 8.0+)
# 변경값: utf8mb4_unicode_ci
# 목적: 다국어 정렬 규칙
# 참고: 호환성을 위해 unicode_ci 사용
# ------------------------------------------------------------------------
# SQL 모드
# ------------------------------------------------------------------------
sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
# 기본값: STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION,... (MySQL 8.0+)
# 목적: SQL 엄격 모드 설정
# 권장: STRICT_TRANS_TABLES (데이터 무결성)
# 참고: 레거시 애플리케이션은 모드 조정 필요
# ------------------------------------------------------------------------
# 타임존
# ------------------------------------------------------------------------
# default_time_zone = '+09:00'
# 기본값: SYSTEM
# 변경값: '+09:00' (한국 시간)
# 목적: 서버 타임존 설정
# 참고: 글로벌 서비스는 '+00:00' (UTC) 권장
# ------------------------------------------------------------------------
# 성능 스키마 (Performance Schema)
# ------------------------------------------------------------------------
performance_schema = ON
# 기본값: ON
# 유지 이유: 성능 모니터링 및 진단
# 참고: 약간의 오버헤드 있지만 필수 모니터링 도구
# ------------------------------------------------------------------------
# 보안 설정
# ------------------------------------------------------------------------
# 로컬 파일 로드 비활성화 (보안)
local_infile = 0
# 기본값: 0 (MySQL 8.0+)
# 목적: LOAD DATA LOCAL INFILE 비활성화
# 보안: 로컬 파일 접근 방지
# 심볼릭 링크 비활성화
symbolic_links = 0
# 기본값: 0
# 목적: 심볼릭 링크 사용 비활성화
# 보안: 디렉토리 탐색 공격 방지
# ------------------------------------------------------------------------
# 기타 최적화
# ------------------------------------------------------------------------
# 쿼리 결과 캐시 (애플리케이션 레벨 권장)
# MySQL 8.0에서는 쿼리 캐시 제거됨
# 오픈 파일 제한
open_files_limit = 65535
# 기본값: 5000
# 변경값: 65535
# 목적: 동시에 열 수 있는 파일 수
# 성능: 많은 테이블과 연결을 처리할 때 필요
# 최대 허용 패킷 크기
max_allowed_packet = 64M
# 기본값: 64MB (MySQL 8.0+)
# 유지 이유: 대용량 데이터 처리
# 참고: 필요시 증가 가능 (최대 1GB)
# 그룹 커밋 최적화
binlog_group_commit_sync_delay = 0
# 기본값: 0
# 목적: 바이너리 로그 그룹 커밋 지연 (마이크로초)
# 성능: 0보다 크면 처리량 증가, 지연 약간 증가
# 참고: 초당 수천 개 트랜잭션 환경에서 1000-10000 설정
binlog_group_commit_sync_no_delay_count = 0
# 기본값: 0
# 목적: 지연 없이 커밋할 트랜잭션 수
# ------------------------------------------------------------------------
# 설정 파일 추가 포함
# ------------------------------------------------------------------------
!includedir /etc/my.cnf.d
# ========================================================================
# 주요 변경사항 요약
# ========================================================================
#
# 1. 메모리 설정 (4GB RAM 기준)
# - innodb_buffer_pool_size: 128MB → 2GB (50% of RAM)
# - tmp_table_size / max_heap_table_size: 16MB → 64MB
# - sort_buffer_size: 256KB → 4MB
# - read_buffer_size: 128KB → 2MB
# - join_buffer_size: 256KB → 4MB
#
# 2. 연결 설정
# - max_connections: 151 → 200
# - thread_cache_size: 8 → 50
# - wait_timeout: 28800 → 600 (10분)
#
# 3. InnoDB 최적화 (SSD 특화)
# - innodb_io_capacity: 200 → 2000
# - innodb_io_capacity_max: 2000 → 4000
# - innodb_flush_neighbors: 1 → 0 (SSD 최적화)
# - innodb_flush_method: fsync → O_DIRECT
#
# 4. 로그 설정
# - innodb_log_file_size: 48MB → 512MB
# - innodb_log_buffer_size: 16MB → 32MB
# - slow_query_log: 활성화 (2초 이상 쿼리)
#
# 5. 버퍼 풀 설정
# - innodb_buffer_pool_instances: 1 → 4 (CPU 코어 수)
#
# ========================================================================
# 예상 성능 향상
# ========================================================================
#
# - 읽기 성능: 40-60% 향상 (innodb_buffer_pool_size 증가)
# - 쓰기 성능: 30-50% 향상 (SSD 최적화, 로그 버퍼 증가)
# - 복잡한 쿼리: 50-100% 향상 (정렬/조인 버퍼 증가)
# - 동시 연결: 연결 처리 능력 향상 (thread_cache, max_connections)
# - 전체 처리량: 30-50% 향상
#
# ========================================================================
# 적용 방법
# ========================================================================
#
# 1. 이 파일을 /etc/my.cnf로 저장 (기존 파일 백업)
# sudo cp /etc/my.cnf /etc/my.cnf.backup
# sudo vi /etc/my.cnf
#
# 2. 로그 디렉토리 생성 및 권한 설정
# sudo mkdir -p /var/log/mysql
# sudo chown mysql:mysql /var/log/mysql
# sudo chmod 755 /var/log/mysql
#
# 3. MySQL 재시작
# sudo systemctl restart mysql
# 또는
# sudo service mysql restart
#
# 4. 설정 확인
# mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
# mysql -u root -p -e "SHOW VARIABLES LIKE 'max_connections';"
#
# 5. 슬로우 쿼리 로그 분석 (정기적으로)
# mysqldumpslow /var/log/mysql/slow-query.log
#
# 6. 성능 모니터링
# mysql -u root -p -e "SHOW ENGINE INNODB STATUS\G"
# mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Threads%';"
#
# ========================================================================
# Docker 환경 주의사항
# ========================================================================
#
# Docker 환경에서 사용 시:
# 1. 로그 디렉토리를 볼륨 마운트
# volumes:
# - ./logs/mysql:/var/log/mysql
#
# 2. 권한 문제 방지
# - 컨테이너 시작 전 호스트에서 디렉토리 생성
# mkdir -p ./logs/mysql
# chmod 777 ./logs/mysql # 또는 적절한 권한
#
# 3. 메모리 제한 확인
# - Docker 컨테이너에 최소 4GB RAM 할당
#
# ========================================================================

View File

@ -0,0 +1,4 @@
CREATE ROLE IF NOT EXISTS devadmin WITH
LOGIN
PASSWORD 'test!'
SUPERUSER;

View File

@ -0,0 +1,128 @@
# PostgreSQL Client Authentication Configuration File
# ===================================================
#
# Refer to the "Client Authentication" section in the PostgreSQL
# documentation for a complete description of this file. A short
# synopsis follows.
#
# ----------------------
# Authentication Records
# ----------------------
#
# This file controls: which hosts are allowed to connect, how clients
# are authenticated, which PostgreSQL user names they can use, which
# databases they can access. Records take one of these forms:
#
# local DATABASE USER METHOD [OPTIONS]
# host DATABASE USER ADDRESS METHOD [OPTIONS]
# hostssl DATABASE USER ADDRESS METHOD [OPTIONS]
# hostnossl DATABASE USER ADDRESS METHOD [OPTIONS]
# hostgssenc DATABASE USER ADDRESS METHOD [OPTIONS]
# hostnogssenc DATABASE USER ADDRESS METHOD [OPTIONS]
#
# (The uppercase items must be replaced by actual values.)
#
# The first field is the connection type:
# - "local" is a Unix-domain socket
# - "host" is a TCP/IP socket (encrypted or not)
# - "hostssl" is a TCP/IP socket that is SSL-encrypted
# - "hostnossl" is a TCP/IP socket that is not SSL-encrypted
# - "hostgssenc" is a TCP/IP socket that is GSSAPI-encrypted
# - "hostnogssenc" is a TCP/IP socket that is not GSSAPI-encrypted
#
# DATABASE can be "all", "sameuser", "samerole", "replication", a
# database name, a regular expression (if it starts with a slash (/))
# or a comma-separated list thereof. The "all" keyword does not match
# "replication". Access to replication must be enabled in a separate
# record (see example below).
#
# USER can be "all", a user name, a group name prefixed with "+", a
# regular expression (if it starts with a slash (/)) or a comma-separated
# list thereof. In both the DATABASE and USER fields you can also write
# a file name prefixed with "@" to include names from a separate file.
#
# ADDRESS specifies the set of hosts the record matches. It can be a
# host name, or it is made up of an IP address and a CIDR mask that is
# an integer (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that
# specifies the number of significant bits in the mask. A host name
# that starts with a dot (.) matches a suffix of the actual host name.
# Alternatively, you can write an IP address and netmask in separate
# columns to specify the set of hosts. Instead of a CIDR-address, you
# can write "samehost" to match any of the server's own IP addresses,
# or "samenet" to match any address in any subnet that the server is
# directly connected to.
#
# METHOD can be "trust", "reject", "md5", "password", "scram-sha-256",
# "gss", "sspi", "ident", "peer", "pam", "oauth", "ldap", "radius" or
# "cert". Note that "password" sends passwords in clear text; "md5" or
# "scram-sha-256" are preferred since they send encrypted passwords.
#
# OPTIONS are a set of options for the authentication in the format
# NAME=VALUE. The available options depend on the different
# authentication methods -- refer to the "Client Authentication"
# section in the documentation for a list of which options are
# available for which authentication methods.
#
# Database and user names containing spaces, commas, quotes and other
# special characters must be quoted. Quoting one of the keywords
# "all", "sameuser", "samerole" or "replication" makes the name lose
# its special character, and just match a database or username with
# that name.
#
# ---------------
# Include Records
# ---------------
#
# This file allows the inclusion of external files or directories holding
# more records, using the following keywords:
#
# include FILE
# include_if_exists FILE
# include_dir DIRECTORY
#
# FILE is the file name to include, and DIR is the directory name containing
# the file(s) to include. Any file in a directory will be loaded if suffixed
# with ".conf". The files of a directory are ordered by name.
# include_if_exists ignores missing files. FILE and DIRECTORY can be
# specified as a relative or an absolute path, and can be double-quoted if
# they contain spaces.
#
# -------------
# Miscellaneous
# -------------
#
# This file is read on server startup and when the server receives a
# SIGHUP signal. If you edit the file on a running system, you have to
# SIGHUP the server for the changes to take effect, run "pg_ctl reload",
# or execute "SELECT pg_reload_conf()".
#
# ----------------------------------
# Put your actual configuration here
# ----------------------------------
#
# If you want to allow non-local connections, you need to add more
# "host" records. In that case you will also need to make PostgreSQL
# listen on a non-local interface via the listen_addresses
# configuration parameter, or via the -i or -h command line switches.
# CAUTION: Configuring the system for local "trust" authentication
# allows any local user to connect as any PostgreSQL user, including
# the database superuser. If you do not trust all your local users,
# use another authentication method.
# TYPE DATABASE USER ADDRESS METHOD
# "local" is for Unix domain socket connections only
local all all trust
# IPv4 local connections:
host all all 127.0.0.1/32 trust
# IPv6 local connections:
host all all ::1/128 trust
# Allow replication connections from localhost, by a user with the
# replication privilege.
local replication all trust
host replication all 127.0.0.1/32 trust
host replication all ::1/128 trust
host all all all scram-sha-256

View File

@ -0,0 +1,766 @@
# -----------------------------
# PostgreSQL 18 최적화 설정
# 하드웨어 사양: 4코어 CPU, 4GB RAM, SSD, 1GB LAN
# -----------------------------
#------------------------------------------------------------------------------
# FILE LOCATIONS
#------------------------------------------------------------------------------
#data_directory = 'ConfigDir'
#hba_file = 'ConfigDir/pg_hba.conf'
#ident_file = 'ConfigDir/pg_ident.conf'
#external_pid_file = ''
#------------------------------------------------------------------------------
# CONNECTIONS AND AUTHENTICATION
#------------------------------------------------------------------------------
# - Connection Settings -
listen_addresses = '*'
# 기본값: 'localhost'
# 변경값: '*' (모든 IP에서 접근 허용)
# 목적: 네트워크를 통한 원격 접속 허용
#port = 5432
max_connections = 100
# 기본값: 100
# 유지 이유: 4GB RAM 환경에서 적절한 연결 수
# 성능: 각 연결은 약 10MB의 메모리를 사용하므로 100개 연결 = 약 1GB
# 참고: 연결 풀링(pgBouncer 등) 사용 시 더 효율적
#reserved_connections = 0
#superuser_reserved_connections = 3
#unix_socket_directories = '/var/run/postgresql'
#unix_socket_group = ''
#unix_socket_permissions = 0777
#bonjour = off
#bonjour_name = ''
# - TCP settings -
tcp_keepalives_idle = 60
# 기본값: 0 (시스템 기본값 사용, 보통 7200초)
# 변경값: 60초
# 목적: 유휴 연결을 60초마다 체크하여 죽은 연결을 빠르게 감지
# 성능: 네트워크 장애 시 빠른 연결 정리로 리소스 확보
tcp_keepalives_interval = 10
# 기본값: 0 (시스템 기본값 사용, 보통 75초)
# 변경값: 10초
# 목적: keepalive 재전송 간격
# 성능: 연결 문제를 빠르게 탐지
tcp_keepalives_count = 3
# 기본값: 0 (시스템 기본값 사용, 보통 9회)
# 변경값: 3회
# 목적: 연결 실패 판정까지의 재시도 횟수
# 성능: 60초 + (10초 × 3) = 최대 90초 내에 죽은 연결 정리
#tcp_user_timeout = 0
#client_connection_check_interval = 0
# - Authentication -
#authentication_timeout = 1min
#password_encryption = scram-sha-256
#scram_iterations = 4096
# - SSL -
#ssl = off
#ssl_ca_file = ''
#ssl_cert_file = 'server.crt'
#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------
# - Memory -
shared_buffers = 1GB
# 기본값: 128MB
# 변경값: 1GB (전체 RAM 4GB의 25%)
# 목적: 데이터베이스가 디스크에서 읽은 데이터를 캐시하는 메모리
# 성능: 자주 사용되는 데이터를 메모리에 유지하여 디스크 I/O 크게 감소
# 참고: PostgreSQL에서 가장 중요한 메모리 설정 중 하나
huge_pages = try
# 기본값: try
# 유지 이유: 가능한 경우 huge pages 사용으로 메모리 관리 효율 향상
# 성능: TLB 미스 감소, 대용량 shared_buffers 사용 시 특히 효과적
#huge_page_size = 0
#temp_buffers = 8MB
#max_prepared_transactions = 0
work_mem = 16MB
# 기본값: 4MB
# 변경값: 16MB
# 목적: 정렬, 해시 테이블 등 쿼리 작업에 사용되는 메모리
# 성능: 복잡한 쿼리의 정렬/조인 성능 향상, 디스크 임시 파일 사용 감소
# 주의: (max_connections × work_mem)이 너무 크면 OOM 위험
# 계산: 100 연결 × 16MB = 최대 1.6GB (복잡한 쿼리가 동시 실행될 경우)
#hash_mem_multiplier = 2.0
maintenance_work_mem = 256MB
# 기본값: 64MB
# 변경값: 256MB (RAM의 약 6%)
# 목적: VACUUM, CREATE INDEX, ALTER TABLE 등 유지보수 작업에 사용
# 성능: 인덱스 생성 및 VACUUM 작업 속도 대폭 향상
# 참고: 유지보수 작업은 동시에 많이 실행되지 않으므로 크게 설정 가능
autovacuum_work_mem = 256MB
# 기본값: -1 (maintenance_work_mem 사용)
# 변경값: 256MB
# 목적: autovacuum 전용 메모리 할당
# 성능: autovacuum 성능 향상으로 테이블 bloat 감소
#logical_decoding_work_mem = 64MB
max_stack_depth = 2MB
# 기본값: 2MB
# 유지 이유: 기본값이 대부분의 경우에 적절
#shared_memory_type = mmap
dynamic_shared_memory_type = posix
# 기본값: posix (Linux에서)
# 유지 이유: Linux에서 가장 효율적인 방식
#min_dynamic_shared_memory = 0MB
# - Disk -
#temp_file_limit = -1
#file_copy_method = copy
# - Kernel Resources -
#max_files_per_process = 1000
# - Background Writer -
bgwriter_delay = 200ms
# 기본값: 200ms
# 유지 이유: SSD 환경에서도 기본값이 적절
bgwriter_lru_maxpages = 100
# 기본값: 100
# 유지 이유: 백그라운드 쓰기 작업의 균형 유지
#bgwriter_lru_multiplier = 2.0
bgwriter_flush_after = 512kB
# 기본값: 512kB
# 유지 이유: SSD에서 적절한 flush 크기
# - I/O -
#backend_flush_after = 0
effective_io_concurrency = 200
# 기본값: 1 (HDD), 16 (SSD 감지 시)
# 변경값: 200
# 목적: SSD의 높은 IOPS를 활용한 병렬 I/O 요청 수
# 성능: bitmap heap scan 등에서 여러 페이지를 동시에 prefetch
# 참고: SSD는 동시 I/O 처리 능력이 뛰어나므로 높게 설정
maintenance_io_concurrency = 200
# 기본값: 10
# 변경값: 200
# 목적: VACUUM, CREATE INDEX 등 유지보수 작업의 병렬 I/O
# 성능: 유지보수 작업 속도 향상
#io_max_combine_limit = 128kB
#io_combine_limit = 128kB
#io_method = worker
#io_max_concurrency = -1
#io_workers = 3
# - Worker Processes -
max_worker_processes = 8
# 기본값: 8
# 유지 이유: 4코어 환경에서 적절 (코어 수 × 2)
# 성능: 병렬 쿼리, autovacuum 등 다양한 백그라운드 작업 처리
max_parallel_workers_per_gather = 2
# 기본값: 2
# 변경값: 2 (4코어 환경에서 적절)
# 목적: 단일 쿼리가 사용할 수 있는 최대 병렬 worker 수
# 성능: 대용량 테이블 스캔 시 쿼리 속도 향상
# 참고: 너무 높으면 다른 쿼리의 리소스 부족 발생 가능
max_parallel_maintenance_workers = 2
# 기본값: 2
# 유지 이유: CREATE INDEX 등 유지보수 작업의 병렬화
# 성능: 인덱스 생성 속도 향상
max_parallel_workers = 4
# 기본값: 8
# 변경값: 4 (CPU 코어 수)
# 목적: 시스템 전체에서 동시 실행 가능한 병렬 worker 총 수
# 성능: CPU 코어 수에 맞춰 과도한 컨텍스트 스위칭 방지
#parallel_leader_participation = on
#------------------------------------------------------------------------------
# WRITE-AHEAD LOG
#------------------------------------------------------------------------------
# - Settings -
#wal_level = replica
#fsync = on
#synchronous_commit = on
#wal_sync_method = fsync
#full_page_writes = on
#wal_log_hints = off
wal_compression = lz4
# 기본값: off
# 변경값: lz4
# 목적: WAL 파일 압축으로 I/O 및 스토리지 사용량 감소
# 성능: 네트워크를 통한 복제 시 대역폭 절약, 아카이빙 효율 향상
# 참고: CPU 사용량은 약간 증가하지만 4코어 환경에서 무리 없음
#wal_init_zero = on
#wal_recycle = on
wal_buffers = 16MB
# 기본값: -1 (shared_buffers의 3%, 최소 64kB, 최대 약 16MB)
# 변경값: 16MB
# 목적: WAL 데이터를 디스크에 쓰기 전 버퍼링
# 성능: 쓰기 집약적 워크로드에서 WAL 쓰기 성능 향상
#wal_writer_delay = 200ms
wal_writer_flush_after = 1MB
# 기본값: 1MB
# 유지 이유: SSD에서 적절한 flush 크기
#wal_skip_threshold = 2MB
#commit_delay = 0
#commit_siblings = 5
# - Checkpoints -
checkpoint_timeout = 15min
# 기본값: 5min
# 변경값: 15min
# 목적: 체크포인트 발생 간격 조정
# 성능: 체크포인트 빈도 감소로 I/O spike 완화, 전체 성능 향상
# 참고: 크래시 복구 시간은 약간 증가하지만 일반적으로 허용 가능
checkpoint_completion_target = 0.9
# 기본값: 0.9
# 유지 이유: 체크포인트를 시간에 걸쳐 분산하여 I/O spike 방지
# 성능: 90%의 시간에 걸쳐 checkpoint 완료하여 부하 분산
#checkpoint_flush_after = 256kB
#checkpoint_warning = 30s
max_wal_size = 2GB
# 기본값: 1GB
# 변경값: 2GB
# 목적: 체크포인트 간 생성 가능한 최대 WAL 크기
# 성능: 쓰기 집약적 워크로드에서 체크포인트 빈도 감소
# 참고: SSD 환경에서 더 큰 WAL 크기는 성능에 유리
min_wal_size = 1GB
# 기본값: 80MB
# 변경값: 1GB
# 목적: 항상 유지할 최소 WAL 크기
# 성능: WAL 파일 재사용으로 파일 생성/삭제 오버헤드 감소
# - Prefetching during recovery -
#recovery_prefetch = try
#wal_decode_buffer_size = 512kB
# - Archiving -
#archive_mode = off
#archive_library = ''
#archive_command = ''
#archive_timeout = 0
#------------------------------------------------------------------------------
# REPLICATION
#------------------------------------------------------------------------------
# - Sending Servers -
#max_wal_senders = 10
#max_replication_slots = 10
#wal_keep_size = 0
#max_slot_wal_keep_size = -1
#idle_replication_slot_timeout = 0
#wal_sender_timeout = 60s
#track_commit_timestamp = off
# - Primary Server -
#synchronous_standby_names = ''
#synchronized_standby_slots = ''
# - Standby Servers -
#primary_conninfo = ''
#primary_slot_name = ''
#hot_standby = on
#max_standby_archive_delay = 30s
#max_standby_streaming_delay = 30s
#wal_receiver_create_temp_slot = off
#wal_receiver_status_interval = 10s
#hot_standby_feedback = off
#wal_receiver_timeout = 60s
#wal_retrieve_retry_interval = 5s
#recovery_min_apply_delay = 0
#sync_replication_slots = off
# - Subscribers -
#max_active_replication_origins = 10
#max_logical_replication_workers = 4
#max_sync_workers_per_subscription = 2
#max_parallel_apply_workers_per_subscription = 2
#------------------------------------------------------------------------------
# QUERY TUNING
#------------------------------------------------------------------------------
# - Planner Method Configuration -
#enable_async_append = on
#enable_bitmapscan = on
#enable_gathermerge = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_incremental_sort = on
#enable_indexscan = on
#enable_indexonlyscan = on
#enable_material = on
#enable_memoize = on
#enable_mergejoin = on
#enable_nestloop = on
#enable_parallel_append = on
#enable_parallel_hash = on
#enable_partition_pruning = on
#enable_partitionwise_join = off
#enable_partitionwise_aggregate = off
#enable_presorted_aggregate = on
#enable_seqscan = on
#enable_sort = on
#enable_tidscan = on
# - Planner Cost Constants -
#seq_page_cost = 1.0
random_page_cost = 1.1
# 기본값: 4.0 (HDD), 1.1 (SSD 자동 감지 시)
# 변경값: 1.1
# 목적: SSD의 랜덤 액세스 특성을 반영
# 성능: 인덱스 스캔 선호도 증가, 쿼리 플래너의 더 나은 결정
# 참고: HDD는 4.0, SSD는 1.1-1.5가 적절
#cpu_tuple_cost = 0.01
#cpu_index_tuple_cost = 0.005
#cpu_operator_cost = 0.0025
#parallel_setup_cost = 1000.0
#parallel_tuple_cost = 0.1
#min_parallel_table_scan_size = 8MB
#min_parallel_index_scan_size = 512kB
effective_cache_size = 3GB
# 기본값: 4GB (시스템에 따라 다름)
# 변경값: 3GB (전체 RAM 4GB의 75%)
# 목적: OS와 PostgreSQL이 파일 캐싱에 사용 가능한 메모리 추정
# 성능: 쿼리 플래너가 인덱스 스캔 비용을 더 정확히 계산
# 참고: shared_buffers + OS 파일 캐시 = 약 3GB
#jit_above_cost = 100000
#jit_inline_above_cost = 500000
#jit_optimize_above_cost = 500000
# - Genetic Query Optimizer -
#geqo = on
#geqo_threshold = 12
# - Other Planner Options -
default_statistics_target = 100
# 기본값: 100
# 유지 이유: 통계 정확도와 ANALYZE 시간의 균형
# 성능: 쿼리 플래너의 정확한 비용 추정
# 참고: 특정 컬럼에 대해 개별적으로 높일 수 있음
#constraint_exclusion = partition
#cursor_tuple_fraction = 0.1
#from_collapse_limit = 8
#jit = on
#join_collapse_limit = 8
#plan_cache_mode = auto
#recursive_worktable_factor = 10.0
#------------------------------------------------------------------------------
# REPORTING AND LOGGING
#------------------------------------------------------------------------------
# 로깅 설정은 기본값 유지 (사용자 요청사항)
#log_destination = 'stderr'
#logging_collector = off
#log_directory = 'log'
#log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
#log_file_mode = 0600
#log_rotation_age = 1d
#log_rotation_size = 10MB
#log_truncate_on_rotation = off
#syslog_facility = 'LOCAL0'
#syslog_ident = 'postgres'
#syslog_sequence_numbers = on
#syslog_split_messages = on
#event_source = 'PostgreSQL'
#log_min_messages = warning
#log_min_error_statement = error
#log_min_duration_statement = -1
#log_min_duration_sample = -1
#log_statement_sample_rate = 1.0
#log_transaction_sample_rate = 0.0
#log_startup_progress_interval = 10s
#debug_print_parse = off
#debug_print_rewritten = off
#debug_print_plan = off
#debug_pretty_print = on
#log_autovacuum_min_duration = 10min
#log_checkpoints = on
#log_connections = ''
#log_disconnections = off
#log_duration = off
#log_error_verbosity = default
#log_hostname = off
#log_line_prefix = '%m [%p] '
#log_lock_waits = off
#log_lock_failures = off
#log_recovery_conflict_waits = off
#log_parameter_max_length = -1
#log_parameter_max_length_on_error = 0
#log_statement = 'none'
#log_replication_commands = off
#log_temp_files = -1
log_timezone = 'Etc/UTC'
#cluster_name = ''
#update_process_title = on
#------------------------------------------------------------------------------
# STATISTICS
#------------------------------------------------------------------------------
#track_activities = on
#track_activity_query_size = 1024
#track_counts = on
#track_cost_delay_timing = off
track_io_timing = on
# 기본값: off
# 변경값: on
# 목적: I/O 작업의 시간 추적으로 성능 병목 지점 파악
# 성능: EXPLAIN ANALYZE 등으로 I/O 병목 진단 가능
# 참고: 약간의 오버헤드 있지만 성능 튜닝에 매우 유용
#track_wal_io_timing = off
#track_functions = none
#stats_fetch_consistency = cache
# - Monitoring -
#compute_query_id = auto
#log_statement_stats = off
#log_parser_stats = off
#log_planner_stats = off
#log_executor_stats = off
#------------------------------------------------------------------------------
# VACUUMING
#------------------------------------------------------------------------------
# - Automatic Vacuuming -
#autovacuum = on
autovacuum_worker_slots = 8
# 기본값: 16 (PostgreSQL 18 신규 파라미터)
# 변경값: 8
# 목적: autovacuum worker 슬롯 수 (동시 실행 가능한 worker 총량)
# 성능: 4코어 환경에서 적절한 슬롯 수로 CPU 리소스 균형
# 참고: max_workers와 별개로 동적으로 worker 생성 가능
autovacuum_max_workers = 3
# 기본값: 3
# 변경값: 3
# 목적: 동시에 실행 가능한 autovacuum worker 프로세스 수
# 성능: 여러 테이블을 동시에 vacuum 처리
# 참고: 4코어 환경에서 적절한 수준
autovacuum_naptime = 30s
# 기본값: 1min
# 변경값: 30s
# 목적: autovacuum이 데이터베이스를 체크하는 주기
# 성능: 더 빈번한 체크로 테이블 bloat 감소, 성능 유지
# 참고: 쓰기가 많은 환경에서 효과적
#autovacuum_vacuum_threshold = 50
#autovacuum_vacuum_insert_threshold = 1000
#autovacuum_analyze_threshold = 50
autovacuum_vacuum_scale_factor = 0.1
# 기본값: 0.2 (테이블의 20%)
# 변경값: 0.1 (테이블의 10%)
# 목적: vacuum 실행 trigger 조건 (dead tuple 비율)
# 성능: 더 자주 vacuum 실행으로 테이블 bloat 최소화
# 참고: 대형 테이블에서 특히 효과적
#autovacuum_vacuum_insert_scale_factor = 0.2
autovacuum_analyze_scale_factor = 0.05
# 기본값: 0.1 (테이블의 10%)
# 변경값: 0.05 (테이블의 5%)
# 목적: analyze 실행 trigger 조건
# 성능: 더 빈번한 통계 업데이트로 쿼리 플래너의 정확도 향상
#autovacuum_vacuum_max_threshold = 100000000
#autovacuum_freeze_max_age = 200000000
#autovacuum_multixact_freeze_max_age = 400000000
#autovacuum_vacuum_cost_delay = 2ms
#autovacuum_vacuum_cost_limit = -1
# - Cost-Based Vacuum Delay -
#vacuum_cost_delay = 0
#vacuum_cost_page_hit = 1
#vacuum_cost_page_miss = 2
#vacuum_cost_page_dirty = 20
#vacuum_cost_limit = 200
# - Default Behavior -
#vacuum_truncate = on
# - Freezing -
#vacuum_freeze_table_age = 150000000
#vacuum_freeze_min_age = 50000000
#vacuum_failsafe_age = 1600000000
#vacuum_multixact_freeze_table_age = 150000000
#vacuum_multixact_freeze_min_age = 5000000
#vacuum_multixact_failsafe_age = 1600000000
#vacuum_max_eager_freeze_failure_rate = 0.03
#------------------------------------------------------------------------------
# CLIENT CONNECTION DEFAULTS
#------------------------------------------------------------------------------
# - Statement Behavior -
#client_min_messages = notice
#search_path = '"$user", public'
#row_security = on
#default_table_access_method = 'heap'
#default_tablespace = ''
#default_toast_compression = 'pglz'
#temp_tablespaces = ''
#check_function_bodies = on
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
#default_transaction_deferrable = off
#session_replication_role = 'origin'
statement_timeout = 30000
# 기본값: 0 (무제한)
# 변경값: 30000ms (30초)
# 목적: 장시간 실행되는 쿼리 자동 종료
# 성능: 문제있는 쿼리로 인한 리소스 점유 방지
# 참고: 애플리케이션 특성에 따라 조정 필요, 0으로 비활성화 가능
#transaction_timeout = 0
lock_timeout = 5000
# 기본값: 0 (무제한)
# 변경값: 5000ms (5초)
# 목적: 락 대기 시간 제한
# 성능: 데드락 상황 빠른 감지, 애플리케이션 응답성 향상
#idle_in_transaction_session_timeout = 0
idle_session_timeout = 300000
# 기본값: 0 (무제한)
# 변경값: 300000ms (5분)
# 목적: 유휴 세션 자동 종료
# 성능: 불필요한 연결로 인한 리소스 낭비 방지
# 참고: 연결 풀 사용 시 조정 필요
#bytea_output = 'hex'
#xmlbinary = 'base64'
#xmloption = 'content'
#gin_pending_list_limit = 4MB
#createrole_self_grant = ''
#event_triggers = on
# - Locale and Formatting -
datestyle = 'iso, mdy'
#intervalstyle = 'postgres'
timezone = 'Etc/UTC'
#timezone_abbreviations = 'Default'
#extra_float_digits = 1
#client_encoding = sql_ascii
lc_messages = 'en_US.utf8'
lc_monetary = 'en_US.utf8'
lc_numeric = 'en_US.utf8'
lc_time = 'en_US.utf8'
#icu_validation_level = warning
default_text_search_config = 'pg_catalog.english'
# - Shared Library Preloading -
#local_preload_libraries = ''
#session_preload_libraries = ''
#shared_preload_libraries = ''
#jit_provider = 'llvmjit'
# - Other Defaults -
#dynamic_library_path = '$libdir'
#extension_control_path = '$system'
#gin_fuzzy_search_limit = 0
#------------------------------------------------------------------------------
# LOCK MANAGEMENT
#------------------------------------------------------------------------------
deadlock_timeout = 1s
# 기본값: 1s
# 유지 이유: 데드락 감지를 위한 적절한 대기 시간
#max_locks_per_transaction = 64
#max_pred_locks_per_transaction = 64
#max_pred_locks_per_relation = -2
#max_pred_locks_per_page = 2
#------------------------------------------------------------------------------
# VERSION AND PLATFORM COMPATIBILITY
#------------------------------------------------------------------------------
#array_nulls = on
#backslash_quote = safe_encoding
#escape_string_warning = on
#lo_compat_privileges = off
#quote_all_identifiers = off
#standard_conforming_strings = on
#synchronize_seqscans = on
#transform_null_equals = off
#allow_alter_system = on
#------------------------------------------------------------------------------
# ERROR HANDLING
#------------------------------------------------------------------------------
#exit_on_error = off
#restart_after_crash = on
#data_sync_retry = off
#recovery_init_sync_method = fsync
#------------------------------------------------------------------------------
# CONFIG FILE INCLUDES
#------------------------------------------------------------------------------
#include_dir = '...'
#include_if_exists = '...'
#include = '...'
#------------------------------------------------------------------------------
# CUSTOMIZED OPTIONS
#------------------------------------------------------------------------------
# =============================================================================
# 주요 변경사항 요약
# =============================================================================
#
# 1. 메모리 설정 (4GB RAM 기준)
# - shared_buffers: 128MB → 1GB (25% of RAM)
# - effective_cache_size: 4GB → 3GB (75% of RAM)
# - work_mem: 4MB → 16MB (쿼리 성능 향상)
# - maintenance_work_mem: 64MB → 256MB (유지보수 작업 가속)
#
# 2. 연결 관리
# - tcp_keepalives 설정: 죽은 연결 빠른 감지 (90초 이내)
# - statement_timeout: 30초 (장시간 쿼리 방지)
# - lock_timeout: 5초 (락 대기 제한)
# - idle_session_timeout: 5분 (유휴 세션 정리)
#
# 3. 병렬 처리 (4코어 최적화)
# - max_parallel_workers: 4 (CPU 코어 수)
# - max_parallel_workers_per_gather: 2
# - max_worker_processes: 8
#
# 4. I/O 최적화 (SSD 특화)
# - random_page_cost: 4.0 → 1.1
# - effective_io_concurrency: 1 → 200
# - maintenance_io_concurrency: 10 → 200
#
# 5. WAL 및 체크포인트
# - wal_compression: off → lz4 (I/O 감소)
# - wal_buffers: 자동 → 16MB
# - checkpoint_timeout: 5min → 15min
# - max_wal_size: 1GB → 2GB
# - min_wal_size: 80MB → 1GB
#
# 6. Autovacuum 튜닝
# - autovacuum_worker_slots: 16 → 8
# - autovacuum_naptime: 1min → 30s (더 빈번한 체크)
# - autovacuum_vacuum_scale_factor: 0.2 → 0.1 (더 자주 실행)
# - autovacuum_analyze_scale_factor: 0.1 → 0.05
#
# 7. 모니터링
# - track_io_timing: off → on (I/O 성능 진단)
#
# =============================================================================
# 예상 성능 향상
# =============================================================================
#
# - 읽기 성능: 30-50% 향상 (shared_buffers, effective_cache_size)
# - 쓰기 성능: 20-40% 향상 (WAL 설정, checkpoint 최적화)
# - 복잡한 쿼리: 40-100% 향상 (work_mem, 병렬 처리)
# - 유지보수 작업: 100-300% 향상 (maintenance_work_mem, I/O 동시성)
# - 전체 처리량: 25-50% 향상 (모든 최적화의 시너지)
#
# =============================================================================
# 적용 방법
# =============================================================================
#
# 1. 이 파일을 postgresql.conf로 저장 (또는 기존 파일 백업 후 교체)
# 2. PostgreSQL 재시작:
# sudo systemctl restart postgresql
# 또는
# sudo pg_ctl restart -D /var/lib/postgresql/data
#
# 3. 설정 확인:
# SHOW shared_buffers;
# SHOW effective_cache_size;
# SHOW work_mem;
#
# 4. 모니터링 (첫 며칠간):
# - 메모리 사용량: free -h, htop
# - 체크포인트 빈도: 로그 확인
# - 쿼리 성능: pg_stat_statements 활용
#
# =============================================================================

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 devspoons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,104 @@
server {
listen 80;
server_name demo.castad.net;
if ($bad_bot) {
return 403;
}
access_log /log/nginx/demo.castad.net.gunicorn_access.log main;
error_log /log/nginx/demo.castad.net.gunicorn_error.log warn;
# if ($host !~* ^(domain\.com|www\.domain\.com)$) {
# return 444;
# }
# 프론트엔드 정적 파일 루트
root /www/o2o-castad-frontend/dist;
index index.html;
# Django media
location /media {
autoindex off;
gzip_static on;
expires max;
#alias /www/django_sample/media;
alias /www/o2o-castad-backend/media; # your Django project's media files - amend as required
#include /etc/nginx/mime.types;
}
location /static {
autoindex off;
gzip_static on;
expires max;
#alias /www/django_sample/static;
# normally static folder is named as /static
alias /www/o2o-castad-backend/static; # your Django project's static files - amend as required
#include /etc/nginx/mime.types;
}
location /api/ {
autoindex off;
# upstream 연결 풀 사용 (nginx.conf에서 정의)
proxy_pass http://uvicorn-app:8000/;
# HTTP/1.1 사용 (keepalive 연결 필수)
proxy_http_version 1.1;
# WebSocket 지원 및 HTTP keepalive 동시 지원
# - WebSocket: Upgrade 헤더 전달, Connection: upgrade
# - 일반 HTTP: Connection: "" (keepalive 유지)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 프록시 헤더 설정
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 프록시 캐시 우회 (WebSocket 및 동적 콘텐츠)
proxy_cache_bypass $http_upgrade;
# 타임아웃 설정 (파일 업로드, AI 생성 등 오래 걸리는 작업용)
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# 파일 업로드 설정
client_max_body_size 100M;
proxy_request_buffering off;
}
# Allow Lets Encrypt Domain Validation Program
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/o2o-castad-backend;
}
# Block dot file (.htaccess .htpasswd .svn .git .env and so on.)
location ~ /\. {
deny all;
}
# Block (log file, binary, certificate, shell script, sql dump file) access.
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
}
# Block access
location ~* (composer\.json|composer\.lock|composer\.phar|contributing\.md|license\.txt|readme\.rst|readme\.md|readme\.txt|copyright|artisan|gulpfile\.js|package\.json|phpunit\.xml|access_log|error_log|gruntfile\.js)$ {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
log_not_found off;
access_log off;
}
}

View File

@ -0,0 +1,563 @@
map $http_user_agent $bad_bot {
default 0;
~*360Spider 1;
~*360Spider 1;
~*80legs 1;
~*Abonti 1;
~*Aboundex 1;
~*AcoonBot 1;
~*Acunetix 1;
~*adbeat_bot 1;
~*AddThis.com 1;
~*adidxbot 1;
~*ADmantX 1;
~*AhrefsBot 1;
~*AIBOT 1;
~*aiHitBot 1;
~*Alexibot 1;
~*Alligator 1;
~*AllSubmitter 1;
~*AngloINFO 1;
~*Antelope 1;
~*Apexoo 1;
~*asterias 1;
~*attach 1;
~*BackDoorBot 1;
~*BackStreet 1;
~*BackWeb 1;
~*Badass 1;
~*Baid 1;
~*Bandit 1;
~*BatchFTP 1;
~*BBBike 1;
~*BeetleBot 1;
~*Bigfoot 1;
~*billigerbot 1;
~*binlar 1;
~*bitlybot 1;
~*Black.Hole 1;
~*BlackWidow 1;
~*BLEXBot 1;
~*Blow 1;
~*BlowFish 1;
~*BLP_bbot 1;
~*BoardReader 1;
~*Bolt\ 0 1;
~*BOT\ for\ JCE 1;
~*Bot\ mailto\:craftbot@yahoo\.com 1;
~*BotALot 1;
~*Buddy 1;
~*BuiltBotTough 1;
~*Bullseye 1;
~*BunnySlippers 1;
~*casper 1;
~*CazoodleBot 1;
~*CCBot 1;
~*Cegbfeieh 1;
~*checkprivacy 1;
~*CheeseBot 1;
~*CherryPicker 1;
~*ChinaClaw 1;
~*chromeframe 1;
~*Clerkbot 1;
~*Cliqzbot 1;
~*clshttp 1;
~*Cogentbot 1;
~*cognitiveseo 1;
~*Collector 1;
~*CommonCrawler 1;
~*comodo 1;
~*Copier 1;
~*CopyRightCheck 1;
~*cosmos 1;
~*CPython 1;
~*crawler4j 1;
~*Crawlera 1;
~*CRAZYWEBCRAWLER 1;
~*Crescent 1;
~*CSHttp 1;
~*Curious 1;
# ~*Curl 1; # 개발/테스트용으로 허용
~*Custo 1;
~*CWS_proxy 1;
~*Default\ Browser\ 0 1;
~*Demon 1;
~*DeuSu 1;
~*Devil 1;
~*diavol 1;
~*DigExt 1;
~*Digincore 1;
~*DIIbot 1;
~*DISCo 1;
~*discobot 1;
~*DittoSpyder 1;
~*DoCoMo 1;
~*DotBot 1;
~*Download.Demon 1;
~*Download.Devil 1;
~*Download.Wonder 1;
~*Download\ Demo 1;
~*dragonfly 1;
~*Drip 1;
~*DTS.Agent 1;
~*EasouSpider 1;
~*EasyDL 1;
~*ebingbong 1;
~*eCatch 1;
~*ecxi 1;
~*EirGrabber 1;
~*Elmer 1;
~*EmailCollector 1;
~*EmailSiphon 1;
~*EmailWolf 1;
~*EroCrawler 1;
~*Exabot 1;
~*ExaleadCloudView 1;
~*ExpertSearch 1;
~*ExpertSearchSpider 1;
~*Express 1;
~*Express\ WebPictures 1;
~*extract 1;
~*Extractor 1;
~*ExtractorPro 1;
~*EyeNetIE 1;
~*Ezooms 1;
~*F2S 1;
~*FastSeek 1;
~*feedfinder 1;
~*FeedlyBot 1;
~*FHscan 1;
~*finbot 1;
~*Flamingo_SearchEngine 1;
~*FlappyBot 1;
~*FlashGet 1;
~*flicky 1;
~*Flipboard 1;
~*FlipboardProxy 1;
~*flunky 1;
~*Foobot 1;
~*FrontPage 1;
~*g00g1e 1;
~*GalaxyBot 1;
~*genieo 1;
~*Genieo 1;
~*GetRight 1;
~*GetWeb\! 1;
~*GigablastOpenSource 1;
~*Go\-Ahead\-Got\-It 1;
~*Go\!Zilla 1;
~*gotit 1;
~*GozaikBot 1;
~*grab 1;
~*Grabber 1;
~*GrabNet 1;
~*Grafula 1;
~*GrapeshotCrawler 1;
~*GT\:\:WWW 1;
~*GTB5 1;
~*Guzzle 1;
~*harvest 1;
~*Harvest 1;
~*HEADMasterSEO 1;
~*heritrix 1;
~*hloader 1;
~*HMView 1;
~*HomePageBot 1;
~*htmlparser 1;
~*HTTP\:\:Lite 1;
~*httrack 1;
~*HTTrack 1;
~*HubSpot 1;
~*humanlinks 1;
~*ia_archiver 1;
~*icarus6 1;
~*id\-search 1;
~*IDBot 1;
~*IlseBot 1;
~*Image.Stripper 1;
~*Image.Sucker 1;
~*Image\ Stripper 1;
~*Image\ Sucker 1;
~*imagefetch 1;
~*Indigonet 1;
~*Indy\ Library 1;
~*InfoNaviRobot 1;
~*InfoTekies 1;
~*integromedb 1;
~*Intelliseek 1;
~*InterGET 1;
~*Internet\ Ninja 1;
~*InternetSeer\.com 1;
~*Iria 1;
~*IRLbot 1;
~*ISC\ Systems\ iRc\ Search\ 2\.1 1;
~*jakarta 1;
~*Jakarta 1;
~*Java 1;
~*JennyBot 1;
~*JetCar 1;
~*JikeSpider 1;
~*JobdiggerSpider 1;
~*JOC 1;
~*JOC\ Web\ Spider 1;
~*Jooblebot 1;
~*JustView 1;
~*Jyxobot 1;
~*kanagawa 1;
~*Kenjin.Spider 1;
~*Keyword.Density 1;
~*KINGSpider 1;
~*kmccrew 1;
~*larbin 1;
~*LeechFTP 1;
~*LeechGet 1;
~*LexiBot 1;
~*lftp 1;
~*libWeb 1;
~*libwww 1;
~*libwww-perl 1;
~*likse 1;
~*Lingewoud 1;
~*LinkChecker 1;
~*linkdexbot 1;
~*LinkextractorPro 1;
~*LinkScan 1;
~*LinksCrawler 1;
~*LinksManager\.com_bot 1;
~*linkwalker 1;
~*LinkWalker 1;
~*LinqiaRSSBot 1;
~*LivelapBot 1;
~*LNSpiderguy 1;
~*ltx71 1;
~*LubbersBot 1;
~*lwp\-trivial 1;
~*Mag-Net 1;
~*Magnet 1;
~*Mail.RU_Bot 1;
~*majestic12 1;
~*MarkWatch 1;
~*Mass.Downloader 1;
~*Mass\ Downloader 1;
~*masscan 1;
~*Mata.Hari 1;
~*maverick 1;
~*Maxthon$ 1;
~*Mediatoolkitbot 1;
~*megaindex 1;
~*MegaIndex 1;
~*Memo 1;
~*MetaURI 1;
~*MFC_Tear_Sample 1;
~*Microsoft\ URL\ Control 1;
~*microsoft\.url 1;
~*MIDown\ tool 1;
~*MIIxpc 1;
~*miner 1;
~*Missigua\ Locator 1;
~*Mister\ PiX 1;
~*MJ12bot 1;
~*Mozilla.*Indy 1;
~*Mozilla.*NEWT 1;
~*MSFrontPage 1;
~*MSIECrawler 1;
~*msnbot 1;
~*NAMEPROTECT 1;
~*Navroad 1;
~*NearSite 1;
~*Net\ Vampire 1;
~*NetAnts 1;
~*Netcraft 1;
~*netEstate 1;
~*NetMechanic 1;
~*NetSpider 1;
~*NetZIP 1;
~*NextGenSearchBot 1;
~*NICErsPRO 1;
~*niki\-bot 1;
~*NimbleCrawler 1;
~*Nimbostratus\-Bot 1;
~*Ninja 1;
~*nmap 1;
~*Nmap 1;
~*NPbot 1;
~*nutch 1;
~*Octopus 1;
~*Offline\.Explorer 1;
~*Offline\.Navigator 1;
~*Offline\ Explorer 1;
~*Offline\ Navigator 1;
~*Openfind 1;
~*OpenindexSpider 1;
~*OpenLinkProfiler 1;
~*OpenWebSpider 1;
~*OrangeBot 1;
~*OutfoxBot 1;
~*Owlin 1;
~*PageGrabber 1;
~*PagesInventory 1;
~*panopta 1;
~*panscient\.com 1;
~*Papa\ Foto 1;
~*pavuk 1;
~*pcBrowser 1;
~*PECL\:\:HTTP 1;
~*PeoplePal 1;
~*Photon 1;
~*PHPCrawl 1;
~*Pixray 1;
~*planetwork 1;
~*PleaseCrawl 1;
~*PNAMAIN\.EXE 1;
~*Pockey 1;
~*PodcastPartyBot 1;
~*prijsbest 1;
~*probethenet 1;
~*ProPowerBot 1;
~*ProWebWalker 1;
~*proximic 1;
~*psbot 1;
~*Pump 1;
~*purebot 1;
~*pycurl 1;
~*python\-requests 1;
~*QueryN\.Metasearch 1;
~*QuerySeekerSpider 1;
~*R6_CommentReader 1;
~*R6_FeedFetcher 1;
~*RealDownload 1;
~*Reaper 1;
~*Recorder 1;
~*ReGet 1;
~*RepoMonkey 1;
~*Riddler 1;
~*Ripper 1;
~*Rippers\ 0 1;
~*RMA 1;
~*rogerbot 1;
~*RSSingBot 1;
~*rv\:1\.9\.1 1;
~*RyzeCrawler 1;
~*SafeSearch 1;
~*SBIder 1;
~*scanbot 1;
~*Scrapy 1;
~*Screaming 1;
~*SeaMonkey$ 1;
~*search_robot 1;
~*SearchmetricsBot 1;
~*Semrush 1;
~*SemrushBot 1;
~*semrush\.com 1;
~*SemrushBot-BA 1;
~*SentiBot 1;
~*SEOkicks 1;
~*SEOkicks\-Robot 1;
~*seoscanners 1;
~*SeznamBot 1;
~*ShowyouBot 1;
~*SightupBot 1;
~*Siphon 1;
~*SISTRIX 1;
~*sitecheck\.internetseer\.com 1;
~*siteexplorer\.info 1;
~*Siteimprove 1;
~*SiteSnagger 1;
~*SiteSucker 1;
~*skygrid 1;
~*Slackbot 1;
~*Slurp 1;
~*SlySearch 1;
~*SmartDownload 1;
~*Snake 1;
~*Snapbot 1;
~*Snoopy 1;
~*sogou 1;
~*Sogou 1;
~*Sosospider 1;
~*SpaceBison 1;
~*SpankBot 1;
~*spanner 1;
~*spaumbot 1;
~*spbot 1;
~*Spinn4r 1;
~*Sqworm 1;
~*Steeler 1;
~*Stripper 1;
~*sucker 1;
~*Sucker 1;
~*SuperBot 1;
~*Superfeedr 1;
~*SuperHTTP 1;
~*SurdotlyBot 1;
~*Surfbot 1;
~*suzuran 1;
~*Szukacz 1;
~*tAkeOut 1;
~*Teleport 1;
~*Teleport\ Pro 1;
~*Telesoft 1;
~*The\.Intraformant 1;
~*TheNomad 1;
~*TightTwatBot 1;
~*TinEye 1;
~*TinEye\-bot 1;
~*Titan 1;
~*Toata\ dragostea\ mea\ pentru\ diavola 1;
~*Toplistbot 1;
~*trendictionbot 1;
~*trovitBot 1;
~*True_Robot 1;
~*turingos 1;
~*turnit 1;
~*TurnitinBot 1;
~*Twitterbot 1;
~*URI\:\:Fetch 1;
~*urllib 1;
~*URLy\.Warning 1;
~*Vacuum 1;
~*Vagabondo 1;
~*VCI 1;
~*VidibleScraper 1;
~*vikspider 1;
~*VoidEYE 1;
~*VoilaBot 1;
~*WallpapersHD 1;
~*WBSearchBot 1;
~*Web.Image.Collector 1;
~*Web\ Image\ Collector 1;
~*Web\ Sucker 1;
~*webalta 1;
~*WebAuto 1;
~*WebBandit 1;
~*WebCollage 1;
~*WebCopier 1;
~*WebEnhancer 1;
~*WebFetch 1;
~*WebFuck 1;
~*WebGo\ IS 1;
~*WebLeacher 1;
~*WebmasterWorldForumBot 1;
~*WebPix 1;
~*WebReaper 1;
~*WebSauger 1;
~*WebShag 1;
~*Website\.eXtractor 1;
~*Website\ eXtractor 1;
~*Website\ Quester 1;
~*Webster 1;
~*WebStripper 1;
~*WebSucker 1;
~*WebWhacker 1;
~*WebZIP 1;
~*Wells\ Search\ II 1;
~*WEP\ Search 1;
~*WeSEE 1;
~*Wget 1;
~*Whack 1;
~*Whacker 1;
~*Widow 1;
~*WinHTTrack 1;
~*WinInet 1;
~*WISENutbot 1;
~*woobot 1;
~*woopingbot 1;
~*worldwebheritage.org 1;
~*Wotbox 1;
~*WPScan 1;
~*WWW\-Collector\-E 1;
~*WWW\-Mechanize 1;
~*WWWOFFLE 1;
~*Xaldon 1;
~*Xaldon\ WebSpider 1;
~*Xenu 1;
~*XoviBot 1;
~*yacybot 1;
~*YisouSpider 1;
~*Zade 1;
~*zermelo 1;
~*Zeus 1;
~*zh\-CN 1;
~*ZmEu 1;
~*ZumBot 1;
~*Zyborg 1;
~*ZyBorg 1;
~*Yandex 1;
~*YandexBot 1;
~*Baiduspider 1;
~*BaiduSpider 1;
~*Slackbot 1;
}
map $http_user_agent $bad_bot1 {
default 0;
~*^Lynx 0; # Let Lynx go through
libwww-perl 1;
~*(?i)(80legs|360Spider|Aboundex|AhrefsBot|Daumoa|DataForSeoBot|DaumBot|applebot|BLEXBot|serpstatbot|MediaMathbot|Abonti|Acunetix|^AIBOT|^Alexibot|Alligator|AllSubmitter|Apexoo|^asterias|^attach|^BackDoorBot|^BackStreet|^BackWeb|Badass|Bandit|petalbot|Baid|Baiduspider|^BatchFTP|^Bigfoot|^Black.Hole|^BlackWidow|BlackWidow|^BlowFish|Blow|^BotALot|Buddy|^BuiltBotTough|^Bullseye|^BunnySlippers|BBBike|^Cegbfeieh|^CheeseBot|^CherryPicker|^ChinaClaw|^Cogentbot|CPython|Collector|cognitiveseo|Copier|^CopyRightCheck|^cosmos|^Crescent|CSHttp|^Custo|^Demon|^Devil|^DISCo|^DIIbot|discobot|^DittoSpyder|Download.Demon|Download.Devil|Download.Wonder|^dragonfly|^Drip|^eCatch|^EasyDL|^ebingbong|^EirGrabber|^EmailCollector|^EmailSiphon|^EmailWolf|^EroCrawler|^Exabot|^Express|Extractor|^EyeNetIE|FHscan|^FHscan|^flunky|^Foobot|^FrontPage|GalaxyBot|^gotit|Grabber|^GrabNet|^Grafula|^Harvest|^HEADMasterSEO|^hloader|^HMView|^HTTrack|httrack|HTTrack|htmlparser|^humanlinks|^IlseBot|Image.Stripper|Image.Sucker|imagefetch|^InfoNaviRobot|^InfoTekies|^Intelliseek|^InterGET|^Iria|^Jakarta|^JennyBot|^JetCar|JikeSpider|^JOC|^JustView|^Jyxobot|^Kenjin.Spider|^Keyword.Density|libwww|^larbin|LeechFTP|LeechGet|^LexiBot|^lftp|^libWeb|^likse|^LinkextractorPro|^LinkScan|^LNSpiderguy|^LinkWalker|msnbot|MSIECrawler|MJ12bot|MegaIndex|^Magnet|^Mag-Net|^MarkWatch|Mass.Downloader|masscan|^Mata.Hari|^Memo|^MIIxpc|^NAMEPROTECT|^Navroad|^NearSite|^NetAnts|^Netcraft|^NetMechanic|^NetSpider|^NetZIP|^NextGenSearchBot|^NICErsPRO|^niki-bot|^NimbleCrawler|^Nimbostratus-Bot|^Ninja|^Nmap|nmap|^NPbot|Offline.Explorer|Offline.Navigator|OpenLinkProfiler|^Octopus|^Openfind|^OutfoxBot|Pixray|probethenet|proximic|^PageGrabber|^pavuk|^pcBrowser|^Pockey|^ProPowerBot|^ProWebWalker|^psbot|^Pump|python-requests|^QueryN.Metasearch|^RealDownload|Reaper|^Reaper|^Ripper|Ripper|Recorder|^ReGet|^RepoMonkey|^RMA|scanbot|SEOkicks-Robot|seoscanners|^Stripper|^Sucker|Siphon|Siteimprove|^SiteSnagger|SiteSucker|^SlySearch|^SmartDownload|^Snake|^Snapbot|^Snoopy|Sosospider|^sogou|spbot|^SpaceBison|^spanner|^SpankBot|Spinn4r|^Sqworm|Sqworm|Stripper|Sucker|^SuperBot|SuperHTTP|^SuperHTTP|^Surfbot|^suzuran|^Szukacz|^tAkeOut|^Teleport|^Telesoft|^TurnitinBot|^The.Intraformant|^TheNomad|^TightTwatBot|^Titan|^True_Robot|^turingos|^TurnitinBot|^URLy.Warning|^Vacuum|^VCI|VidibleScraper|^VoidEYE|^WebAuto|^WebBandit|^WebCopier|^WebEnhancer|^WebFetch|^Web.Image.Collector|^WebLeacher|^WebmasterWorldForumBot|WebPix|^WebReaper|^WebSauger|Website.eXtractor|^Webster|WebShag|^WebStripper|WebSucker|^WebWhacker|^WebZIP|Whack|Whacker|^Widow|Widow|WinHTTrack|^WISENutbot|WWWOFFLE|^WWWOFFLE|^WWW-Collector-E|^Xaldon|^Xenu|^Zade|^Zeus|ZmEu|^Zyborg|SemrushBot|^WebFuck|^MJ12bot|^majestic12|^WallpapersHD) 1;
}
## Add here all referrers that are to blocked.
map $http_referer $bad_referer {
default 0;
~(?i)(adcash|advair|allegra|ambien|amoxicillin|adult|anal|asshole|babes|baccarat|betting|bithack|blackjack|cash|casino|celeb|cheap|cialis|craps|credit|click|cunt|deal|debt|drug|diamond|effexor|equity|faxo|finance|fisting|forsale|gambling|gaysex|girl|hardcore|hold-em|holdem|iconsurf|ilovevitaly|insurance|interest|internetsupervision|jewelry|keno|levitra|lipitor|loan|loans|love|makemoneyonline|make-money-online|meds|money|mortgage|myftpupload|nudit|omaha|organic|paxil|pharmacy|pharmacies|phentermine|pheromone|pills|piss|poker|porn|poweroversoftware|refinance|replica|rimming|roulette|screentoolkit|seoexperimenty|sex|snuff|scout|seventwentyfour|slot|slots|syntryx|teen|texas|t0phackteam|tournament|tramadol|tramidol|valtrex|vvakhrin-ws1|viagra|vicodin|webcam|xanax|xnxx|xxxrus|zanax|zippo|zoloft) 1;
}
## Add here all bad referer domains to be blocked - broken up into sections
## Alphabetical A - E (incl numbers)
map $http_referer $bad_urls1 {
default 0;
~(?i)(^http://(www\.)?38ha(-|.).*$|^http://(www\.)?4free(-|.).*$|^http://(www\.)?4hs8(-|.).*$|^http://(www\.)?4t(-|.).*$|^http://(www\.)?4u(-|.).*$|^http://(www\.)?6q(-|.).*$|^http://(www\.)?7makemoneyonline(-|.).*$|^http://(www\.)?8gold(-|.).*$|^http://(www\.)?911(-|.).*$|^http://(www\.)?adcash(-|.).*$|^http://(www\.)?.*(-|.)?adult(-|.).*$|^http://(www\.)?.*(-|.)?acunetix-referrer(-|.).*$|^http://(www\.)?abalone(-|.).*$|^http://(www\.)?adminshop(-|.).*$|^http://(www\.)?adultactioncam(-|.).*$|^http://(www\.)?aizzo(-|.).*$|^http://(www\.)?alphacarolinas(-|.).*$|^http://(www\.)?amateur(-|.).*$|^http://(www\.)?amateurxpass(-|.).*$|^http://(www\.)?.*(-|.)?anal(-|.).*$|^http://(www\.)?ansar-u-deen(-|.).*$|^http://(www\.)?atelebanon(-|.).*$|^http://(www\.)?beastiality(-|.).*$|^http://(www\.)?bestiality(-|.).*$|^http://(www\.)?belize(-|.).*$|^http://(www\.)?best-deals(-|.).*$|^http://(www\.)?bithack(-|.).*$|^http://(www\.)?blogincome(-|.).*$|^http://(www\.)?bontril(-|.).*$|^http://(www\.)?bruce-holdeman(-|.).*$|^http://(www\.)?.*(-|.)?blow.?job(-|.).*$|^http://(www\.)?buttons-for-website(-|.).*$|^http://(www\.)?ca-america(-|.).*$|^http://(www\.)?chatt-net(-|.).*$|^http://(www\.)?cenokos(-|.).*$|^http://(www\.)?cenoval(-|.).*$|^http://(www\.)?cityadspix(-|.).*$|^http://(www\.)?commerce(-|.).*$|^http://(www\.)?condo(-|.).*$|^http://(www\.)?conjuratia(-|.).*$|^http://(www\.)?consolidate(-|.).*$|^http://(www\.)?coswap(-|.).*$|^http://(www\.)?crescentarian(-|.).*$|^http://(www\.)?crepesuzette(-|.).*$|^http://(www\.)?darodar(-|.).*$|^http://(www\.)?dating(-|.).*$|^http://(www\.)?devaddict(-|.).*$|^http://(www\.)?discount(-|.).*$|^http://(www\.)?doobu(-|.).*$|^http://(www\.)?domainsatcost(-|.).*$|^http://(www\.)?econom.co(-|.).*$|^http://(www\.)?edakgfvwql(-|.).*$|^http://(www\.)?.*(-|.)?sex(-|.).*$|^http://(www\.)?e-site(-|.).*$|^http://(www\.)?egygift(-|.).*$|^http://(www\.)?empathica(-|.).*$|^http://(www\.)?empirepoker(-|.).*$|^http://(www\.)?e-poker-2005(-|.).*$|^http://(www\.)?escal8(-|.).*$|^http://(www\.)?eurip(-|.).*$|^http://(www\.)?exitq(-|.).*$|^http://(www\.)?eyemagination(-|.).*$) 1;
}
## F - I
map $http_referer $bad_urls2 {
default 0;
~(?i)(^http://(www\.)?fastcrawl(-|.).*$|^http://(www\.)?fearcrow(-|.).*$|^http://(www\.)?ferretsoft(-|.).*$|^http://(www\.)?fick(-|.).*$|^http://(www\.)?finance(-|.).*$|^http://(www\.)?flafeber(-|.).*$|^http://(www\.)?fidelityfunding(-|.).*$|^http://(www\.)?freakycheats(-|.).*$|^http://(www\.)?freeality(-|.).*$|^http://(www\.)?fuck(-|.).*$|^http://(www\.)?future-2000(-|.).*$|^http://(www\.)?.*(-|.)?gay(-|.).*$|^http://(www\.)?gobongo.info(-|.).*$|^http://(www\.)?gabriola(-|.).*$|^http://(www\.)?gallerylisting(-|.).*$|^http://(www\.)?gb.com(-|.).*$|^http://(www\.)?ghostvisitor(-|.).*$|^http://(www\.)?globusy(-|.).*$|^http://(www\.)?golf-e-course(-|.).*$|^http://(www\.)?gospelcom(-|.).*$|^http://(www\.)?gradfinder(-|.).*$|^http://(www\.)?hasfun(-|.).*$|^http://(www\.)?herbal(-|.).*$|^http://(www\.)?hermosa(-|.).*$|^http://(www\.)?highprofitclub(-|.).*$|^http://(www\.)?hilton(-|.).*$|^http://(www\.)?teaminspection(-|.).*$|^http://(www\.)?hotel(-|.).*$|^http://(www\.)?houseofseven(-|.).*$|^http://(www\.)?hurricane(-|.).*$|^http://(www\.)?.*(-|.)?incest(-|.).*$|^http://(www\.)?iaea(-|.).*$|^http://(www\.)?ilovevitality(-|.).*$|^http://(www\.)?ime(-|.).*$|^http://(www\.)?info(-|.).*$|^http://(www\.)?ingyensms(-|.).*$|^http://(www\.)?inkjet-toner(-|.).*$|^http://(www\.)?isacommie(-|.).*$|^http://(www\.)?istarthere(-|.).*$|^http://(www\.)?it.tt(-|.).*$|^http://(www\.)?italiancharms(-|.).*$|^http://(www\.)?iwantu(-|.).*$|^http://(www\.)?ilovevitality(-|.).*$|^http://(www\.)?iskalko.ru(-|.).*$) 1;
}
## J - P
map $http_referer $bad_urls3 {
default 0;
~(?i)(^http://(www\.)?jfcadvocacy(-|.).*$|^http://(www\.)?jmhic(-|.).*$|^http://(www\.)?juris(-|.).*$|^http://(www\.)?kylos(-|.).*$|^http://(www\.)?laser-eye(-|.).*$|^http://(www\.)?leathertree(-|.).*$|^http://(www\.)?lillystar(-|.).*$|^http://(www\.)?linkerdome(-|.).*$|^http://(www\.)?livenet(-|.).*$|^http://(www\.)?low-limit(-|.).*$|^http://(www\.)?lowest-price(-|.).*$|^http://(www\.)?luxup.ru(-|.).*$|^http://(www\.)?macsurfer(-|.).*$|^http://(www\.)?mall.uk(-|.).*$|^http://(www\.)?maloylawn(-|.).*$|^http://(www\.)?marketing(-|.).*$|^http://(www\.)?.*(-|.)?mature(-|.).*$|^http://(www\.)?mcdortaklar(-|.).*$|^http://(www\.)?mediavisor(-|.).*$|^http://(www\.)?medications(-|.).*$|^http://(www\.)?mirror.sytes(-|.).*$|^http://(www\.)?mp3(-|.).*$|^http://(www\.)?(-|.)musicbox1(-|.).*$|^http://(www\.)?myftpupload(-|.).*$|^http://(www\.)?naked(-|.).*$|^http://(www\.)?netdisaster(-|.).*$|^http://(www\.)?netfirms(-|.).*$|^http://(www\.)?newtruths(-|.).*$|^http://(www\.)?no-limit(-|.).*$|^http://(www\.)?nude(-|.).*$|^http://(www\.)?nudeceleb(-|.).*$|^http://(www\.)?nutzu(-|.).*$|^http://(www\.)?odge(-|.).*$|^http://(www\.)?oiline(-|.).*$|^http://(www\.)?onlinegamingassoc(-|.).*$|^http://(www\.)?outpersonals(-|.).*$|^http://(www\.)?o-o-6-o-o.ru(-|.).*$|^http://(www\.)?o-o-8-o-o.ru(-|.).*$|^http://(www\.)?pagetwo(-|.).*$|^http://(www\.)?paris(-|.).*$|^http://(www\.)?passions(-|.).*$|^http://(www\.)?peblog(-|.).*$|^http://(www\.)?peng(-|.).*$|^http://(www\.)?perfume-cologne(-|.).*$|^http://(www\.)?personal(-|.).*$|^http://(www\.)?php-soft(-|.).*$|^http://(www\.)?pisoc(-|.).*$|^http://(www\.)?pisx(-|.).*$|^http://(www\.)?popwow(-|.).*$|^http://(www\.)?porn(-|.).*$|^http://(www\.)?prescriptions(-|.).*$|^http://(www\.)?priceg(-|.).*$|^http://(www\.)?.*(-|.)?pus*y(-|.).*$|^http://(www\.)?printdirectforless(-|.).*$|^http://(www\.)?ps2cool(-|.).*$|^http://(www\.)?psnarones(-|.).*$|^http://(www\.)?psxtreme(-|.).*$) 1;
}
## Q - Z
map $http_referer $bad_urls4 {
default 0;
~(?i)(^http://(www\.)?quality-traffic(-|.).*$|^http://(www\.)?registrarprice(-|.).*$|^http://(www\.)?reliableresults(-|.).*$|^http://(www\.)?rimpim(-|.).*$|^http://(www\.)?ro7kalbe(-|.).*$|^http://(www\.)?rohkalby(-|.).*$|^http://(www\.)?ronnieazza(-|.).*$|^http://(www\.)?rulo.biz(-|.).*$|^http://(www\.)?responsinator(-|.).*$|^http://(www\.)?s5(-|.).*$|^http://(www\.)?samiuls(-|.).*$|^http://(www\.)?savefrom(-|.).*$|^http://(www\.)?savetubevideo.com(-|.).*$|^http://(www\.)?screentoolkit.com(-|.).*$|^http://(www\.)?searchedu(-|.).*$|^http://(www\.)?semalt.com(-|.).*$|^http://(www\.)?seoexperimenty(-|.).*$|^http://(www\.)?seventwentyfour(-|.).*$|^http://(www\.)?seventwentyfour.*$|^http://(www\.)?sex(-|.).*$|^http://(www\.)?sexsearch(-|.).*$|^http://(www\.)?sexsq(-|.).*$|^http://(www\.)?shoesdiscount(-|.).*$|^http://(www\.)?site-4u(-|.).*$|^http://(www\.)?site5(-|.).*$|^http://(www\.)?slatersdvds(-|.).*$|^http://(www\.)?slftsdybbg.ru(-|.).*$|^http://(www\.)?sml338(-|.).*$|^http://(www\.)?sms(-|.).*$|^http://(www\.)?smsportali(-|.).*$|^http://(www\.)?socialseet.ru(-|.).*$|^http://(www\.)?software(-|.).*$|^http://(www\.)?sortthemesitesby(-|.).*$|^http://(www\.)?spears(-|.).*$|^http://(www\.)?spoodles(-|.).*$|^http://(www\.)?sportsparent(-|.).*$|^http://(www\.)?srecorder(-|.).*$|^http://(www\.)?stmaryonline(-|.).*$|^http://(www\.)?superiends.org(-|.).*$|^http://(www\.)?strip(-|.).*$|^http://(www\.)?suttonjames(-|.).*$|^http://(www\.)?talk.uk-yankee(-|.).*$|^http://(www\.)?tecrep-inc(-|.).*$|^http://(www\.)?teen(-|.).*$|^http://(www\.)?terashells(-|.).*$|^http://(www\.)?thatwhichis(-|.).*$|^http://(www\.)?thorcarlson(-|.).*$|^http://(www\.)?.*(-|.)?tits(-|.).*$|^http://(www\.)?.*(-|.)?titten(-|.).*$|^http://(www\.)?tmsathai(-|.).*$|^http://(www\.)?traffixer(-|.).*$|^http://(www\.)?tranny(-|.).*$|^http://(www\.)?valeof(-|.).*$|^http://(www\.)?video(-|.).*$|^http://(www\.)?vinhas(-|.).*$|^http://(www\.)?vixen1(-|.).*$|^http://(www\.)?vpshs(-|.).*$|^http://(www\.)?vrajitor(-|.).*$|^http://(www\.)?vodkoved.ru(-|.).*$|^http://(www\.)?w3md(-|.).*$|^http://(www\.)?websocial.me(-|.).*$|^http://(www\.)?webdevsquare(-|.).*$|^http://(www\.)?whois(-|.).*$|^http://(www\.)?withdrawal(-|.).*$|^http://(www\.)?worldemail(-|.).*$|^http://(www\.)?wslp24(-|.).*$|^http://(www\.)?ws-op(-|.).*$|^http://(www\.)?xnxx(-|.).*$|^http://(www\.)?xopy(-|.).*$|^http://(www\.)?xxx(-|.).*$|^http://(www\.)?yelucie(-|.).*$|^http://(www\.)?youradulthosting(-|.).*$|^http://(www\.)?ykecwqlixx.ru(-|.).*$|^http://(www\.)?yougetsignal.com(-|.).*$|^http://(www\.)?(-|.)zindagi(-|.).*$) 1;
}
## Domains Linked to Yontoo Browser Malware and a Few Other New Ones
## Have split this into it's own section to keep lines shorter NOTE: changes to instructions
## adding if ($bad_urls5) and if ($bad_urls6) to your site(s) config.
map $http_referer $bad_urls5 {
default 0;
~(?i)(^http://(www\.)?101raccoon.ru(-|.).*$|^http://(www\.)?28n2gl3wfyb0.ru(-|.).*$|^http://(www\.)?627ad6438b58439cad1fc8cf6d67a92e.com(-|.).*$|^http://(www\.)?6ab9743d0152486387559b4abaa02ada.com(-|.).*$|^http://(www\.)?a342ae9750004b14b55f7310eff0ab65.com(-|.).*$|^http://(www\.)?aa08daf7e13b6345e09e92f771507fa5f4.com(-|.).*$|^http://(www\.)?aa14ab57a3339c4064bd9ae6fad7495b5f.com(-|.).*$|^http://(www\.)?aa625d84f1587749c1ab011d6f269f7d64.com(-|.).*$|^http://(www\.)?aa81bf391151884adfa3dd677e41f94be1.com(-|.).*$|^http://(www\.)?aa8780bb28a1de4eb5bff33c28a218a930.com(-|.).*$|^http://(www\.)?aa8b68101d388c446389283820863176e7.com(-|.).*$|^http://(www\.)?aa9bd78f328a6a41279d0fad0a88df1901.com(-|.).*$|^http://(www\.)?aa9d046aab36af4ff182f097f840430d51.com(-|.).*$|^http://(www\.)?aaa38852e886ac4af1a3cff9b47cab6272.com(-|.).*$|^http://(www\.)?aab94f698f36684c5a852a2ef272e031bb.com(-|.).*$|^http://(www\.)?aac500b7a15b2646968f6bd8c6305869d7.com(-|.).*$|^http://(www\.)?aac52006ec82a24e08b665f4db2b5013f7.com(-|.).*$|^http://(www\.)?aad1f4acb0a373420d9b0c4202d38d94fa.com(-|.).*$|^http://(www\.)?asrv-a.akamoihd.net(-|.).*$|^http://(www\.)?asrvrep-a.akamaihd.net(-|.).*$|^http://(www\.)?bestpriceninja.com(-|.).*$|^http://(www\.)?bronzeaid-a.akamaihd.net(-|.).*$|^http://(www\.)?browsepulse-a.akamaihd.net(-|.).*$|^http://(www\.)?cashkitten-a.akamaihd.net(-|.).*$|^http://(www\.)?coolbar.pro(-|.).*$) 1;
}
map $http_referer $bad_urls6 {
default 0;
~(?i)(^http://(www\.)?davebestdeals.com(-|.).*$|^http://(www\.)?discovertreasure-a.akamaihd.net(-|.).*$|^http://(www\.)?discovertreasurenow.com(-|.).*$|^http://(www\.)?foxydeal.com(-|.).*$|^http://(www\.)?gameonasia.com(-|.).*$|^http://(www\.)?gameplexcity.com(-|.).*$|^http://(www\.)?gamerextra.com(-|.).*$|^http://(www\.)?gamerscorps.com(-|.).*$|^http://(www\.)?gamewrath.com(-|.).*$|^http://(www\.)?generousdeal-a.akamaihd.net(-|.).*$|^http://(www\.)?girlgamerdaily.com(-|.).*$|^http://(www\.)?hdapp1008-a.akamaihd.net(-|.).*$|^http://(www\.)?highstairs-a.akamaihd.net(-|.).*$|^http://(www\.)?hotshoppymac.com(-|.).*$|^http://(www\.)?matchpal-a.akamaihd.net(-|.).*$|^http://(www\.)?mecash.ru(-|.).*$|^http://(www\.)?monarchfind-a.akamaihd.net(-|.).*$|^http://(www\.)?myshopmatemac.com(-|.).*$|^http://(www\.)?nottyu.xyz(-|.).*$|^http://(www\.)?onlinemegax.com(-|.).*$|^http://(www\.)?outrageousdeal-a.akamaihd.net(-|.).*$|^http://(www\.)?pijoto.net(-|.).*$|^http://(www\.)?recordpage-a.akamaihd.net(-|.).*$|^http://(www\.)?resultshub-a.akamaihd.net(-|.).*$|^http://(www\.)?rvzr-a.akamaihd.net(-|.).*$|^http://(www\.)?savingsslider-a.akamaihd.net(-|.).*$|^http://(www\.)?searchinterneat-a.akamaihd.net(-|.).*$|^http://(www\.)?searchwebknow-a.akamaihd.net(-|.).*$|^http://(www\.)?seeresultshub-a.akamaihd.net(-|.).*$|^http://(www\.)?shoppytoolmac.com(-|.).*$|^http://(www\.)?skytraf.xyz(-|.).*$|^http://(www\.)?splendorsearch-a.akamaihd.net(-|.).*$|^http://(www\.)?strongsignal-a.akamaihd.net(-|.).*$|^http://(www\.)?surfbuyermac.com(-|.).*$|^http://(www\.)?treasuretrack-a.akamaihd.net(-|.).*$|^http://(www\.)?webshoppermac.com(-|.).*$|^http://(www\.)?pospr.waw.pl(-|.).*$|^http://(www\.)?abclauncher.com(-|.).*$|^http://(www\.)?alert-fjg.xyz(-|.).*$|^http://(www\.)?analytics-ads.xyz(-|.).*$|^http://(www\.)?bamo.xsl.pt(-|.).*$|^http://(www\.)?compliance-olga.top(-|.).*$|^http://(www\.)?digital-video-processing.com(-|.).*$|^http://(www\.)?eu-cookie-law.info(-|.).*$|^http://(www\.)?findpik.com(-|.).*$|^http://(www\.)?forum20.smailik.org(-|.).*$|^http://(www\.)?free-share-buttons.top(-|.).*$|^http://(www\.)?free-social-buttons2.xyz(-|.).*$|^http://(www\.)?free-social-buttons3.xyz(-|.).*$|^http://(www\.)?free-social-buttons4.xyz(-|.).*$|^http://(www\.)?free-social-buttons5.xyz(-|.).*$|^http://(www\.)?front.to(-|.).*$|^http://(www\.)?infokonkurs.ru(-|.).*$|^http://(www\.)?mapquestz.us(-|.).*$|^http://(www\.)?quick-offer.com(-|.).*$|^http://(www\.)?rank-checker.online(-|.).*$|^http://(www\.)?rankchecker.online(-|.).*$|^http://(www\.)?rapidokbrain.com(-|.).*$|^http://(www\.)?real-time-analytics.com(-|.).*$|^http://(www\.)?sharebutton.net(-|.).*$|^http://(www\.)?sharebutton.org(-|.).*$|^http://(www\.)?shemale-sex.net(-|.).*$|^http://(www\.)?site-speed-check.site(-|.).*$|^http://(www\.)?site-speed-checker.site(-|.).*$|^http://(www\.)?trafficmania.com(-|.).*$|^http://(www\.)?website-speed-up.site(-|.).*$|^http://(www\.)?website-speed-up.top(-|.).*$|^http://(www\.)?xn--80aagddcgkbcqbad7amllnejg6dya.xn--p1ai(-|.).*$|^http://(www\.)?xn--80aikhbrhr.net(-|.).*$|^http://(www\.)?pila.pl(-|.).*$|^http://(www\.)?dytohqka.su(-|.).*$|^http://(www\.)?fqvjhqciw.net.ru(-|.).*$|^http://(www\.)?wycjrqzy.ua(-|.).*$|^http://(www\.)?0ca29773681c7e82.com(-|.).*$|^http://(www\.)?intervsem.ru(-|.).*$|^http://(www\.)?candy-glam-hp.com(-|.).*$|^http://(www\.)?thecoolimages.net(-|.).*$|^http://(www\.)?rebuildermedical.com(-|.).*$|^http://(www\.)?gaygalls.net(-|.).*$|^http://(www\.)?keywordteam.net(-|.).*$|^http://(www\.)?netfacet.net(-|.).*$|^http://(www\.)?pattersonsweb.com(-|.).*$|^http://(www\.)?trapit.com.gg(-|.).*$) 1;
}
## Add here all hosts that should be spared any referrer checking.
## Whitelist all your own IPs in this section, each IP followed by a 0;
geo $bad_referer {
127.0.0.1 0;
40.82.153.189 0;
111.111.111.111 0;
}
# Geo directive to deny certain ip addresses
geo $validate_client {
default 0;
# Cyveillance
38.100.19.8/29 1;
38.100.21.0/24 1;
38.100.41.64/26 1;
38.105.71.0/25 1;
38.105.83.0/27 1;
38.112.21.140/30 1;
38.118.42.32/29 1;
65.213.208.128/27 1;
65.222.176.96/27 1;
65.222.185.72/29 1;
85.25.176.0/20 1;
85.25.192.0/20 1;
85.25.208.0/22 1;
}

View File

@ -0,0 +1,245 @@
# ===============================================
# Production Level Nginx Configuration (Sample Template)
# 서버 사양: 쿼드코어 CPU, 4GB RAM, ~50 req/s
# 백엔드: FastAPI REST API Server
# ===============================================
# HTTP 서버 블록 (포트 80) - HTTPS로 리다이렉트
server {
listen 80;
server_name demo.castad.net www.demo.castad.net;
# 보안을 위한 호스트 검증 - 허용되지 않은 도메인 차단
# 도메인 확장자가 다르다면 추가해줘야함
# if ($host !~* ^(www\.)?demo.castad.net\.(com|kr|net|org)$) {
# return 444;
# }
# Let's Encrypt 도메인 검증 프로그램 허용 (리다이렉트 전에 처리)
# SSL 인증서 갱신을 위해 필수
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404; # 디렉토리 순회 공격 방지
}
# HTTP를 HTTPS로 리다이렉트 (acme-challenge 제외)
# return 301은 rewrite보다 효율적이며 $server_name이 $host보다 안전함
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS 서버 블록 (포트 443) - 메인 애플리케이션
server {
listen 443 ssl;
http2 on;
server_name demo.castad.net www.demo.castad.net;
# 악성 봇 차단 (nginx.conf의 http 블록에서 $bad_bot 맵 정의 필요)
if ($bad_bot) {
return 403;
}
# SSL/TLS 인증서 설정
ssl_certificate /etc/letsencrypt/live/demo.castad.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/demo.castad.net/privkey.pem;
ssl_dhparam /etc/ssl/certs/demo.castad.net/dhparam.pem; # openssl dhparam -out /etc/ssl/certs/demo.castad.net/dhparam.pem 2048
# 최신 SSL/TLS 설정 - SSL Labs A+ 등급 달성 가능
ssl_session_cache shared:SSL:50m; # SSL 세션 캐시 크기 증가 (트래픽 많을 시 유용)
ssl_session_timeout 10m; # SSL 세션 타임아웃
ssl_session_tickets off; # 보안 향상을 위해 세션 티켓 비활성화 (nginx >= 1.5.9)
ssl_protocols TLSv1.2 TLSv1.3; # 최신 TLS 프로토콜만 사용
ssl_prefer_server_ciphers off; # TLSv1.3에서는 클라이언트 선호 암호화 사용 (모범 사례)
# 최신 암호화 스위트 - CHACHA20-POLY1305 포함 (모바일 최적화)
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
ssl_ecdh_curve secp384r1; # ECDH 곡선 설정 (nginx >= 1.1.0)
# OCSP 스테이플링 - SSL 핸드셰이크 성능 향상
# 인증서에 OCSP URL이 없으면 자동으로 비활성화됨 (경고는 정상)
#ssl_stapling on;
#ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/demo.castad.net/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s; # Cloudflare와 Google DNS 사용
resolver_timeout 5s;
# 보안 헤더 - 다양한 공격으로부터 보호
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # HSTS - 다운그레이드 공격 방지
add_header X-Frame-Options "DENY" always; # 클릭재킹 방지
add_header X-Content-Type-Options "nosniff" always; # MIME 스니핑 방지
add_header X-XSS-Protection "1; mode=block" always; # 레거시 XSS 보호
add_header Referrer-Policy "strict-origin-when-cross-origin" always; # 리퍼러 정보 제어
#add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' ''; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: ws:;" always;
#add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.tailwindcss.com https://www.youtube.com https://s.ytimg.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.tailwindcss.com; font-src 'self' data: https://fonts.gstatic.com; img-src 'self' data: https: https://i.ytimg.com https://img.youtube.com; connect-src 'self' wss: ws: https://cdn.tailwindcss.com; frame-src 'self' https://www.youtube.com https://www.youtube-nocookie.com; media-src 'self' https://www.youtube.com;" always;
add_header Content-Security-Policy "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;" always;
# XSS 방지를 위한 CSP
# 'unsafe-inline'은 범용 설정이 아니며, 보안상 위험한 설정입니다. CSP의 핵심 보호 기능을 무력화시키므로, 개발 환경이나 레거시 코드 마이그레이션 과정에서만 임시로 사용하고, 프로덕션 환경에서는 nonce, hash, 또는 외부 파일 분리 방식으로 대체해야 합니다.
# 클라이언트가 업로드할 수 있는 전체 요청 본문(body) 의 최대 허용 크기
# 요청 바디(파일, 폼 데이터 등)가 이 값을 초과하면 Nginx는 즉시 413 Request Entity Too Large 에러를 반환
# 업로드 제한선.
client_max_body_size 100M; # 최대 업로드 크기 제한 (애플리케이션에 맞게 조정)
# 파일 캐시 - I/O 성능 향상
open_file_cache max=1000 inactive=20s; # 최대 1000개 파일 캐시, 20초 비활성 시 제거
open_file_cache_valid 30s; # 캐시 유효성 검사 주기
open_file_cache_min_uses 2; # 최소 2회 사용 시 캐시
open_file_cache_errors on; # 파일 오류도 캐시
# 로깅 설정 - 버퍼링으로 I/O 감소
access_log /log/nginx/demo.castad.net.com.gunicorn_access.log main buffer=32k flush=5s;
error_log /log/nginx/demo.castad.net.com.gunicorn_error.log warn;
# frontend에 오류 페이지 정의가 되어 있는 경우 사용 가능
# # 커스텀 오류 페이지 - 더 나은 사용자 경험 및 정보 노출 방지
# error_page 404 /404.html;
# error_page 500 502 503 504 /50x.html;
# location = /404.html {
# internal; # 내부 리다이렉트만 허용
# root /www/error_pages;
# }
# location = /50x.html {
# internal; # 내부 리다이렉트만 허용
# root /www/error_pages;
# }
# 프론트엔드 정적 파일 루트
root /www/o2o-castad-frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Fastapi 미디어 파일 - 사용자 업로드 파일
location /media {
autoindex off; # 디렉토리 목록 비활성화
# gzip_static on; # 사전 압축된 .gz 파일 사용
expires 30d; # 브라우저 캐시 30일 후 브라우저가 다시 요청할 때 재검증
alias /www/o2o-castad-backend/media; # Fastapi 프로젝트의 미디어 파일 경로
# 정적 파일 캐싱 - 브라우저 캐시 최적화
add_header Cache-Control "public, immutable";
access_log off; # 액세스 로그 비활성화로 성능 향상
}
# Fastapi 정적 파일 - CSS, JS, 이미지 등
location /static {
autoindex off; # 디렉토리 목록 비활성화
# gzip_static on; # 사전 압축된 .gz 파일 사용 압축된 파일이 없다면 설정 무의미
expires 30d; # 브라우저 캐시 30일 후 브라우저가 다시 요청할 때 재검증
alias /www/o2o-castad-backend/static; # Fastapi 프로젝트의 정적 파일 경로
# 정적 파일 캐싱 - 브라우저 캐시 최적화
add_header Cache-Control "public, immutable";
access_log off; # 액세스 로그 비활성화로 성능 향상
}
# 메인 애플리케이션 - 백엔드로 프록시
location /api/ {
autoindex off; # 디렉토리 목록 비활성화
# 속도 제한 - DDoS 및 무차별 대입 공격 방지
# (nginx.conf의 http 블록에서 limit_req_zone과 limit_conn_zone 정의 필요)
#limit_req zone=general burst=20 nodelay; # 초당 요청 제한
#limit_conn addr 10; # IP당 동시 연결 제한
# HTTP 메서드 제한 - HTTP 동사 변조 공격 방지
limit_except GET POST HEAD OPTIONS DELETE {
deny all;
}
# 백엔드 애플리케이션으로 프록시
proxy_pass http://uvicorn-app:8000/;
# WebSocket 지원 - 실시간 통신 애플리케이션에 필수
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # nginx.conf에서 $connection_upgrade 맵 정의 필요
# 프록시 헤더 - 클라이언트 정보 전달
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# 타임아웃 설정 - 애플리케이션에 맞게 조정
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
# 버퍼 설정 - 대화형 애플리케이션에 최적화 - Websocket 사용시 설정
# proxy_buffering off; # 즉시 응답 전달
# proxy_request_buffering off; # 즉시 요청 전달
# 버퍼링 활성화 (기본값) - 기본 fastapi 사용시 설정
proxy_buffering on;
proxy_request_buffering on;
# 버퍼 크기 설정 - 기본 fastapi 사용시 설정
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
proxy_set_header Accept-Encoding gzip;
}
# Let's Encrypt 도메인 검증 프로그램 허용 (HTTPS에서도 필요시)
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404; # 디렉토리 순회 공격 방지
}
# 정적 리소스 캐싱 - 이미지, 폰트, CSS, JS 등
# 브라우저 캐시로 로드 시간 단축 및 서버 부하 감소
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y; # 1년 캐시
add_header Cache-Control "public, immutable";
access_log off; # 액세스 로그 비활성화로 성능 향상
}
# 닷 파일 차단 - .htaccess, .htpasswd, .svn, .git, .env 등
# 민감한 설정 파일 노출 방지
location ~ /\. {
deny all;
access_log off; # 차단된 시도 로그 비활성화
log_not_found off;
}
# 민감한 파일 확장자 차단 - 로그, 인증서, 스크립트, SQL 등
# 보안을 위해 직접 접근 차단
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
access_log off; # 차단된 시도 로그 비활성화
log_not_found off;
}
# 민감한 설정 파일 차단 - composer, package.json, phpunit 등
# 프로젝트 메타데이터 및 설정 파일 노출 방지
location ~* (composer\.json|composer\.lock|composer\.phar|contributing\.md|license\.txt|readme\.rst|readme\.md|readme\.txt|copyright|artisan|gulpfile\.js|package\.json|phpunit\.xml|access_log|error_log|gruntfile\.js)$ {
deny all;
access_log off; # 차단된 시도 로그 비활성화
log_not_found off;
}
# 파비콘 - 로그 노이즈 제거
location = /favicon.ico {
log_not_found off;
access_log off;
}
# robots.txt - 검색 엔진 크롤러 제어
location = /robots.txt {
log_not_found off;
access_log off;
allow all;
}
}

71
config/web-server/nginx_conf.sh Executable file
View File

@ -0,0 +1,71 @@
#!/bin/bash
while :
do
echo "* if your webroot has sub-level, you should be insert as \\\/A\\\/B\\\/C"
echo "ex) shop\\\/django_sample"
echo -n "Enter the service web root without the path of '/www/' >"
read webroot
echo "Entered service web root: $webroot"
if [[ "$webroot" != "" ]]; then
break
fi
done
while :
do
echo -n "Enter the service portnumber >"
read portnumber
echo "Entered service portnumber: $portnumber"
if [[ "$portnumber" != "" ]]; then
break
fi
done
while :
do
echo -n "Enter the service domain >"
read domain
echo "Entered service domain: $domain"
if [[ "$domain" != "" ]]; then
break
fi
done
while :
do
echo -n "Enter the app name >"
read appname
echo "Entered app name: $appname"
if [[ "$appname" != "" ]]; then
break
fi
done
echo "Enter the serviceport"
echo -n "if you push enter with none, there are no port number >"
read serviceport
echo "Entered proxy port: $serviceport"
while :
do
echo -n "Enter the file name >"
read filename
echo "Entered file name: $filename"
if [[ "$filename" != "" ]]; then
break
fi
done
sed 's/webroot/'$webroot'/g' sample_nginx.conf > $filename'1'.temp
sed 's/portnumber/'$portnumber'/g' $filename'1'.temp > $filename'2'.temp
sed 's/domain/'$domain'/g' $filename'2'.temp > $filename'3'.temp
sed 's/appname/'$appname'/g' $filename'3'.temp > $filename'4'.temp
if [[ "$serviceport" == "" ]]; then
sed 's/:serviceport/''/g' $filename'4'.temp > $filename'5'.temp
else
sed 's/serviceport/'$serviceport'/g' $filename'4'.temp > $filename'5'.temp
fi
sed 's/filename/'$filename'/g' $filename'5'.temp > ./conf.d/$filename'_gunicorn_ng'.conf
rm *.temp

View File

@ -0,0 +1,126 @@
user www-data;
worker_processes auto;
# worker_rlimit_nofile directive
# CPU: 쿼드코어, RAM: 4GB, 요청수: ~50/s 기반 설정시
worker_rlimit_nofile 8192;
# worker_rlimit_nofile 8192;
# worker_priority 0;
# worker_cpu_affinity 0001 0010 0100 1000;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
# Load ModSecurity dynamic module
# load_module /etc/nginx/modules/ngx_http_modsecurity_module.so
events {
use epoll; # 리눅스에서 권장
accept_mutex on; # 기본값이지만 명시해두면 좋음
# 단일 워커당 동시 연결
# worker_processes * worker_connections = 최대 동시 연결
# 여기에 워커가 상시 쓰는 FD(에러/액세스 로그, 리스닝 소켓, epoll 등) 오버헤드를 조금 빼고 잡아야 안전하다 (약 50~150 정도 버퍼).
# CPU: 쿼드코어, RAM: 4GB, 요청수: ~50/s 기반 설정시
# off 설정에 대한 효과
# 부하 분산: 워커 균등한 연결 분배
# 안정성: 워커가 과부하되는 방지
# 예측 가능한 성능: 일관된 응답 시간
multi_accept off;
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# WebSocket HTTP keepalive 동시 지원을 위한 Connection 헤더 동적 설정
# - WebSocket 요청 ($http_upgrade가 있는 경우): "upgrade" 반환
# - 일반 HTTP 요청 ($http_upgrade가 없는 경우): "" 반환 (keepalive 연결 유지)
map $http_upgrade $connection_upgrade {
default upgrade;
'' '';
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/main_access.log main;
error_log /var/log/nginx/main_error.log;
server_tokens off;
charset utf-8;
# Docker 내부 DNS 사용 (컨테이너 이름 해석)
resolver 127.0.0.11 valid=30s ipv6=off;
resolver_timeout 5s;
# 기본 헤더 버퍼 (대부분의 요청 처리)
client_header_buffer_size 4k;
# 헤더 버퍼 (JWT, 쿠키 등)
large_client_header_buffers 4 16k;
# POST나 PUT 요청으로 전송하는 본문(body) 데이터를 받을 때, Nginx가 메모리(RAM)에 임시 저장할 버퍼의 크기
client_body_buffer_size 128k; # 일반적으로 64KB~256KB 권장
client_body_timeout 15s; # 클라이언트 본문 수신 타임아웃
client_header_timeout 15s; # 클라이언트 헤더 수신 타임아웃
# 효율적인 파일 전송 설정
sendfile on; # 커널 공간에서 직접 파일 전송 (제로카피)
tcp_nopush on; # sendfile 사용 패킷 효율 향상
tcp_nodelay on; # Keep-alive 연결에서 지연 없이 전송 (실시간 통신에 유리)
# Keep-alive 연결 설정
keepalive_timeout 30s; # 연결 유지 시간
keepalive_requests 1000; # 연결당 최대 요청
send_timeout 15s; # 클라이언트로 응답 전송 타임아웃
# ===== 파일 업로드 설정 =====
client_max_body_size 50M; # 최대 업로드 크기 (필요에 따라 조정)
# ===== 프록시 타임아웃 설정 =====
proxy_connect_timeout 300s; # 백엔드 연결 타임아웃
proxy_send_timeout 300s; # 백엔드로 요청 전송 타임아웃
proxy_read_timeout 300s; # 백엔드 응답 수신 타임아웃
proxy_request_buffering off; # 대용량 파일 스트리밍 업로드 (버퍼링 비활성화)
# ===== 해시 테이블 =====
# MIME 타입 해시 테이블 설정
# mime.types 파일에 정의된 파일 확장자와 MIME 타입 매핑을 저장
# 예: .html -> text/html, .jpg -> image/jpeg 등의 매핑을 빠르게 찾기 위해 사용됨
types_hash_max_size 2048; # 기본 mime.types의 수백 타입을 충분히 수용하는 크기
types_hash_bucket_size 64; # 해시 버킷 크기, CPU 캐시 라인과 정렬하여 성능 최적화
# 서버 이름 해시 테이블 설정 (server_name 지시자에 정의된 도메인명만 해당)
# server 블록의 server_name 지시자에 설정된 도메인명들을 빠르게 매칭하기 위한 해시 테이블
server_names_hash_max_size 1024; # 여러 도메인/서브도메인 운영 충분한 공간 확보
server_names_hash_bucket_size 64; # 도메인명 길이를 고려한 버킷 크기 (일반적으로 32~64면 충분)
# Nginx 변수 해시 테이블 설정
# $host, $remote_addr 같은 내장 변수와 map/set으로 정의한 커스텀 변수들을 저장하는 해시 테이블
# HTTP 헤더를 변수로 변환한 $http_* 변수들도 여기에 포함됨 (예: $http_user_agent, $http_referer)
variables_hash_max_size 2048; # 내장 변수 + 커스텀 변수를 위한 충분한 공간
variables_hash_bucket_size 64; # 변수명 길이와 충돌 방지를 위한 적절한 버킷 크기
# ===== 압축 설정 =====
# 실시간 압축 비활성화 (CPU 부하 감소)
gzip off;
# 미리 압축된 .gz 파일 제공
# 예: style.css 요청 style.css.gz 파일을 찾아서 전송
gzip_static on;
# Vary: Accept-Encoding 헤더 추가 (프록시 캐시 호환성)
gzip_vary on;
include /etc/nginx/proxy_params/*;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}

View File

@ -0,0 +1,71 @@
#!/bin/bash
while :
do
echo "* if your webroot has sub-level, you should be insert as \\\/A\\\/B\\\/C"
echo "ex) shop\\\/django_sample"
echo -n "Enter the service web root without the path of '/www/' >"
read webroot
echo "Entered service web root: $webroot"
if [[ "$webroot" != "" ]]; then
break
fi
done
while :
do
echo -n "Enter the service portnumber >"
read portnumber
echo "Entered service portnumber: $portnumber"
if [[ "$portnumber" != "" ]]; then
break
fi
done
while :
do
echo -n "Enter the service domain >"
read domain
echo "Entered service domain: $domain"
if [[ "$domain" != "" ]]; then
break
fi
done
while :
do
echo -n "Enter the app name >"
read appname
echo "Entered app name: $appname"
if [[ "$appname" != "" ]]; then
break
fi
done
echo "Enter the serviceport"
echo -n "if you push enter with none, there are no port number >"
read serviceport
echo "Entered proxy port: $serviceport"
while :
do
echo -n "Enter the file name >"
read filename
echo "Entered file name: $filename"
if [[ "$filename" != "" ]]; then
break
fi
done
sed 's/webroot/'$webroot'/g' sample_nginx_https.conf > $filename'1'.temp
sed 's/portnumber/'$portnumber'/g' $filename'1'.temp > $filename'2'.temp
sed 's/domain/'$domain'/g' $filename'2'.temp > $filename'3'.temp
sed 's/appname/'$appname'/g' $filename'3'.temp > $filename'4'.temp
if [[ "$serviceport" == "" ]]; then
sed 's/:serviceport/''/g' $filename'4'.temp > $filename'5'.temp
else
sed 's/serviceport/'$serviceport'/g' $filename'4'.temp > $filename'5'.temp
fi
sed 's/filename/'$filename'/g' $filename'5'.temp > ./conf.d/$filename'_gunicorn_https_ng'.conf
rm *.temp

View File

@ -0,0 +1,14 @@
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cache_bypass $http_upgrade;
proxy_buffering off;
proxy_redirect off;
# proxy_connect_timeout, proxy_send_timeout, proxy_read_timeout은 nginx.conf에서 설정
proxy_buffers 32 4k;
proxy_headers_hash_max_size 512;
proxy_headers_hash_bucket_size 64;

View File

@ -0,0 +1,73 @@
server {
listen portnumber;
server_name domain www.domain;
if ($bad_bot) {
return 403;
}
access_log /log/nginx/filename.com.gunicorn_access.log main;
error_log /log/nginx/filename.com.gunicorn_error.log warn;
# if ($host !~* ^(domain\.com|www\.domain\.com)$) {
# return 444;
# }
# Django media
location /media {
autoindex off;
gzip_static on;
expires max;
#alias /www/django_sample/media;
alias /www/webroot/media; # your Django project's media files - amend as required
#include /etc/nginx/mime.types;
}
location /static {
autoindex off;
gzip_static on;
expires max;
#alias /www/django_sample/static;
# normally static folder is named as /static
alias /www/webroot/static; # your Django project's static files - amend as required
#include /etc/nginx/mime.types;
}
location / {
autoindex off;
proxy_pass http://appname:serviceport;
# proxy_redirect http:// https://;
}
# Allow Lets Encrypt Domain Validation Program
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/webroot;
}
# Block dot file (.htaccess .htpasswd .svn .git .env and so on.)
location ~ /\. {
deny all;
}
# Block (log file, binary, certificate, shell script, sql dump file) access.
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
}
# Block access
location ~* (composer\.json|composer\.lock|composer\.phar|contributing\.md|license\.txt|readme\.rst|readme\.md|readme\.txt|copyright|artisan|gulpfile\.js|package\.json|phpunit\.xml|access_log|error_log|gruntfile\.js)$ {
deny all;
}
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
log_not_found off;
access_log off;
}
}

View File

@ -0,0 +1,232 @@
# ===============================================
# Production Level Nginx Configuration (Sample Template)
# 서버 사양: 쿼드코어 CPU, 4GB RAM, ~50 req/s
# 백엔드: FastAPI REST API Server
# ===============================================
# HTTP 서버 블록 (포트 80) - HTTPS로 리다이렉트
server {
listen 80;
server_name domain www.domain;
# 보안을 위한 호스트 검증 - 허용되지 않은 도메인 차단
# 도메인 확장자가 다르다면 추가해줘야함
if ($host !~* ^(www\.)?domain\.(com|kr|net|org)$) {
return 444;
}
# Let's Encrypt 도메인 검증 프로그램 허용 (리다이렉트 전에 처리)
# SSL 인증서 갱신을 위해 필수
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404; # 디렉토리 순회 공격 방지
}
# HTTP를 HTTPS로 리다이렉트 (acme-challenge 제외)
# return 301은 rewrite보다 효율적이며 $server_name이 $host보다 안전함
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS 서버 블록 (포트 443) - 메인 애플리케이션
server {
listen 443 ssl http2;
server_name domain.com www.domain.com;
# 악성 봇 차단 (nginx.conf의 http 블록에서 $bad_bot 맵 정의 필요)
if ($bad_bot) {
return 403;
}
# SSL/TLS 인증서 설정
ssl_certificate /etc/letsencrypt/live/domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain/privkey.pem;
ssl_dhparam /etc/ssl/certs/domain/dhparam.pem; # openssl dhparam -out /etc/ssl/certs/domain/dhparam.pem 2048
# 최신 SSL/TLS 설정 - SSL Labs A+ 등급 달성 가능
ssl_session_cache shared:SSL:50m; # SSL 세션 캐시 크기 증가 (트래픽 많을 시 유용)
ssl_session_timeout 10m; # SSL 세션 타임아웃
ssl_session_tickets off; # 보안 향상을 위해 세션 티켓 비활성화 (nginx >= 1.5.9)
ssl_protocols TLSv1.2 TLSv1.3; # 최신 TLS 프로토콜만 사용
ssl_prefer_server_ciphers off; # TLSv1.3에서는 클라이언트 선호 암호화 사용 (모범 사례)
# 최신 암호화 스위트 - CHACHA20-POLY1305 포함 (모바일 최적화)
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
ssl_ecdh_curve secp384r1; # ECDH 곡선 설정 (nginx >= 1.1.0)
# OCSP 스테이플링 - SSL 핸드셰이크 성능 향상
# 인증서에 OCSP URL이 없으면 자동으로 비활성화됨 (경고는 정상)
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/domain/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s; # Cloudflare와 Google DNS 사용
resolver_timeout 5s;
# 보안 헤더 - 다양한 공격으로부터 보호
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # HSTS - 다운그레이드 공격 방지
add_header X-Frame-Options "DENY" always; # 클릭재킹 방지
add_header X-Content-Type-Options "nosniff" always; # MIME 스니핑 방지
add_header X-XSS-Protection "1; mode=block" always; # 레거시 XSS 보호
add_header Referrer-Policy "strict-origin-when-cross-origin" always; # 리퍼러 정보 제어
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' ''; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: ws:;" always;
# XSS 방지를 위한 CSP
# 'unsafe-inline'은 범용 설정이 아니며, 보안상 위험한 설정입니다. CSP의 핵심 보호 기능을 무력화시키므로, 개발 환경이나 레거시 코드 마이그레이션 과정에서만 임시로 사용하고, 프로덕션 환경에서는 nonce, hash, 또는 외부 파일 분리 방식으로 대체해야 합니다.
# 클라이언트가 업로드할 수 있는 전체 요청 본문(body) 의 최대 허용 크기
# 요청 바디(파일, 폼 데이터 등)가 이 값을 초과하면 Nginx는 즉시 413 Request Entity Too Large 에러를 반환
# 업로드 제한선.
client_max_body_size 100M; # 최대 업로드 크기 제한 (애플리케이션에 맞게 조정)
# 파일 캐시 - I/O 성능 향상
open_file_cache max=1000 inactive=20s; # 최대 1000개 파일 캐시, 20초 비활성 시 제거
open_file_cache_valid 30s; # 캐시 유효성 검사 주기
open_file_cache_min_uses 2; # 최소 2회 사용 시 캐시
open_file_cache_errors on; # 파일 오류도 캐시
# 로깅 설정 - 버퍼링으로 I/O 감소
access_log /log/nginx/domain.com.gunicorn_access.log main buffer=32k flush=5s;
error_log /log/nginx/domain.com.gunicorn_error.log warn;
# frontend에 오류 페이지 정의가 되어 있는 경우 사용 가능
# # 커스텀 오류 페이지 - 더 나은 사용자 경험 및 정보 노출 방지
# error_page 404 /404.html;
# error_page 500 502 503 504 /50x.html;
# location = /404.html {
# internal; # 내부 리다이렉트만 허용
# root /www/error_pages;
# }
# location = /50x.html {
# internal; # 내부 리다이렉트만 허용
# root /www/error_pages;
# }
# Fastapi 미디어 파일 - 사용자 업로드 파일
location /media {
autoindex off; # 디렉토리 목록 비활성화
# gzip_static on; # 사전 압축된 .gz 파일 사용
expires 30d; # 브라우저 캐시 30일 후 브라우저가 다시 요청할 때 재검증
alias /www/webroot/media; # Fastapi 프로젝트의 미디어 파일 경로
# 정적 파일 캐싱 - 브라우저 캐시 최적화
add_header Cache-Control "public, immutable";
access_log off; # 액세스 로그 비활성화로 성능 향상
}
# Fastapi 정적 파일 - CSS, JS, 이미지 등
location /static {
autoindex off; # 디렉토리 목록 비활성화
# gzip_static on; # 사전 압축된 .gz 파일 사용 압축된 파일이 없다면 설정 무의미
expires 30d; # 브라우저 캐시 30일 후 브라우저가 다시 요청할 때 재검증
alias /www/webroot/static; # Fastapi 프로젝트의 정적 파일 경로
# 정적 파일 캐싱 - 브라우저 캐시 최적화
add_header Cache-Control "public, immutable";
access_log off; # 액세스 로그 비활성화로 성능 향상
}
# 메인 애플리케이션 - 백엔드로 프록시
location / {
autoindex off; # 디렉토리 목록 비활성화
# 속도 제한 - DDoS 및 무차별 대입 공격 방지
# (nginx.conf의 http 블록에서 limit_req_zone과 limit_conn_zone 정의 필요)
limit_req zone=general burst=20 nodelay; # 초당 요청 제한
limit_conn addr 10; # IP당 동시 연결 제한
# HTTP 메서드 제한 - HTTP 동사 변조 공격 방지
limit_except GET POST HEAD OPTIONS {
deny all;
}
# 백엔드 애플리케이션으로 프록시
proxy_pass http://appname:serviceport;
# WebSocket 지원 - 실시간 통신 애플리케이션에 필수
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade; # nginx.conf에서 $connection_upgrade 맵 정의 필요
# 프록시 헤더 - 클라이언트 정보 전달
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# 타임아웃 설정 - 애플리케이션에 맞게 조정
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 버퍼 설정 - 대화형 애플리케이션에 최적화 - Websocket 사용시 설정
# proxy_buffering off; # 즉시 응답 전달
# proxy_request_buffering off; # 즉시 요청 전달
# 버퍼링 활성화 (기본값) - 기본 fastapi 사용시 설정
proxy_buffering on;
proxy_request_buffering on;
# 버퍼 크기 설정 - 기본 fastapi 사용시 설정
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
proxy_set_header Accept-Encoding gzip;
}
# Let's Encrypt 도메인 검증 프로그램 허용 (HTTPS에서도 필요시)
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404; # 디렉토리 순회 공격 방지
}
# 정적 리소스 캐싱 - 이미지, 폰트, CSS, JS 등
# 브라우저 캐시로 로드 시간 단축 및 서버 부하 감소
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y; # 1년 캐시
add_header Cache-Control "public, immutable";
access_log off; # 액세스 로그 비활성화로 성능 향상
}
# 닷 파일 차단 - .htaccess, .htpasswd, .svn, .git, .env 등
# 민감한 설정 파일 노출 방지
location ~ /\. {
deny all;
access_log off; # 차단된 시도 로그 비활성화
log_not_found off;
}
# 민감한 파일 확장자 차단 - 로그, 인증서, 스크립트, SQL 등
# 보안을 위해 직접 접근 차단
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
access_log off; # 차단된 시도 로그 비활성화
log_not_found off;
}
# 민감한 설정 파일 차단 - composer, package.json, phpunit 등
# 프로젝트 메타데이터 및 설정 파일 노출 방지
location ~* (composer\.json|composer\.lock|composer\.phar|contributing\.md|license\.txt|readme\.rst|readme\.md|readme\.txt|copyright|artisan|gulpfile\.js|package\.json|phpunit\.xml|access_log|error_log|gruntfile\.js)$ {
deny all;
access_log off; # 차단된 시도 로그 비활성화
log_not_found off;
}
# 파비콘 - 로그 노이즈 제거
location = /favicon.ico {
log_not_found off;
access_log off;
}
# robots.txt - 검색 엔진 크롤러 제어
location = /robots.txt {
log_not_found off;
access_log off;
allow all;
}
}

View File

@ -0,0 +1,75 @@
FROM ubuntu:24.04
ENV TZ=Asia/Seoul
ENV PYTHONUNBUFFERED=1
ENV DEBIAN_FRONTEND=noninteractive
# ========================================
# 1. Base packages & timezone setup
# ========================================
RUN apt-get update && \
apt-get install -y --no-install-recommends apt-utils && \
apt-get install -yq tzdata && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone && \
apt-get install -y \
curl wget git tar gnupg2 lsb-release lz4 zstd vim \
build-essential zlib1g-dev libncurses5-dev libgdbm-dev \
libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev \
python3-dev libmysqlclient-dev pkg-config ca-certificates
# ========================================
# 2. Python 3.13 build & install
# ========================================
RUN cd /usr/src && \
wget https://www.python.org/ftp/python/3.13.11/Python-3.13.11.tar.xz && \
tar -xf Python-3.13.11.tar.xz && \
cd Python-3.13.11 && \
./configure --enable-optimizations && \
make altinstall && \
rm -rf /usr/src/Python-3.13.11 /usr/src/Python-3.13.11.tar.xz
# ========================================
# 3. Python symlinks
# ========================================
RUN rm -f /usr/bin/python /usr/bin/python3 && \
ln -s /usr/local/bin/python3.13 /usr/bin/python && \
ln -s /usr/local/bin/python3.13 /usr/bin/python3 && \
ln -s /usr/local/bin/python3.13 /usr/local/bin/python && \
ln -s /usr/local/bin/python3.13 /usr/local/bin/python3 && \
ln -s /usr/local/bin/pip3.13 /usr/bin/pip && \
ln -s /usr/local/bin/pip3.13 /usr/bin/pip3 && \
ln -s /usr/local/bin/pip3.13 /usr/local/bin/pip && \
ln -s /usr/local/bin/pip3.13 /usr/local/bin/pip3
# ========================================
# 4. Python packages
# ========================================
RUN pip install --upgrade pip && \
pip install wheel && \
pip install sqlalchemy alembic pydantic && \
pip install psycopg2-binary asyncpg && \
pip install mysqlclient asyncmy && \
pip install gunicorn uvicorn[standard] && \
pip install fastapi uv poetry
# ========================================
# 5. Percona XtraBackup (mysql backup)
# ========================================
RUN curl -O https://repo.percona.com/apt/percona-release_latest.generic_all.deb && \
apt-get install -y ./percona-release_latest.generic_all.deb && \
rm -f percona-release_latest.generic_all.deb && \
apt-get update && \
percona-release enable pxb-84-lts && \
apt-get install -y percona-xtrabackup-84
# ========================================
# 6. PostgreSQL backup (pgbackrest)
# ========================================
RUN apt-get install -y pgbackrest
# ========================================
# 7. Cleanup
# ========================================
RUN apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
update-ca-certificates

45
docker/nginx/Dockerfile Normal file
View File

@ -0,0 +1,45 @@
FROM nginx:1.26-bookworm
ENV TZ=Asia/Seoul
ENV DEBIAN_FRONTEND=noninteractive
# ========================================
# 1. Base packages & timezone setup
# ========================================
RUN apt-get update && \
apt-get install -y --no-install-recommends apt-utils && \
apt-get install -yq tzdata && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# ========================================
# 2. Required packages
# ========================================
RUN apt-get install -y sendmail wget gnupg ca-certificates
# ========================================
# 3. Cron & Certbot (SSL auto-renewal)
# ========================================
RUN apt-get install -y cron certbot python3-certbot-nginx
# ========================================
# 4. CA certificates
# ========================================
RUN update-ca-certificates && \
chmod 644 /etc/ssl/certs/ca-certificates.crt
# ========================================
# 5. Cleanup
# ========================================
RUN apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# ========================================
# 6. Certbot auto-renewal cron job
# ========================================
RUN crontab -l 2>/dev/null | { cat; echo "0 5 * * 1 certbot renew --quiet --deploy-hook \"nginx -t && service nginx reload\" >> /log/nginx/crontab_\$(date +\%Y\%m\%d).log 2>&1"; } | crontab -
# ========================================
# 7. Add cron to nginx entrypoint
# ========================================
RUN sed -i'' -r -e "/set/i\cron" /docker-entrypoint.sh || true

0
log/mysql/.gitkeep Normal file
View File

0
log/nginx/.gitkeep Normal file
View File

0
log/postgresql/.gitkeep Normal file
View File

View File

View File

3
script/crontab_gunicorn_set.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
echo "0 6 * * 1 root docker restart nginx-gunicorn-webserver" >> /etc/crontab

91
script/letsencrypt.sh Executable file
View File

@ -0,0 +1,91 @@
#!/bin/bash
my_array=()
delimiter="-d"
domain_string=""
apt-get update && apt-get install -y sendmail wget vim cron certbot python3-certbot-nginx ca-certificates
while :
do
echo -n "Enter the service webroot_folder >"
read webroot_folder
echo "Entered service webroot_folder: $webroot_folder"
if [[ "$webroot_folder" != "" ]]; then
break
fi
done
while :
do
echo -n "To add a subdomain, type something like 'aaa.com www.aaa.com sub.aaa.com', but all domains refer to the same web root"
echo -n "A domain in aaa.com format must be entered first."
echo -n "Enter the service domain >"
read domain
echo "Entered service domain: $domain"
if [[ "$domain" != "" ]]; then
break
fi
done
IFS=' ' read -ra my_array <<< "$domain"
while :
do
echo -n "Enter the user e-mail >"
read mail
echo "Entered user e-mail: $mail"
if [[ "$mail" != "" ]]; then
break
fi
done
for element in "${my_array[@]}"; do
domain_string+=" $delimiter $element"
done
# Remove leading space
# domain_string="${domain_string# }"
# for element in "${my_array[@]}"; do
if ! test -f /ssl/${my_array[0]}/dhparam.pem ; then
if ! test -f /etc/ssl/certs/${my_array[0]}/dhparam.pem ; then
echo "try to create ssl key using openssl "
if ! test -d /etc/ssl/certs/${my_array[0]}/ ; then
echo "create "${my_array[0]}" folder: /etc/ssl/certs/"${my_array[0]}"/"
mkdir -p /etc/ssl/certs/${my_array[0]}/
fi
openssl dhparam -out /etc/ssl/certs/${my_array[0]}/dhparam.pem 4096
if ! test -d /ssl/${my_array[0]}/ ; then
echo "create "${my_array[0]}" folder: /ssl/"${my_array[0]}"/"
mkdir -p /ssl/${my_array[0]}/
fi
cp /etc/ssl/certs/${my_array[0]}/dhparam.pem /ssl/${my_array[0]}/ -r
# else
# echo "copy ssl folder by already maden"
# cp /ssl/certs/$domain/dhparam.pem /etc/ssl/certs/dhparam.pem -r
fi
else
if ! test -d /etc/ssl/certs/${my_array[0]}/ ; then
echo "create "${my_array[0]}" folder: /etc/ssl/certs/"${my_array[0]}"/"
mkdir -p /etc/ssl/certs/${my_array[0]}/
fi
cp /ssl/${my_array[0]}/dhparam.pem /etc/ssl/certs/${my_array[0]}/ -r
fi
# done
#if ! test -d /etc/letsencrypt/live/test.com ;
if ! test -d /etc/letsencrypt/${my_array[0]}/letsencrypt ; then
echo "try to create authentication key using certbot "
certbot certonly --non-interactive --agree-tos --email $mail --webroot -w /www/$webroot_folder$domain_string
echo "certbot certonly --non-interactive --agree-tos --email "$mail" --webroot -w /www/"$webroot_folder$domain_string
# if ! test -d /ssl/letsencrypt/$domain/ ; then
# echo "create domain folder: /ssl/letsencrypt/"$domain"/"
# mkdir -p /ssl/letsencrypt/$domain/
# fi
#cp /etc/letsencrypt/ /ssl/letsencrypt/$domain/ -r
# else
# echo "copy letsencrypt folder by already maden"
# cp /ssl/letsencrypt/$domain/ /etc/letsencrypt/ -r
fi
cat <(crontab -l) <(echo '0 5 * * 1 certbot renew --quiet --deploy-hook "service nginx restart" > /log/nginx/crontab_renew.log 2>&1') | crontab -

View File

@ -0,0 +1,21 @@
# Nginx 로그 로테이션 설정
# - Docker 컨테이너 환경에서 무중단 운영을 위해 copytruncate 방식 사용
# - copytruncate: 로그 파일을 복사 후 원본을 비우는 방식 (서비스 재시작 불필요)
# - 로테이션 순간 극소량의 로그가 누락될 수 있으나 서비스는 중단되지 않음
/log/nginx/*.log {
daily
size 100M
rotate 30
missingok
notifempty
compress
delaycompress
create 0640 nginx nginx
sharedscripts
postrotate
# Nginx에 USR1 signal 전송
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
endscript
}

View File

@ -0,0 +1,14 @@
# Uvicorn Celery Worker 로그 로테이션 설정
# - Docker 컨테이너 환경에서 무중단 운영을 위해 copytruncate 방식 사용
# - copytruncate: 로그 파일을 복사 후 원본을 비우는 방식 (서비스 재시작 불필요)
# - 로테이션 순간 극소량의 로그가 누락될 수 있으나 서비스는 중단되지 않음
/log/uvicorn/celery/*.log {
daily
size 100M
rotate 30
missingok
notifempty
compress
delaycompress
copytruncate
}

View File

@ -0,0 +1,13 @@
# Uvicorn Celery Beat 로그 로테이션 설정
# - Docker 컨테이너 환경에서 무중단 운영을 위해 copytruncate 방식 사용
# - copytruncate: 로그 파일을 복사 후 원본을 비우는 방식 (서비스 재시작 불필요)
# - 로테이션 순간 극소량의 로그가 누락될 수 있으나 서비스는 중단되지 않음
/log/uvicorn/celerybeat/*.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
copytruncate
}

View File

@ -0,0 +1,14 @@
# Uvicorn 로그 로테이션 설정
# - Docker 컨테이너 환경에서 무중단 운영을 위해 copytruncate 방식 사용
# - copytruncate: 로그 파일을 복사 후 원본을 비우는 방식 (서비스 재시작 불필요)
# - 로테이션 순간 극소량의 로그가 누락될 수 있으나 서비스는 중단되지 않음
/log/uvicorn/*.log {
daily
size 100M
rotate 30
missingok
notifempty
compress
delaycompress
copytruncate
}

0
www/.gitkeep Normal file
View File