Nginx 핵심 정리

Nginx는 고성능 웹 서버(Web Server)이자 Reverse Proxy, Load Balancer, HTTP Cache, SSL/TLS 종료 지점 등으로 사용할 수 있는 서버 소프트웨어다.

처음에는 정적 파일을 빠르게 제공하기 위한 웹 서버로 개발됐지만, 실제 서비스에서는 애플리케이션 서버 앞에 배치해서 외부 요청을 받아 내부 서버로 전달하는 Reverse Proxy 용도로 많이 사용한다.

일반적인 웹 서비스 구조를 보면 다음과 같다.

Client -> Nginx -> Static Files / Web Server / REST API Server / WebSocket Server

Nginx가 모든 비즈니스 로직을 처리하는 것은 아니다.

Nginx는 네트워크 앞단에서 요청을 받아 적절한 서버로 전달하고, 정적 리소스를 제공하고, 연결을 관리하는 역할에 가깝다.


Web Server

웹 서버의 가장 기본적인 역할은 HTTP 요청을 받고 파일을 반환하는 것이다.

예를 들어 브라우저에서

GET /index.html

을 요청하면 Nginx가 해당 파일을 찾아 응답할 수 있다.

Browser -> GET /index.html -> Nginx -> index.html -> HTTP Response

HTML뿐만 아니라

CSS / JavaScript / Image / Font / Video / JSON

같은 정적 파일도 제공할 수 있다.


정적 콘텐츠와 동적 콘텐츠

웹 서버를 이해할 때 정적 콘텐츠와 동적 콘텐츠를 구분하면 Nginx의 역할이 명확해진다.

정적 콘텐츠

이미 만들어져 있는 파일이다.

/index.html /style.css /app.js /logo.png

Nginx가 직접 읽어서 반환할 수 있다.

동적 콘텐츠

요청에 따라 서버에서 데이터를 처리해서 결과를 생성한다.

예를 들어

GET /api/users/100

을 요청하면 데이터베이스를 조회한 뒤 사용자 정보를 반환해야 할 수 있다.

이런 비즈니스 로직은 일반적으로 Nginx가 처리하지 않는다.

Client -> Nginx -> API Server -> Database

Reverse Proxy

Nginx를 실무에서 사용하는 가장 대표적인 이유다.

Reverse Proxy는 클라이언트의 요청을 대신 받아 내부 서버로 전달하는 구조다.

Client -> HTTP Request -> Nginx -> Proxy -> Application Server

예를 들어 실제 서버 구조가

Nginx -> Node.js :3000

이라고 하자.

외부 사용자는

https://example.com/api/users

로 요청한다.

Nginx는 이 요청을 내부적으로

http://localhost:3000/api/users

로 전달할 수 있다.

사용자는 Node.js 서버의 3000 포트를 직접 알 필요가 없다.


Reverse Proxy를 사용하는 이유

단순히 요청을 전달하는 것 이상의 장점이 있다.

Client -> Nginx -> API Server 1 / API Server 2 / API Server 3

외부에서는 Nginx 하나만 바라보고 내부 서버는 자유롭게 구성할 수 있다.

그래서 다음과 같은 기능을 Nginx에서 처리할 수 있다.

SSL/TLS / Load Balancing / Access Control / Rate Limiting / Compression / Caching / Static File Serving / Request Routing

애플리케이션 서버가 HTTP 연결 관리와 외부 노출을 전부 담당하지 않아도 된다.


Reverse Proxy 설정

간단한 예시는 다음과 같다.

server {
    listen 80;
    server_name example.com;

    location /api/ {
        proxy_pass http://localhost:3000;
    }
}

구조는 다음과 같다.

