gunicorn 명령어 기반 uvicorn 실행시 사용되는 설정파일 추가, logrotate 설정 추가.

main
bluebamus 2025-11-10 10:14:27 +09:00
parent 628b22dd03
commit 94b5633065
29 changed files with 4979 additions and 0 deletions

View File

@ -0,0 +1,11 @@
PROJECT_DIR=django_sample
WORKERS=4
GUNICORN_PORT=8000
REQUIREMENTS=./requirements.txt
PROJECT_NAME=django_sample
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

View File

@ -0,0 +1,158 @@
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/nginx/gunicorn/conf.d/:/etc/nginx/conf.d/
- ../../../config/web-server/nginx/gunicorn/nginx_conf/nginx.conf:/etc/nginx/nginx.conf
- ../../../config/web-server/nginx/gunicorn/proxy_params/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
environment:
TZ: "Asia/Seoul"
restart: always
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
volumes:
- ../../../www:/www
- ../../../log:/log
- ../../../config/app-server/uvicorn/:/uvicorn
- ../../../script/logrotate/uvicorn/uvicorn:/etc/logrotate.d/uvicorn
command: bash -c "poetry config virtualenvs.create false && poetry install --no-root --without test && gunicorn -c /uvicorn/gunicorn_uvicorn.conf.py"
environment:
TZ: "Asia/Seoul"
restart: always
celery:
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: celery-app
volumes:
- ../../../www:/www
- ../../../log:/log
- ../../../script/logrotate/uvicorn/celery/uvicorn-celery:/etc/logrotate.d/uvicorn-celery
command: bash -c "poetry config virtualenvs.create false && \
poetry install --no-root && \
celery -A config worker \
--loglevel=INFO \
--logfile=/log/uvicorn/celery/worker-%n%I.log "
environment:
TZ: "Asia/Seoul"
restart: always
depends_on:
- redis
- gunicorn-uvicorn-app
profiles:
- celery
celery-beat:
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: celerybeat-app
volumes:
- ../../../www:/www
- ../../../log:/log
- ../../../script/logrotate/uvicorn/celerybeat/uvicorn-celerybeat:/etc/logrotate.d/uvicorn-celerybeat
command: bash -c "poetry config virtualenvs.create false && \
poetry install --no-root && \
celery -A config beat \
--loglevel=INFO \
--scheduler django_celery_beat.schedulers:DatabaseScheduler \
--logfile=/log/uvicorn/celerybeat/celerybeat.log "
environment:
TZ: "Asia/Seoul"
restart: always
depends_on:
- celery
profiles:
- celery
flower:
image: mher/flower:master
logging:
driver: "${LOG_DRIVER}"
options:
max-file: "${LOG_OPT_MAXF}"
max-size: "${LOG_OPT_MAXS}"
container_name: flower
ports:
- "5555:5555"
environment:
- CELERY_BROKER_URL=${CELERY_BROKER_URL}
- FLOWER_BASIC_AUTH=${FLOWER_ID}:${FLOWER_PWD}
restart: always
depends_on:
- celery-beat
profiles:
- celery
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
redis-stats:
image: insready/redis-stat:latest
logging:
driver: "${LOG_DRIVER}"
options:
max-file: "${LOG_OPT_MAXF}"
max-size: "${LOG_OPT_MAXS}"
container_name: redis-stats
ports:
- 63790:63790
command: ["--verbose", "--server", "redis:6379"]
environment:
TZ: "Asia/Seoul"
depends_on:
- redis
restart: always
profiles:
- redis

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,253 @@
"""
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 = "config.asgi:application"
# ============================================================================
# 타임아웃 설정
# ============================================================================
# Worker 타임아웃
# 역할: Worker가 요청 처리 최대 허용 시간 (초)
# 동작: 타임아웃 초과 시 Worker 강제 종료 후 재시작
# 계산: FastAPI REST API 평균 응답 1-5초
# 파일 업로드 고려 (100MB / 10Mbps = 80초)
# Nginx 연동: proxy_read_timeout(60s)보다 짧거나 같게 설정
# 30초: 일반 API 응답 충분, 긴 작업은 백그라운드 처리 권장
timeout = 30
# 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 = 30
# ============================================================================
# 프로세스 리소스 관리 (메모리 누수 방지)
# ============================================================================
# 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,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,562 @@
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;
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,7 @@
# ip access drop
server {
listen 80;
server_name _;
return 444;
}

View File

@ -0,0 +1,62 @@
server {
listen 80;
server_name localhost www.localhost;
access_log /log/nginx/django_sample.com.access.log main;
error_log /log/nginx/django_sample.com.error.log warn;
# Django media
location /media {
gzip_static on;
expires max;
#alias /www/django_sample/django_sample/media;
alias /www/django_sample/media; # your Django project's media files - amend as required
#include /etc/nginx/mime.types;
}
location /static {
gzip_static on;
expires max;
#alias /www/django_sample/django_sample/static;
# normally static folder is named as /static
alias /www/django_sample/static; # your Django project's static files - amend as required
#include /etc/nginx/mime.types;
}
location / {
proxy_pass http://gunicorn-app:8000;
# proxy_redirect http:// https://;
}
# Allow Lets Encrypt Domain Validation Program
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/django_sample;
}
# 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,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,108 @@
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;
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;
resolver 8.8.8.8 8.8.4.4 valid=300s; # resolver $DNS-IP-1 $DNS-IP-2 valid=300s;
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; # 클라이언트로 응답 전송 타임아웃
# ===== 해시 테이블 =====
# 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,17 @@
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 "Upgrade";
proxy_set_header Connection keep-alive;
proxy_cache_bypass $http_upgrade;
proxy_buffering off;
proxy_redirect off;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
proxy_headers_hash_max_size 512;
proxy_headers_hash_bucket_size 64;

View File

@ -0,0 +1,774 @@
# Nginx HTTPS 설정 검토 보고서
## 요약
본 보고서는 `sample_nginx_https.conf` (템플릿)와 `https.conf` (code.devspoon.com의 운영 설정)을 비교하고, 현재 설정의 운영 준비 상태를 평가합니다.
**종합 평가**: `https.conf`는 샘플 템플릿보다 훨씬 더 운영 환경에 적합하며, 최신 보안 헤더와 우수한 설정을 갖추고 있습니다. 그러나 일부 개선 사항이 권장됩니다.
---
## 1. 설정 비교 분석
### 1.1 HTTP 서버 블록 (포트 80)
#### 샘플 설정의 문제점:
```nginx
server {
listen portnumber;
server_name domain www.domain;
rewrite ^ https://$host$request_uri permanent;
# if ($host !~* ^(domain\.com|www\.domain\.com)$) {
# return 444;
# }
}
```
**문제점:**
- 일반적인 플레이스홀더 사용 (`portnumber`, `domain`)
- `return 301` 대신 더 이상 권장되지 않는 `rewrite` 지시어 사용
- 호스트 검증이 주석 처리됨
- Let's Encrypt ACME 챌린지 location 누락
#### 운영 설정 (https.conf) - 올바름:
```nginx
server {
listen 80;
server_name code.devspoon.com www.code.devspoon.com;
# 리다이렉트 전 호스트 검증
if ($host !~* ^(code\.devspoon\.com|www\.code\.devspoon\.com)$) {
return 444;
}
# 리다이렉트 전 ACME 챌린지
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404;
}
# HTTPS로 리다이렉트
location / {
return 301 https://$server_name$request_uri;
}
}
```
**장점:**
`return 301` 사용 (rewrite보다 효율적)
✅ 호스트 검증 활성화
✅ 리다이렉트 전 ACME 챌린지 적절히 처리
✅ location 블록으로 더 나은 구조
`$host` 대신 `$server_name` 사용 (더 안전함)
---
### 1.2 HTTPS 서버 블록 (포트 443)
#### SSL/TLS 설정
**샘플 설정 - 구식:**
```nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
```
**운영 설정 - 최신 & 올바름:**
```nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off; # TLSv1.3에서는 OFF
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_prefer_server_ciphers off` 사용 - TLSv1.3에 **올바름** (클라이언트 선호가 더 나음)
- ✅ 일반적인 약칭 대신 명시적이고 최신 암호화 스위트 사용
- ✅ 모바일 장치 최적화를 위한 CHACHA20-POLY1305 포함
---
### 1.3 보안 헤더
**샘플 설정 - 누락:**
- 보안 헤더가 전혀 없음
**운영 설정 - 우수:**
```nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: ws:;" always;
```
**평가:**
**HSTS** (preload 포함) - 다운그레이드 공격 방지
**X-Frame-Options** - 클릭재킹 방지
**X-Content-Type-Options** - MIME 스니핑 방지
**X-XSS-Protection** - 레거시 XSS 보호
**Referrer-Policy** - 리퍼러 정보 제어
**CSP** - XSS 방지를 위한 콘텐츠 보안 정책
✅ 모든 헤더에 `always` 플래그 사용 - 오류 응답에도 헤더 보장
---
### 1.4 OCSP 스테이플링
**샘플 설정 - 누락**
**운영 설정 - 구현됨:**
```nginx
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/code.devspoon.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
```
**평가:**
✅ OCSP 스테이플링으로 SSL 핸드셰이크 성능 향상
✅ Cloudflare (1.1.1.1)와 Google (8.8.8.8) DNS 리졸버 사용
✅ OCSP 경고에 대한 유용한 주석 포함
---
### 1.5 프록시 설정
**샘플 설정 - 기본:**
```nginx
location / {
autoindex off;
proxy_pass http://appname:serviceport;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
```
**운영 설정 - 포괄적:**
```nginx
location / {
proxy_pass http://code-server:8443;
# WebSocket 지원
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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;
# 버퍼 설정
proxy_buffering off;
proxy_request_buffering off;
proxy_set_header Accept-Encoding gzip;
}
```
**평가:**
✅ WebSocket 지원 (code-server에 필수)
✅ 포괄적인 forwarded 헤더
✅ 적절한 타임아웃 설정
✅ 대화형 애플리케이션에 최적화된 버퍼 설정
⚠️ 샘플에는 WebSocket 지원이 완전히 없음
---
### 1.6 정적 파일 처리
**샘플 설정 - 존재:**
```nginx
location /media {
autoindex off;
gzip_static on;
expires max;
alias /www/webroot/media;
}
location /static {
autoindex off;
gzip_static on;
expires max;
alias /www/webroot/static;
}
```
**운영 설정 - 누락:**
- /media 또는 /static location 없음
**평가:**
⚠️ **누락** - 애플리케이션이 정적 파일을 제공하는 경우 `https.conf`에 추가해야 함
code-server의 경우, 내부적으로 정적 파일을 처리하므로 의도적일 수 있음
⚠️ **권장사항**: Django/Flask 백엔드를 추가하는 경우 정적 파일 location 포함
---
### 1.7 Let's Encrypt ACME 챌린지
**샘플 설정:**
```nginx
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/webroot;
}
```
**운영 설정:**
```nginx
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404; # 보안 추가
}
```
**평가:**
✅ 운영 설정에 `try_files $uri =404` 추가 - 디렉토리 순회 방지
✅ 전용 `/www/certbot` 디렉토리 사용
---
### 1.8 보안 블록 Location
**샘플 설정:**
```nginx
location ~ /\. {
deny all;
}
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
}
location ~* (composer\.json|...) {
deny all;
}
```
**운영 설정 - 향상됨:**
```nginx
location ~ /\. {
deny all;
access_log off; # 차단된 시도는 로그에 남기지 않음
log_not_found off;
}
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
access_log off;
log_not_found off;
}
location ~* (composer\.json|...) {
deny all;
access_log off;
log_not_found off;
}
```
**평가:**
✅ 운영 설정에서 차단된 요청에 대한 로깅 비활성화 - 로그 노이즈 감소
✅ 스캐너 시도를 로그에 남기지 않아 성능 향상
---
## 2. https.conf에 누락된 운영 준비 기능
### 2.1 속도 제한 - **누락**
**권장사항: 추가**
```nginx
# http 블록 또는 server 블록에 추가
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
# location 블록에
location / {
limit_req zone=general burst=20 nodelay;
limit_conn addr 10;
...
}
```
**영향:** DDoS 및 무차별 대입 공격으로부터 보호
---
### 2.2 클라이언트 본문 크기 제한 - **누락**
**권장사항: 추가**
```nginx
client_max_body_size 10M; # 애플리케이션 요구사항에 따라 조정
client_body_buffer_size 128k;
client_body_timeout 60s;
```
**영향:** 메모리 고갈 공격 방지
---
### 2.3 연결 제한 - **누락**
**권장사항: 추가**
```nginx
keepalive_timeout 65;
keepalive_requests 100;
send_timeout 60s;
```
**영향:** 더 나은 리소스 관리
---
### 2.4 Gzip 압축 - **누락**
**권장사항: 추가**
```nginx
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
gzip_disable "msie6";
```
**영향:** 대역폭 사용량을 크게 감소
---
### 2.5 오류 페이지 - **누락**
**권장사항: 추가**
```nginx
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;
}
```
**영향:** 더 나은 사용자 경험 및 정보 노출 방지
---
### 2.6 요청 메서드 제한 - **누락**
**권장사항: 추가**
```nginx
location / {
limit_except GET POST HEAD OPTIONS {
deny all;
}
...
}
```
**영향:** HTTP 동사 변조 공격 방지
---
### 2.7 버전 정보 숨김 - **누락**
**권장사항: 추가** (http 블록에)
```nginx
server_tokens off;
more_clear_headers 'Server';
more_clear_headers 'X-Powered-By';
```
**영향:** 정보 노출 감소
---
## 3. SSL/TLS 모범 사례 검증
### 현재 설정 평가:
| 설정 | 현재 값 | 상태 | 권장사항 |
|------|---------|------|----------|
| ssl_protocols | TLSv1.2 TLSv1.3 | ✅ 올바름 | 현재 유지 |
| ssl_prefer_server_ciphers | off | ✅ 올바름 | 유지 (TLSv1.3 모범 사례) |
| ssl_session_cache | shared:SSL:20m | ✅ 좋음 | 트래픽이 많으면 50m 고려 |
| ssl_session_timeout | 10m | ✅ 좋음 | 현재 유지 |
| ssl_session_tickets | off | ✅ 올바름 | 유지 (더 나은 보안) |
| ssl_stapling | on | ✅ 올바름 | 현재 유지 |
| ssl_dhparam | 4096-bit | ✅ 우수 | 현재 유지 |
| HSTS max-age | 31536000 | ✅ 올바름 | 유지 (1년) |
---
## 4. 성능 최적화 격차
### 4.1 캐싱 헤더 누락
**권장사항: 추가**
```nginx
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
```
### 4.2 열린 파일 캐시 누락
**권장사항: 추가**
```nginx
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
```
---
## 5. 로깅 개선
### 현재 설정:
```nginx
access_log /log/nginx/code.devspoon.com.nginx.log main;
error_log /log/nginx/code.devspoon.com.nginx_error.log warn;
```
**권장사항:**
```nginx
# I/O 감소를 위한 버퍼 추가
access_log /log/nginx/code.devspoon.com.nginx.log main buffer=32k flush=5s;
error_log /log/nginx/code.devspoon.com.nginx_error.log warn;
# 더 많은 세부 정보를 포함한 로그 형식 추가
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$upstream_addr $upstream_status';
```
---
## 6. 권장 최종 설정 구조
```nginx
# HTTP 블록 (포트 80)
server {
listen 80;
server_name code.devspoon.com www.code.devspoon.com;
# 호스트 검증
if ($host !~* ^(code\.devspoon\.com|www\.code\.devspoon\.com)$) {
return 444;
}
# ACME 챌린지
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404;
}
# HTTPS로 리다이렉트
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS 블록 (포트 443)
server {
listen 443 ssl;
http2 on;
server_name code.devspoon.com www.code.devspoon.com;
# SSL 인증서
ssl_certificate /etc/letsencrypt/live/code.devspoon.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/code.devspoon.com/privkey.pem;
ssl_dhparam /etc/ssl/certs/code.devspoon.com/dhparam.pem;
# 최신 SSL 설정
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 10m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
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;
# OCSP 스테이플링
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/code.devspoon.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# 보안 헤더
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss: ws:;" always;
# 신규: 보안 설정
server_tokens off;
client_max_body_size 10M;
client_body_buffer_size 128k;
client_body_timeout 60s;
# 신규: 성능 설정
keepalive_timeout 65;
keepalive_requests 100;
send_timeout 60s;
# 신규: Gzip 압축
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss;
# 신규: 파일 캐시
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 버퍼링을 사용한 로깅
access_log /log/nginx/code.devspoon.com.nginx.log main buffer=32k flush=5s;
error_log /log/nginx/code.devspoon.com.nginx_error.log warn;
# 호스트 검증
if ($host !~* ^(code\.devspoon\.com|www\.code\.devspoon\.com)$) {
return 444;
}
# 나쁜 봇 차단
if ($bad_bot) {
return 403;
}
# 메인 애플리케이션
location / {
# 신규: 속도 제한
limit_req zone=general burst=20 nodelay;
limit_conn addr 10;
# 신규: 메서드 제한
limit_except GET POST HEAD OPTIONS {
deny all;
}
proxy_pass http://code-server:8443;
# WebSocket 지원
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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;
# 버퍼 설정
proxy_buffering off;
proxy_request_buffering off;
proxy_set_header Accept-Encoding gzip;
}
# ACME 챌린지
location ^~ /.well-known/acme-challenge/ {
allow all;
root /www/certbot;
try_files $uri =404;
}
# 신규: 정적 파일 캐싱 (필요시)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# 닷 파일 차단
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 민감한 파일 차단
location ~* \.(log|binary|pem|enc|crt|conf|cnf|sql|sh|key|yml|lock)$ {
deny all;
access_log off;
log_not_found off;
}
# 민감한 설정 파일 차단
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;
}
# 파비콘 및 robots
location = /favicon.ico {
log_not_found off;
access_log off;
}
location = /robots.txt {
log_not_found off;
access_log off;
allow all;
}
}
```
---
## 7. 우선순위 실행 항목
### 긴급 (즉시 구현)
1. ✅ **이미 존재**: 보안 헤더 (HSTS, CSP 등)
2. ✅ **이미 존재**: OCSP 스테이플링
3. ⚠️ **추가**: 속도 제한 설정
4. ⚠️ **추가**: `server_tokens off`
5. ⚠️ **추가**: `client_max_body_size` 제한
### 높은 우선순위 (권장)
6. ⚠️ **추가**: Gzip 압축
7. ⚠️ **추가**: 열린 파일 캐시
8. ⚠️ **추가**: 오류 페이지 처리
9. ⚠️ **추가**: HTTP 메서드 제한
10. ⚠️ **추가**: 로깅 버퍼 설정
### 중간 우선순위 (선택 사항)
11. ⚠️ **추가**: 정적 파일 캐싱 헤더
12. ⚠️ **고려**: SSL 세션 캐시 크기 증가
13. ⚠️ **고려**: 향상된 로깅 형식
14. ⚠️ **검토**: 특정 애플리케이션 요구사항에 맞는 CSP 정책
---
## 8. 설정 의존성
### nginx.conf (http 블록)에 필요:
```nginx
# 서버 블록 전에 정의되어야 함
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
# 나쁜 봇 탐지 맵
map $http_user_agent $bad_bot {
default 0;
~*malicious 1;
~*scrapy 1;
~*bot 1;
~*crawler 1;
}
# WebSocket 업그레이드 맵
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# 로그 형식
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
```
---
## 9. SSL/TLS 테스트 권장사항
변경 사항 구현 후 다음으로 테스트:
1. **SSL Labs**: https://www.ssllabs.com/ssltest/
- 목표 등급: A+
2. **Mozilla Observatory**: https://observatory.mozilla.org/
- 목표 점수: A+ 또는 100+
3. **Security Headers**: https://securityheaders.com/
- 목표 등급: A+
4. **OpenSSL CLI**:
```bash
openssl s_client -connect code.devspoon.com:443 -tls1_2
openssl s_client -connect code.devspoon.com:443 -tls1_3
```
---
## 10. 요약
### https.conf에서 이미 우수한 점:
✅ 최신 SSL/TLS 설정
✅ 포괄적인 보안 헤더
✅ OCSP 스테이플링
✅ WebSocket 지원
✅ 적절한 ACME 챌린지 처리
✅ 향상된 프록시 설정
✅ 로깅 최적화가 적용된 보안 블록 location
### 추가해야 할 사항:
⚠️ 속도 제한
⚠️ 클라이언트 본문 크기 제한
⚠️ Gzip 압축
⚠️ 열린 파일 캐시
⚠️ 서버 토큰 숨김
⚠️ 오류 페이지 처리
⚠️ HTTP 메서드 제한
### 결론:
**https.conf는 sample_nginx_https.conf보다 훨씬 우수하며** 최신 모범 사례를 따릅니다. 샘플 파일은 구식이고 중요한 보안 기능이 누락되어 있습니다. 위의 권장 추가 사항을 적용하면 `https.conf`는 운영 등급의 엔터프라이즈 준비 nginx 설정이 될 것입니다.
---
## 11. 다음 단계
1. 긴급 우선순위 항목 검토 및 구현
2. 설정 테스트: `nginx -t`
3. 변경 사항 적용 전 현재 설정 백업
4. 변경 사항을 점진적으로 적용
5. 문제가 있는지 로그 모니터링
6. A+ 등급 확인을 위한 SSL/TLS 테스트 실행
7. 속도 제한 및 성능 메트릭 모니터링 설정
---
**보고서 생성**: sample_nginx_https.conf vs https.conf 비교 기반
**기준선**: https.conf (code.devspoon.com의 운영 설정)
**평가**: 권장 개선 사항을 포함한 운영 준비 완료

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,73 @@
FROM ubuntu:latest
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends apt-utils
ENV TZ=Asia/Seoul
RUN apt-get install -yq tzdata
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# curl/wget/git/gnupg2/lsb-release/lz4/zstd
RUN apt-get install -y curl wget git tar gnupg2 lsb-release lz4 zstd
# vim
RUN apt-get install -y vim
# Python
RUN apt-get install -y build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev
RUN cd /usr/src ; wget https://www.python.org/ftp/python/3.14.0/Python-3.14.0.tar.xz ; tar -xf Python-3.14.0.tar.xz ; cd Python-3.14.0 ; ./configure ; make altinstall
RUN rm -rf /usr/src/Python-3.14.0 /usr/src/Python-3.14.0.tar.xz
# replace python version to have 3.14 as default
RUN rm -f /usr/bin/python
RUN rm -f /usr/bin/python3
RUN ln -s /usr/local/bin/python3.14 /usr/bin/python
RUN ln -s /usr/local/bin/python3.14 /usr/bin/python3
RUN ln -s /usr/local/bin/python3.14 /usr/local/bin/python
RUN ln -s /usr/local/bin/python3.14 /usr/local/bin/python3
ENV PYTHONUNBUFFERED=1
# create links to pip3.14
RUN ln -s /usr/local/bin/pip3.14 /usr/bin/pip
RUN ln -s /usr/local/bin/pip3.14 /usr/bin/pip3
RUN ln -s /usr/local/bin/pip3.14 /usr/local/bin/pip
RUN ln -s /usr/local/bin/pip3.14 /usr/local/bin/pip3
# update apt-get
RUN apt-get update && apt-get -y upgrade
RUN pip install --upgrade pip
RUN pip3 install --upgrade pip
RUN apt-get install -y python3-dev libmysqlclient-dev pkg-config
RUN pip3 install wheel
# SQLAlchemy
RUN pip3 install sqlalchemy alembic pydantic
# postgresql 동기 / 비동기
RUN pip3 install psycopg2-binary asyncpg
# mysql 동기 / 비동기
RUN pip3 install mysqlclient asyncmy
# mysql 백업
RUN curl -O https://repo.percona.com/apt/percona-release_latest.generic_all.deb
RUN apt-get install -y ./percona-release_latest.generic_all.deb
RUN rm -f percona-release_latest.generic_all.deb
RUN apt-get update
RUN percona-release enable pxb-84-lts
RUN apt-get install -y percona-xtrabackup-84
# postgresql 백업
RUN apt-get -y install pgbackrest
# gunicorn & uvicorn
RUN pip3 install gunicorn uvicorn[standard]
# pip3 package
RUN pip3 install fastapi uv
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN update-ca-certificates

51
docker/nginx/Dockerfile Normal file
View File

@ -0,0 +1,51 @@
FROM nginx:latest
# ========================================
# 1. APT 업데이트
# ========================================
RUN apt-get update
# ========================================
# 2. APT 기본 도구 설치 (경고 제거용)
# ========================================
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends apt-utils
# ========================================
# 3. 타임존 설정 (Asia/Seoul)
# ========================================
ENV TZ=Asia/Seoul
RUN apt-get install -yq tzdata
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# ========================================
# 4. 필요한 패키지 설치
# ========================================
RUN apt-get install -y sendmail wget gnupg
# ========================================
# 7. Cron 및 Certbot 설치 (SSL 인증서 자동 갱신)
# ========================================
RUN apt-get install -y cron certbot python3-certbot-nginx
# ========================================
# 8. CA 인증서 설치 및 업데이트
# ========================================
RUN apt-get update && apt-get install -y ca-certificates
RUN update-ca-certificates
RUN chmod 644 /etc/ssl/certs/ca-certificates.crt
# ========================================
# 9. APT 캐시 정리
# ========================================
RUN apt-get autoremove -y && apt-get clean
RUN rm -rf /var/lib/apt/lists/*
# ========================================
# 10. Certbot 자동 갱신 Cron 작업 등록
# ========================================
RUN crontab -l | { 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 -
# ========================================
# 11. Nginx 시작 스크립트에 Cron 추가
# ========================================
RUN sed -i'' -r -e "/set/i\cron" docker-entrypoint.sh

0
log/nginx/.gitkeep Normal file
View File

View File

View 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 Normal 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,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
}

21
script/nginx/nginx Normal file
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
}