Home Nginx Reverse Proxy Project
Post
Cancel

Nginx Reverse Proxy Project

Project: Nginx Reverse Proxy

목차


프로젝트 목표

Nginx가 외부 요청을 받아 내부 애플리케이션 서버로 전달하는 리버스 프록시 구조를 실습하고, 다중 계층 웹 애플리케이션의 기본 아키텍처를 이해한다.

1
2
3
4
5
6
아키텍처:
사용자 (Internet)
    ↓ (HTTP:80)
Nginx (Reverse Proxy)
    ↓ (Internal Network)
App Server (localhost:8080)

왜 리버스 프록시가 필요한가?

1
2
3
4
5
6
7
8
9
10
직접 접속 (위험):
- 사용자가 app 서버의 실제 주소:포트를 알게 됨
- 보안 취약: 서버 정보 노출
- 유지보수 어려움: 서버 변경 시 모든 클라이언트 영향

리버스 프록시 (안전):
- 사용자는 nginx만 봄
- 앱 서버는 숨겨짐
- 로드 밸런싱, TLS, 압축 등 추가 기능 가능
- 서버 변경 시 nginx 설정만 수정

App Server

애플리케이션 서버 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 간단한 Python Flask 앱 (app.py)
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello from App Server!'

@app.route('/api/status')
def status():
    return {'status': 'ok', 'port': 8080}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

Docker로 실행

1
2
3
4
5
6
7
8
9
10
11
# Dockerfile
FROM python:3.9-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install flask

COPY app.py .

EXPOSE 8080
CMD ["python", "app.py"]

컨테이너 실행

1
2
3
4
5
# 빌드
docker build -t app-server .

# 실행 (localhost:8080에서만 접근 가능)
docker run --name app-server -p 127.0.0.1:8080:8080 app-server

Nginx Proxy

Nginx 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# nginx.conf
upstream app_backend {
    server app:8080;  # Docker network 내 hostname
}

