Nginx 리버스 프록시 설정: 효율적인 트래픽 라우팅

명확한 라우팅, 올바른 헤더, WebSocket 지원, 타임아웃, 버퍼링 및 문제 해결 단계를 포함한 Nginx 리버스 프록시를 설정합니다.

Nginx 리버스 프록시 설정: 효율적인 트래픽 라우팅

Nginx 리버스 프록시 설정을 통해 Nginx가 공개 웹 트래픽을 수신하여 하나 이상의 백엔드 애플리케이션으로 전달할 수 있습니다. 이는 Node.js, Python, Go, Java 또는 인터넷에 직접 노출되어서는 안 되는 다른 서비스에서 앱이 실행될 때 유용합니다.

사용자가 앱 포트에 직접 연결하는 대신, 표준 HTTP 또는 HTTPS 포트에서 Nginx에 연결합니다. Nginx는 공개 엣지를 처리한 다음 트래픽을 적절한 내부 서비스로 효율적으로 라우팅합니다.

리버스 프록시의 역할

리버스 프록시는 애플리케이션 서버 앞에 위치합니다. 클라이언트는 Nginx와 통신하고, Nginx는 백엔드와 통신합니다. 브라우저에게 Nginx는 웹사이트입니다. 앱에게 Nginx는 원래 요청 세부 정보를 보존하는 헤더를 전달하지 않는 한 업스트림 클라이언트입니다.

이 패턴은 여러 이점을 제공합니다:

  • 3000, 5000 또는 8080과 같은 개인 포트에서 앱을 실행할 수 있습니다.
  • Nginx에서 TLS를 종료할 수 있습니다.
  • 다른 호스트 이름이나 경로를 다른 서비스로 라우팅할 수 있습니다.
  • 버퍼링, 타임아웃, 압축 및 캐싱을 추가할 수 있습니다.
  • 공개 네트워크에서 백엔드 구현 세부 정보를 숨길 수 있습니다.

127.0.0.1:3000에서 실행되는 앱에 대한 기본 리버스 프록시는 다음과 같습니다:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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_pass 지시문은 Nginx에 요청을 보낼 위치를 알려줍니다. proxy_set_header 줄은 유용한 요청 컨텍스트를 보존합니다. 이 헤더가 없으면 앱은 모든 요청이 Nginx에서 온 것으로 기록하고 원래 요청이 HTTP인지 HTTPS인지 알 수 없습니다.

가상 호스트 구조가 처음이라면 여러 도메인 간에 트래픽을 분할하기 전에 Nginx 서버 블록을 검토하세요.

호스트 또는 경로별 트래픽 라우팅

리버스 프록시 규칙은 일반적으로 호스트 이름, 경로 또는 둘 다로 라우팅합니다. 호스트 기반 라우팅은 별도의 앱이 다른 도메인을 사용할 때 일반적입니다:

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

    location / {
        proxy_pass http://127.0.0.1:4000;
        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;
    }
}

경로 기반 라우팅은 하나의 도메인이 여러 서비스를 처리할 때 유용합니다:

server {
    listen 80;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
    }

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

proxy_pass에서 후행 슬래시에 주의하세요. Nginx에서 proxy_pass http://backend;proxy_pass http://backend/;는 위치 블록 내에서 사용될 때 전달된 URI를 다르게 다시 쓸 수 있습니다. 앱이 예상하는 정확한 URL 경로를 테스트하세요.

예를 들어, /api/users가 예기치 않게 백엔드에 /users 또는 /api/api/users로 도달하는 경우 먼저 위치 접두사와 후행 슬래시 조합을 확인하세요. 이것은 가장 흔한 리버스 프록시 실수 중 하나입니다.

헤더, 타임아웃 및 WebSocket

헤더는 백엔드가 원래 요청을 인식하도록 합니다. Host 헤더는 앱이 절대 URL을 생성하거나, 허용된 호스트를 검증하거나, 여러 테넌트를 지원할 때 중요합니다. X-Forwarded-For는 원래 클라이언트 IP를 보존하는 데 도움이 됩니다. X-Forwarded-Proto는 TLS 종료 후 앱이 보안 링크를 생성하는 데 도움이 됩니다.

백엔드가 WebSocket을 사용하는 경우 업그레이드 헤더를 추가하세요:

location /socket/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

타임아웃은 애플리케이션의 동작과 일치해야 합니다. 일반 웹 요청은 빠르게 완료되어야 합니다. 보고서 내보내기, 스트리밍 엔드포인트 또는 롱 폴링 요청은 더 많은 시간이 필요할 수 있습니다:

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