Client -> /api/* -> Nginx :80 -> proxy_pass -> Node.js :3000

Nginx가 /api/ 요청을 Node.js 서버로 전달한다.


여러 서버로 분리하기

실제 서비스에서는 요청 종류에 따라 다른 서버로 전달할 수도 있다.

Client -> Nginx -> /api/ -> API Server / /ws/ -> WebSocket Server / / -> Static Web

예를 들어

location /api/ {
    proxy_pass http://api-server:3000;
}

location /ws/ {
    proxy_pass http://game-server:8080;
}

location / {
    root /var/www/html;
}

같은 구조를 만들 수 있다.

Nginx가 URL을 기준으로 요청을 분배하는 라우터 역할도 하는 것이다.


Load Balancing

서버가 하나라면

Nginx -> API Server

지만 트래픽이 증가하면 여러 서버를 사용할 수 있다.

Client -> Nginx -> Server 1 / Server 2 / Server 3

Nginx는 여러 서버에 요청을 분산할 수 있다.

upstream api_servers {
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;
}

server {
    location /api/ {
        proxy_pass http://api_servers;
    }
}

Load Balancing 방식

대표적으로 Round Robin 방식이 있다.

Request 1 -> Server 1 -> Request 2 -> Server 2 -> Request 3 -> Server 3 -> Request 4 -> Server 1

그 외에도 환경에 따라

Least Connections / IP Hash / Hash

등의 방식을 사용할 수 있다.

단순한 Round Robin이 항상 최선인 것은 아니다.

세션 상태가 특정 서버에 묶여 있다면 요청을 다른 서버로 보내는 순간 문제가 발생할 수 있다.

그래서 서버를 여러 대 사용하는 구조에서는 애플리케이션의 상태 관리 방식도 함께 설계해야 한다.


Stateless 서버와 Nginx

Load Balancing을 제대로 활용하려면 API 서버를 Stateless하게 만드는 것이 유리하다.

예를 들어 로그인 세션을 특정 서버의 메모리에만 저장하면

Client -> Nginx -> Server 1 / Server 2

에서 첫 번째 요청이 Server 1로 가고 두 번째 요청이 Server 2로 가면 세션을 찾지 못할 수 있다.

그래서 세션을

Redis / Database

같은 외부 저장소에 두거나 JWT 같은 방식으로 상태 의존성을 줄일 수 있다.

Nginx -> API 1 / API 2 / API 3 -> Redis

Nginx 자체가 세션 문제를 해결해주는 것은 아니다.


SSL/TLS Termination

Nginx는 HTTPS를 처리하는 앞단으로 사용할 수도 있다.

Client -> HTTPS -> Nginx -> HTTP -> Application Server

이 구조에서 TLS 암호화와 복호화는 Nginx가 담당한다.

이를 TLS Termination이라고 한다.

애플리케이션 서버마다 HTTPS 인증서를 관리하는 것보다 앞단의 Nginx에서 처리하면 관리가 단순해질 수 있다.

실제 환경에서는 내부 통신도 HTTPS로 구성하는 경우가 있으므로 무조건 HTTP로 전달하는 것은 아니다.


HTTP와 HTTPS Redirect

HTTP 요청을 HTTPS로 강제할 수도 있다.

server {
    listen 80;
    server_name example.com;

    return 301 https://example.com$request_uri;
}

그러면

http://example.com -> 301 Redirect -> https://example.com

구조가 된다.


정적 파일 제공

Nginx는 정적 파일 서버로도 사용할 수 있다.

server {
    listen 80;

    root /var/www/html;

    location / {
        try_files $uri $uri/ =404;
    }
}

예를 들어

/var/www/html/ -> index.html / css/ / js/ / images/

가 있다면 Nginx가 직접 파일을 제공한다.

React나 Vue 같은 SPA를 빌드한 결과물을 Nginx에서 서비스하는 구성도 흔하다.

React Source -> npm run build -> dist/ -> Nginx -> Browser

SPA와 Nginx

React 같은 SPA에서는 모든 URL이 실제 파일로 존재하지 않을 수 있다.

예를 들어

/ -> /users -> /settings -> /inventory

가 모두 React Router에서 처리될 수 있다.

이때 Nginx가 /users라는 실제 파일을 찾으면 404가 발생한다.

그래서 fallback을 설정할 수 있다.

location / {
    try_files $uri $uri/ /index.html;
}

그러면 존재하는 파일은 그대로 반환하고, 존재하지 않는 경로는 index.html로 전달한다.

/users -> Nginx -> 실제 파일 있음 -> 파일 반환 / 실제 파일 없음 -> index.html

이후 라우팅은 브라우저의 SPA Router가 처리한다.


WebSocket Proxy

Nginx는 WebSocket 연결도 Proxy할 수 있다.

일반 HTTP와 WebSocket은 연결 방식이 다르기 때문에 필요한 Header를 전달해야 한다.

location /ws/ {
    proxy_pass http://localhost:8080;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

구조는

Client -> WebSocket -> Nginx -> WebSocket Proxy -> WebSocket Server

가 된다.

게임 서버에서도

Client -> Nginx -> WebSocket / Gateway -> Game Server

같은 구조를 만들 수 있다.

다만 게임의 실시간 통신 프로토콜이 TCP/UDP 기반이라면 Nginx의 HTTP Reverse Proxy만으로 처리할 수 있는 것은 아니다. Nginx의 HTTP 기능과 Stream 기능을 구분해서 봐야 한다.


TCP / UDP Proxy

Nginx는 HTTP 서버만 제공하는 것이 아니다.

stream 기능을 사용하면 TCP/UDP 기반 트래픽을 Proxy할 수 있다.

Client -> TCP -> Nginx -> Game Server

예를 들어 TCP 기반 서버 앞에 Nginx를 배치할 수 있다.

다만 게임 서버의 실시간 네트워크 구조에서는 Nginx만 사용하는 것이 정답은 아니다.

프로토콜과 서버 구조에 따라

Nginx / HAProxy / L4 Load Balancer / Cloud Load Balancer / Dedicated Gateway

등을 선택할 수 있다.


Request Routing

Nginx는 요청의 경로, Host, Header 등을 기준으로 적절한 서버를 선택할 수 있다.

예를 들어

api.example.com -> API Server
game.example.com -> Game Server
www.example.com -> Web Server

처럼 도메인별로 분리할 수 있다.

또는

/api -> API
/admin -> Admin Server
/static -> Static Files

처럼 URL 기준으로 분리할 수도 있다.


Header 처리

Reverse Proxy에서는 Client의 HTTP Header를 내부 서버로 전달하거나 수정할 수 있다.

대표적으로 실제 Client IP를 전달할 때 사용하는 Header가 있다.

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;

구조는

Client -> IP = 123.123.123.123 -> Nginx -> X-Real-IP = 123.123.123.123 -> API Server

가 된다.

Reverse Proxy를 사용하면 애플리케이션 서버 입장에서는 직접 Client와 통신하는 것이 아니기 때문에 Proxy 환경에 맞게 IP 정보를 전달해야 한다.


Access Log

Nginx는 요청 정보를 로그로 남길 수 있다.

예를 들어

IP / Method / URL / Status Code / Response Size / Request Time / User Agent

등을 기록할 수 있다.

로그를 보면

GET /api/users 200 / POST /api/login 401 / GET /static/app.js 200

같은 요청 흐름을 확인할 수 있다.

운영 환경에서는 장애 분석과 트래픽 분석에 중요하다.


Error Handling

Nginx에서 HTTP 오류 페이지를 직접 처리할 수도 있다.

404 / 403 / 500 / 502 / 503 / 504

등의 상태 코드를 기준으로 별도의 응답을 구성할 수 있다.

특히 Reverse Proxy 환경에서는 502 Bad Gateway504 Gateway Timeout이 자주 등장한다.

예를 들어

Client -> Nginx -> API Server -> 연결 실패

처럼 API 서버가 죽어 있거나 연결할 수 없다면 Nginx에서 502가 발생할 수 있다.

따라서 Nginx 오류라고 해서 반드시 Nginx 자체가 문제라는 뜻은 아니다.


Timeout

Reverse Proxy에서는 Timeout 설정이 중요하다.

예를 들어 API 서버가 너무 오래 응답하지 않으면 Nginx가 계속 연결을 유지할 수 있다.

대표적인 설정으로

proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;

등이 있다.

다만 무조건 짧게 설정하는 것도 문제가 된다.

파일 업로드나 긴 작업을 수행하는 API라면 실제 처리 시간에 맞게 설계해야 한다.


Buffering

Nginx는 Proxy 응답을 Buffering할 수 있다.

Application Server -> Nginx -> Buffer -> Client

이를 통해 내부 서버와 Client의 전송 속도 차이를 어느 정도 흡수할 수 있다.

다만 Streaming이나 SSE 같은 특수한 통신에서는 Buffering 설정이 서비스 특성에 영향을 줄 수 있다.


Compression

Nginx는 HTTP 응답을 압축해서 전송할 수 있다.

대표적으로 gzip을 사용할 수 있다.

Server -> 큰 응답 -> Nginx -> gzip -> Client

응답 데이터 크기를 줄여 네트워크 전송량을 줄일 수 있다.

다만 압축에는 CPU 비용이 발생하기 때문에 무조건 높은 압축률을 사용하는 것이 좋은 것은 아니다.


Cache

Nginx는 Proxy Cache를 사용해서 동일한 요청의 결과를 저장할 수도 있다.

첫 요청은

Client -> Nginx -> API Server -> Response -> Cache 저장

다음 요청은

Client -> Nginx -> Cache

로 처리할 수 있다.

Cache Hit가 발생하면 애플리케이션 서버까지 요청이 전달되지 않는다.

다만 어떤 데이터를 캐싱해도 안전한지는 애플리케이션의 데이터 특성을 봐야 한다.

사용자별 데이터나 실시간 데이터에 무작정 Cache를 적용하면 오래된 데이터나 잘못된 데이터가 반환될 수 있다.


Rate Limiting

Nginx는 요청 빈도를 제한할 수도 있다.

Client -> Nginx -> 정상 요청 -> API / 과도한 요청 -> 제한

로그인 API나 특정 Public API처럼 요청을 과도하게 보내면 문제가 되는 Endpoint에 사용할 수 있다.

다만 Rate Limiting만으로 보안 문제가 모두 해결되는 것은 아니다.

Application Level의 인증과 권한 검증은 별도로 필요하다.


Nginx와 WAS

Nginx를 이해할 때 WAS와 혼동하면 안 된다.

Nginx -> Web Server / Reverse Proxy
WAS -> Application 실행 환경

예를 들어 Java Spring 서버라면

Client -> Nginx -> Spring Boot

구조가 가능하다.

Node.js라면

Client -> Nginx -> Node.js

가 된다.

Nginx가 Spring이나 Node.js의 비즈니스 로직을 대신 실행하는 것이 아니다.


Nginx와 Application Server

각자의 역할을 분리하면 이해하기 쉽다.

Nginx -> HTTP 연결 / Reverse Proxy / Static File / TLS / Load Balancing / Cache / Rate Limiting
Application Server -> 인증 / 비즈니스 로직 / 데이터 처리 / DB 접근 / 게임 로직

예를 들어

GET /api/player/100 -> Client -> Nginx -> /api/* -> Application Server -> 인증 / Player 조회 / DB 조회 / JSON 생성 -> Nginx -> Client

처럼 역할을 분리한다.


Nginx와 Database

일반적인 구조에서는 Nginx가 Database에 직접 접근하지 않는다.

Client -> Nginx -> Application Server -> Database

Database 접근 권한과 비즈니스 로직은 Application Server가 담당한다.

Nginx는 네트워크 앞단의 Gateway에 가까운 역할을 한다.


Nginx 설정 구조

Nginx의 설정은 계층적인 구조를 가진다.

대표적인 구성은

http -> server -> location

이다.

예를 들어

http {
    server {
        listen 80;
        server_name example.com;

        location / {
            root /var/www/html;
        }

        location /api/ {
            proxy_pass http://localhost:3000;
        }
    }
}

구조를 보면

http -> server -> / -> Static Files / /api/ -> Application Server

처럼 이해할 수 있다.


Nginx의 실무적인 전체 구조

웹 서비스에서 흔히 볼 수 있는 구조를 하나로 묶으면 다음과 같다.

Internet -> Nginx -> Static Files / API Server / WebSocket Server -> Redis -> Database

서버를 여러 대 사용한다면

Internet -> Nginx -> API 1 / API 2 / API 3 -> Redis -> Database

처럼 Load Balancing을 적용할 수 있다.


Docker 환경에서의 Nginx

Docker를 사용하는 경우 Nginx를 별도의 Container로 구성할 수 있다.

Docker Network -> nginx / api / redis / postgres

Nginx가 api Container로 요청을 전달한다.

location /api/ {
    proxy_pass http://api:3000;
}

여기서 api는 Docker Network에서 다른 Container를 가리키는 이름이 될 수 있다.

구조는

Client -> Nginx Container -> API Container -> PostgreSQL Container

가 된다.

실제 Production에서는 Database를 별도 인프라로 분리하는 경우도 많다.


Nginx와 Kubernetes

Kubernetes 환경에서도 Nginx 계열의 Reverse Proxy 또는 Ingress Controller를 사용할 수 있다.

기본적인 개념은

Internet -> Ingress -> Service -> Pod

형태다.

Nginx가 Kubernetes 내부 Service로 트래픽을 전달하는 구조를 만들 수 있다.

다만 최신 Kubernetes 환경에서는 Nginx Ingress Controller만이 유일한 선택지는 아니다. Gateway API나 다른 Ingress/Gateway 구현체를 사용할 수도 있다.


Nginx의 핵심을 하나의 흐름으로 보면

Nginx를 가장 단순하게 표현하면 다음과 같다.

Client -> HTTP / HTTPS -> Nginx -> 정적 파일이면 직접 응답 / API 요청이면 Application Server / WebSocket이면 WebSocket Server / 여러 서버라면 Load Balancing

그리고 그 앞뒤에서

TLS / Logging / Caching / Compression / Rate Limiting / Header 처리 / Timeout

등을 수행한다.


정리

Nginx의 역할을 크게 나누면 다음과 같다.

Web Server -> 정적 파일 제공
Reverse Proxy -> Client 요청을 내부 서버로 전달
Load Balancer -> 여러 서버에 요청 분산
TLS Termination -> HTTPS 처리
Gateway -> URL / Host 등에 따른 요청 라우팅
Cache -> 응답 결과 저장
Security / Traffic Control -> Rate Limiting, 접근 제어 등
Logging -> 요청과 오류 기록

실무에서는 보통 다음과 같은 형태로 사용한다.

Client -> Nginx -> Static Files / API Server / WebSocket Server -> Redis -> Database

Nginx 자체가 애플리케이션 서버는 아니다.

Nginx는 서비스의 가장 앞에서 외부 요청을 받아 적절한 내부 시스템으로 전달하고, 그 과정에서 연결, 보안, 정적 파일, 트래픽 분산 같은 공통적인 네트워크 작업을 처리하는 계층으로 보는 것이 가장 정확하다.

특히 실무에서는

Client -> Nginx -> Application Server -> Database

라는 구조를 기본으로 이해하고, 여기에

Load Balancing / Caching / WebSocket / Docker / Kubernetes / TLS / CI/CD

를 필요에 따라 추가한다고 생각하면 전체 구조를 잡기 쉽다.