server {
    listen 80;
    server_name localhost;

    client_max_body_size 10M;

    # 정적 파일 (있다면)
    location /static/ {
        alias /var/www/static/;
        expires 30d;
    }

    # 모든 요청을 app 서버로 전달
    location / {
        proxy_pass http://app_backend;
        
        # 클라이언트 정보 전달
        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_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # 헬스 체크 엔드포인트
    location /health {
        access_log off;
        return 200 'ok';
        add_header Content-Type text/plain;
    }
}

Dockerfile (Nginx)

1
2
3
4
5
6
7
FROM nginx:alpine

COPY nginx.conf /etc/nginx/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Port Mapping

“사용자는 왜 app 서버 포트에 직접 접속하지 않는가?”

직접 접속은 서버 정보 노출, 보안 취약, 확장성 제한 등의 문제를 야기한다. 리버스 프록시를 통해 추상화한다.

1
2
3
4
5
6
7
8
9
10
11
직접 접속 (❌ 위험):
브라우저 → app.example.com:8080
- 포트 8080 노출 → 서버 기술 스택 드러남
- 직접 포트 변경 불가능
- 다중 앱 서버 로드 밸런싱 불가능

리버스 프록시 (✓ 안전):
브라우저 → example.com:80 → 내부 app:8080
- 외부: 포트 80만 노출 (표준)
- 내부: 앱 서버 주소/포트 숨김
- 여러 앱 서버 관리 가능

Docker Compose로 네트워크 구성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
version: '3.8'

services:
  nginx:
    build: ./nginx
    ports:
      - "80:80"      # 외부에 80 포트 노출
    depends_on:
      - app
    networks:
      - web-net

  app:
    build: ./app
    # ports: 주석 처리 (외부 노출 안 함)
    networks:
      - web-net
    environment:
      - FLASK_ENV=production

networks:
  web-net:
    driver: bridge

포트 매핑 설명

1
2
3
4
5
6
7
8
9
10
11
호스트 포트: 실제 시스템 (또는 Docker 호스트)의 포트
컨테이너 포트: 컨테이너 내부 포트

-p 80:80
├─ 호스트 포트: 80
└─ 컨테이너 포트: 80

-p 127.0.0.1:8080:8080
├─ 127.0.0.1: 루프백 (localhost만 접근)
├─ 호스트 포트: 8080
└─ 컨테이너 포트: 8080

Request Flow

요청이 흐르는 과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1. 브라우저 요청 (사용자)
   GET http://example.com/ (포트 80)
   
2. Nginx 수신
   포트 80 → listen 80
   → location / 매칭
   
3. Upstream 결정
   upstream app_backend: app:8080
   
4. App 서버로 전달
   proxy_pass http://app:8080/
   + 헤더: X-Real-IP, X-Forwarded-For
   
5. App 서버 처리
   Flask: request.remote_addr
   (X-Real-IP 헤더로부터 클라이언트 IP 가져옴)
   
6. 응답 반환
   App → Nginx → 브라우저
   
7. 로그 기록
   Nginx access log: 요청/응답 기록

헤더의 중요성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
클라이언트: 1.2.3.4

Proxy_pass 없이:
app.remote_addr = "127.0.0.1"
(localhost에서 온 것처럼 보임)

X-Real-IP 헤더 포함:
X-Real-IP: 1.2.3.4
app.request.headers.get('X-Real-IP') = "1.2.3.4"
(실제 클라이언트 IP 알 수 있음)

X-Forwarded-For:
X-Forwarded-For: 1.2.3.4, 127.0.0.1
(프록시 체인 기록)

“nginx와 app은 어떤 네트워크로 연결되는가?”

Docker Compose로 실행하면 같은 custom bridge network (web-net)로 자동 연결되어, hostname으로 통신한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Docker Network 종류:

1. Bridge Network (기본)
   - 각 컨테이너에 IP 할당
   - 컨테이너끼리 hostname으로 통신
   - 예: app:8080 (DNS 자동 해석)

2. Host Network
   - 호스트 네트워크 공유
   - 성능 좋음, 포트 격리 없음

3. None Network
   - 네트워크 없음 (고립)

Request Flow 검증

“access log로 요청 흐름을 어떻게 확인하는가?”

Nginx access log를 통해 누가, 언제, 무엇을 요청했는지 추적할 수 있다.

1
2
3
4
5
6
7
# access_log 설정 (nginx.conf)
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/access.log main;

로그 예시

1
2
3
4
5
6
7
8
9
10
192.168.1.100 - - [23/May/2026 10:15:30 +0000] "GET / HTTP/1.1" 200 25 "-" "Mozilla/5.0" "1.2.3.4"

해석:
192.168.1.100: Nginx를 접속한 주소 (호스트 또는 프록시)
[23/May/2026 10:15:30]: 접속 시각
GET / HTTP/1.1: 요청 메서드, 경로, 프로토콜
200: 응답 상태 코드 (성공)
25: 응답 바이트 크기
Mozilla/5.0: User-Agent
1.2.3.4: X-Forwarded-For (실제 클라이언트 IP)

로그 보기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 실시간 로그 보기
docker logs -f nginx_container

# 액세스 로그 모니터링
docker exec nginx_container tail -f /var/log/nginx/access.log

# 에러 로그
docker exec nginx_container tail -f /var/log/nginx/error.log

# 특정 IP 요청만 보기
docker exec nginx_container grep "1.2.3.4" /var/log/nginx/access.log

# 상태 코드 통계
docker exec nginx_container awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c

실습 과정

1단계: 파일 준비

1
2
3
4
5
6
7
8
9
10
11
12
13
mkdir -p nginx-reverse-proxy
cd nginx-reverse-proxy

# 디렉토리 구조
.
├── app/
│   ├── Dockerfile
│   ├── app.py
│   └── requirements.txt
├── nginx/
│   ├── Dockerfile
│   └── default.conf
└── docker-compose.yml

2단계: 빌드 및 실행

1
2
3
4
5
6
7
8
9
10
11
12
docker-compose up -d

# 확인
docker ps
# 두 컨테이너 실행: nginx, app-server

# 상태 확인
curl http://localhost/
# 응답: Hello from App Server!

curl http://localhost/api/status
# 응답: {"status": "ok", "port": 8080}

3단계: 로그 확인

1
2
# Nginx 액세스 로그
docker exec nginx tail -f /var/log/nginx/access.log

4단계: 문제 해결

1
2
3
4
5
6
7
8
9
10
11
# 컨테이너 간 통신 확인
docker exec nginx ping app
# app:8080 응답 확인

# Nginx 설정 검증
docker exec nginx nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# 포트 확인
docker ps -a
netstat -tulpn | grep 80

확장 사항

로드 밸런싱 (다중 app 서버)

1
2
3
4
5
6
7
8
9
10
upstream app_backend {
    server app1:8080;
    server app2:8080;
    server app3:8080;
    
    # 로드 밸런싱 방식
    # 라운드 로빈 (기본)
    # least_conn: 연결 수 최소
    # ip_hash: 클라이언트 IP 기반
}

TLS/HTTPS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
    listen 443 ssl;
    
    ssl_certificate /etc/nginx/cert.pem;
    ssl_certificate_key /etc/nginx/key.pem;
    
    location / {
        proxy_pass http://app_backend;
    }
}

server {
    listen 80;
    return 301 https://$host$request_uri;  # HTTP → HTTPS 리다이렉트
}

정리

개념설명
리버스 프록시클라이언트와 서버 사이 중개
Upstream백엔드 서버 그룹 정의
Proxy Pass요청 전달 대상
Port Mapping호스트와 컨테이너 포트 연결
Request Flow요청 흐름 추적
Access Log요청/응답 기록
This post is licensed under CC BY 4.0 by the author.