느린 엔드포인트 하나를 숨기기 위해 모든 곳에 큰 타임아웃을 설정하지 마세요. 긴 타임아웃은 리소스를 점유하고 실제 장애를 알아차리기 어렵게 만들 수 있습니다. 필요한 위치만 조정하세요.

버퍼링도 중요한 설정입니다. 기본적으로 Nginx는 클라이언트로 보내기 전에 업스트림 응답을 버퍼링할 수 있습니다. 이는 많은 웹 앱에 유용하지만 스트리밍 엔드포인트는 버퍼링을 비활성화해야 할 수 있습니다:

proxy_buffering off;

스트리밍 동작이 필요한 경우에만 사용하세요. 표준 HTML 및 API 응답의 경우 버퍼링은 종종 안정성을 향상시킵니다.

TLS 종료 및 HTTPS 리디렉션

많은 설정에서 Nginx는 HTTPS도 처리합니다. 이를 통해 백엔드 앱은 개인 HTTP 포트에서 실행되는 동안 사용자는 포트 443에서 정상적인 보안 사이트를 얻을 수 있습니다.

일반적인 형태는 다음과 같습니다:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
    }
}

리디렉션 서버 블록은 의도적으로 작습니다. 한 가지 작업만 수행합니다: 일반 HTTP 트래픽을 HTTPS로 이동시킵니다. HTTPS 서버 블록이 프록시를 처리합니다.

앱이 Nginx 뒤에 있고 여전히 http:// 링크를 생성하는 경우 X-Forwarded-Proto를 신뢰하는지 확인하세요. 많은 프레임워크는 전달된 헤더를 사용하기 전에 "trust proxy" 설정이나 허용된 프록시 목록이 필요합니다. 애플리케이션 계층에서 공개 인터넷의 전달된 헤더를 맹목적으로 신뢰하지 마세요. Nginx만이 앱 포트에 도달할 수 있는지 확인하세요.

업스트림 그룹 및 간단한 로드 밸런싱

하나의 백엔드로 충분하지 않을 때 업스트림 그룹을 정의하세요:

upstream app_backend {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
    }
}

오픈 소스 Nginx는 기본적으로 라운드 로빈 로드 밸런싱을 사용합니다. 긴 요청으로 인해 하나의 백엔드가 다른 것보다 더 바쁠 때 least_conn과 같은 옵션을 사용할 수도 있습니다. 오픈 소스 Nginx의 상태 확인은 대부분 수동적입니다: 백엔드가 실패하면 Nginx는 실패 설정에 따라 일정 기간 동안 사용 불가능으로 표시할 수 있습니다. Nginx Plus에는 활성 상태 확인이 있지만 모든 설치에 해당 기능이 있다고 가정하지 마세요.

업스트림 블록의 Keepalive는 백엔드 연결을 재사용을 위해 열어 둡니다. 이는 많은 작은 요청에 도움이 되지만 백엔드는 Nginx가 유지할 수 있는 유휴 및 활성 연결 수를 처리할 수 있어야 합니다.

컨테이너 및 개인 네트워크

리버스 프록시 설정은 Docker 또는 Kubernetes에서 localhost의 의미가 변경되기 때문에 종종 혼란스러워집니다. Nginx가 하나의 컨테이너 내에서 실행되는 경우 127.0.0.1:3000은 별도의 앱 컨테이너가 아닌 Nginx 컨테이너 자체를 가리킵니다.

Docker Compose에서는 서비스 이름으로 프록시하세요:

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

Kubernetes에서는 일반적으로 Service DNS 이름으로 프록시하지만, 많은 Kubernetes 배포는 수동으로 작성된 Nginx 서버 블록 대신 Ingress 컨트롤러를 사용합니다.

간단한 규칙은 다음과 같습니다: 노트북이나 백엔드 컨테이너가 아닌 Nginx가 실행되는 곳에서 연결을 테스트하세요. 이것이 실패하면 Nginx도 실패합니다:

curl -v http://app:3000/

배포 방식에 따라 Nginx 컨테이너 내부 또는 Nginx 호스트에서 실행하세요.

확인해야 할 보안 경계

리버스 프록시는 공개 노출을 줄여야 하며 실수로 더 많이 만들어서는 안 됩니다. 백엔드 앱은 일반적으로 개인 인터페이스, 개인 서브넷 또는 컨테이너 네트워크에서 수신 대기해야 합니다. 앱이 공개 VM에서 0.0.0.0:3000에서 수신 대기하는 경우 사용자는 http://example.com:3000을 방문하여 Nginx를 완전히 우회할 수 있습니다.

호스트의 수신 포트를 확인하세요:

sudo ss -ltnp

백엔드가 컨테이너 내부의 모든 인터페이스에서 수신 대기해야 하는 경우 방화벽 규칙, 보안 그룹 또는 컨테이너 네트워크 설정을 사용하여 외부에서 Nginx만 접근할 수 있도록 하세요. 이는 앱이 TLS, 요청 크기 제한, 속도 제한, 인증 게이트웨이 또는 IP 허용 목록을 위해 Nginx에 의존하는 경우가 많기 때문에 중요합니다.

또한 전달된 헤더에 주의하세요. X-Forwarded-For와 같은 헤더는 Nginx가 덮어쓰고 앱이 프록시만 신뢰하지 않는 한 클라이언트가 쉽게 스푸핑할 수 있습니다. 일반적인 Nginx 패턴은 다음과 같습니다:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

이는 클라이언트 주소를 체인에 추가합니다. 애플리케이션 또는 로깅 파이프라인은 신뢰할 수 있는 프록시 주소를 알고 있어야 합니다. 그렇지 않으면 속도 제한 또는 감사 로그에 잘못된 "클라이언트" IP가 기록될 수 있습니다.

요청 크기 제한도 이 대화에 포함됩니다. 앱이 파일 업로드를 허용하는 경우 client_max_body_size를 의도적으로 설정하세요:

client_max_body_size 25m;

모든 경로에 필요하지 않으면 전역적으로 큰 값으로 올리지 마세요. 프로필 사진 업로드 엔드포인트와 JSON 로그인 엔드포인트는 동일한 요청 본문 제한이 필요하지 않습니다.

실용적인 배포 체크리스트

리버스 프록시가 완료되었다고 말하기 전에 사용자와 운영자처럼 테스트하세요:

  • curl -I http://example.com/은 예상된 리디렉션 또는 응답을 표시해야 합니다.
  • curl -I https://example.com/은 예상된 상태와 헤더를 표시해야 합니다.
  • 앱 로그는 원래 호스트와 유용한 클라이언트 IP를 표시해야 합니다.
  • WebSocket 또는 스트리밍 엔드포인트는 별도로 테스트해야 합니다.
  • 잘못된 경로(예: /api/does-not-exist)는 앱이 예상하는 방식으로 실패해야 합니다.
  • Nginx 오류 로그는 정상 요청 중에 조용해야 합니다.

경로 라우팅의 경우 각 위치에 대해 세 가지 URL을 테스트하는 것을 좋아합니다: 기본 접두사, 하나의 일반 중첩 경로, 하나의 쿼리 문자열이 있는 경로. 예를 들어:

curl -i http://example.com/api/
curl -i http://example.com/api/users
curl -i 'http://example.com/api/users?page=2'

이 간단한 확인은 사용자가 발견하기 전에 많은 후행 슬래시 실수를 잡아냅니다.

리로드할 때마다 동일한 안전한 순서를 사용하세요:

sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log

앱이 TLS 종료 뒤에 있는 경우 생성된 링크, 리디렉션, 쿠키 및 콜백 URL이 HTTPS를 사용하는지도 확인하세요. 로그인 흐름은 리디렉션과 보안 쿠키가 앱이 원래 체계를 이해하는 데 의존하기 때문에 종종 먼저 깨집니다.

일반적인 실패 패턴

502 Bad Gateway는 일반적으로 Nginx가 리버스 프록시 위치에 도달했지만 업스트림에서 유효한 응답을 받지 못했음을 의미합니다. 백엔드가 다운되었거나, 포트가 잘못되었거나, 앱이 다른 인터페이스에서 수신 대기 중이거나, 방화벽에 의해 연결이 거부되었을 수 있습니다.

504 Gateway Timeout은 일반적으로 Nginx가 무언가에 연결되었지만 제 시간에 응답을 받지 못했음을 의미합니다. 느린 앱, 차단된 데이터베이스 쿼리, 과부하된 작업자 풀 또는 엔드포인트에 비해 너무 짧은 타임아웃일 수 있습니다. 알려진 장기 실행 내보내기 엔드포인트의 경우 proxy_read_timeout을 늘리는 것이 적절할 수 있습니다. 일반적으로 느린 앱에 대한 해결책은 아닙니다.

리디렉션 루프는 종종 TLS 종료와 애플리케이션 신뢰 설정 간의 불일치에서 발생합니다. 브라우저는 HTTPS를 통해 Nginx에 도달하고, Nginx는 HTTP를 통해 앱에 프록시하며, 앱은 원래 요청이 일반 HTTP였다고 생각합니다. 앱이 HTTPS로 리디렉션하지만 같은 일이 다시 발생합니다. X-Forwarded-Proto를 전달하는 것은 해결책의 절반일 뿐입니다. 앱도 프록시에서 이를 신뢰해야 합니다.

누락된 클라이언트 IP는 일반적으로 모든 요청이 127.0.0.1, Docker 브리지 주소 또는 개인 로드 밸런서 주소에서 오는 것으로 나타납니다. X-Real-IPX-Forwarded-For를 전달한 다음 애플리케이션 및 로깅 계층이 이를 안전하게 읽도록 구성하세요.

경로 라우팅 후 손상된 정적 자산은 종종 앱이 /에 있다고 가정하기 때문에 발생합니다. 앱을 /admin/ 아래에 마운트하면 여전히 /assets/app.css에 대한 링크를 생성할 수 있습니다. 앱 기본 경로 설정으로 이를 수정할 수 있습니다. Nginx에서 모든 자산 경로를 다시 쓰려고 시도하는 것은 일반적으로 취약합니다.

작은 실제 예제

하나의 VM에서 세 가지 서비스를 실행한다고 가정해 보세요:

  • 127.0.0.1:3000의 마케팅 사이트
  • 127.0.0.1:4000의 API
  • 127.0.0.1:5000의 관리 도구

다음과 같이 라우팅할 수 있습니다:

server {
    listen 443 ssl;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
        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;
    }

    location /admin/ {
        proxy_pass http://127.0.0.1:5000/;
        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;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        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;
    }
}

이것은 작동할 수 있지만 절충점이 있습니다. API와 관리 앱은 모두 접두사 아래에서 올바르게 동작해야 합니다. 그렇지 않으면 api.example.comadmin.example.com과 같은 별도의 호스트 이름이 더 깔끔할 수 있습니다. 좋은 리버스 프록시 설계는 Nginx가 구성을 수락하게 하는 것뿐만 아니라 애플리케이션이 사용할 수 있는 라우팅을 선택하는 것입니다.

설정 테스트 및 문제 해결

리로드하기 전에 항상 구성을 테스트하세요:

nginx -t

그런 다음 Nginx를 리로드하고 공개 호스트 이름을 통해 요청을 보내세요. 브라우저와 로그를 모두 확인하세요. Nginx 액세스 로그는 요청이 Nginx에 도달했는지 보여줍니다. 오류 로그는 연결 실패, 업스트림 타임아웃 및 잘못된 게이트웨이 세부 정보를 보여줍니다.

실용적인 예: Node.js 앱이 curl http://127.0.0.1:3000에서 잘 실행되지만 공개 사이트에 502 Bad Gateway가 표시됩니다. 이는 Nginx에 연결할 수 있지만 업스트림과 성공적으로 통신할 수 없음을 의미합니다. 앱이 예상된 주소에서 수신 대기 중인지, 포트가 올바른지, 로컬 방화벽이 연결을 차단하는지 확인하세요.

일반적인 리버스 프록시 문제는 다음과 같습니다:

  • 잘못된 업스트림 포트 또는 주소.
  • Nginx가 다른 컨테이너에서 실행될 때 백엔드가 localhost에 바인딩됨.
  • WebSocket 업그레이드 헤더 누락.
  • Host 헤더가 예상치 못한 경우 앱이 요청을 거부.
  • 후행 슬래시로 인한 잘못된 URI 재작성.
  • 느린 엔드포인트에 비해 너무 짧은 타임아웃.

더 깊은 업스트림 실패의 경우 Nginx 502 문제 해결을 사용하세요.

도움이 필요할 때

리버스 프록시가 여러 컨테이너, 개인 네트워크, TLS 인증서 또는 로드 밸런싱된 업스트림에 걸쳐 있는 경우 DevOps 엔지니어에게 도움을 요청하세요. 이러한 설정은 Nginx 문제처럼 보이지만 실제로는 DNS, 방화벽, 컨테이너 네트워킹 또는 애플리케이션 상태 문제인 방식으로 실패할 수 있습니다.

또한 공개 리버스 프록시를 통해 관리 패널, 내부 API 또는 스테이징 서비스를 노출하기 전에 도움을 받아야 합니다. 작은 라우팅 실수로 심각한 액세스 문제가 발생할 수 있습니다.

Nginx 리버스 프록시 설정은 웹 인프라에서 가장 유용한 패턴 중 하나입니다. 라우팅을 명확하게 유지하고, 올바른 헤더를 전달하고, 경로 동작을 신중하게 테스트하고, Nginx가 백엔드 서비스의 안정적인 공개 진입점이 되도록 하세요.