Nginx에서 HTTP를 HTTPS로 리디렉션: 모범 사례

전용 80번 포트 블록을 사용하여 Nginx에서 안정적인 HTTP에서 HTTPS로의 리디렉션을 설정하고, 리디렉션 루프를 피하며, 올바른 리디렉션 코드를 선택하고, 준비가 되면 HSTS를 추가하세요.

Nginx에서 HTTP를 HTTPS로 리디렉션: 모범 사례

Nginx에서 HTTP를 HTTPS로 리디렉션하면 사용자가 이전 http:// URL을 입력하거나 오래된 링크를 따라가더라도 암호화된 버전의 사이트를 사용하도록 보장합니다. 깔끔한 리디렉션 설정은 보안을 강화하고, 중복 URL을 방지하며, 사용자에게 일관된 진입점을 제공합니다.

가장 좋은 접근 방식은 일반적으로 간단합니다. 포트 80은 트래픽을 리디렉션하는 데에만 충분히 열어두고, 실제 사이트는 유효한 TLS 인증서와 함께 포트 443에서 제공하는 것입니다.

세부 사항이 중요한 이유는 리디렉션이 거의 올바르게 설정되기 쉽기 때문입니다. 경로를 삭제하는 리디렉션은 북마크를 망가뜨립니다. 잘못된 호스트 이름을 유지하는 리디렉션은 중복된 표준 URL을 생성할 수 있습니다. 로드 밸런서 뒤에서 TLS가 종료되는 위치를 Nginx가 이해하지 못하면 리디렉션이 무한 루프에 빠질 수 있습니다.

리디렉션을 공개 API의 일부로 생각하세요. 사람들은 채팅에 링크를 붙여넣고, 검색 엔진은 이를 크롤링하며, 모니터링 시스템은 이를 확인하고, 오래된 이메일은 수년간 이를 유지합니다. 리디렉션이 안정적이면 아무도 눈치채지 못합니다. 엉성하면 사용자는 애플리케이션이 요청을 받기도 전에 인증서 경고, 끊어진 경로, 또는 너무 많은 리디렉션 오류를 보게 됩니다.

HTTPS 리디렉션이 중요한 이유

HTTPS는 TLS 암호화를 사용하여 브라우저와 서버 간의 트래픽을 보호합니다. 암호화가 없으면 사용자와 사이트 사이의 네트워크에서 데이터를 검사하거나 수정할 수 있습니다. 이는 로그인, 양식, 쿠키, 관리자 영역, API 및 일반적인 브라우징에도 중요합니다.

리디렉션은 일관성에도 도움이 됩니다. 검색 엔진과 사용자는 http://example.com/pagehttps://example.com/page를 두 개의 별도 대상으로 보아서는 안 됩니다. 영구 리디렉션은 HTTPS가 선호되는 버전임을 클라이언트에 알립니다.

표준 Nginx 패턴은 전용 포트 80 서버 블록입니다:

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

    return 301 https://$host$request_uri;
}

그런 다음 HTTPS 서버 블록이 실제 사이트를 처리합니다:

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

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

    root /var/www/example.com;
    index index.html;
}

return 301 지시문은 효율적이고 명확합니다. 추가 위치 처리를 하지 않고 영구 리디렉션을 보내도록 Nginx에 지시합니다. $request_uri는 경로와 쿼리 문자열을 유지하므로 /docs?page=2https://example.com/docs?page=2가 됩니다.

전체 TLS 설정에 대해서는 Nginx를 HTTPS로 보호하기 단계별 가이드를 참조하세요.

대부분의 사이트에서는 포트 80 블록에 애플리케이션 로직을 넣지 않는 것이 좋습니다. 정적 파일을 제공하거나, 앱에 프록시하거나, 많은 위치 집합을 포함해서는 안 됩니다. 단순하게 유지하세요:

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

이 작은 블록은 감사하기 쉽고 HTTPS 구성에서 벗어날 가능성이 적습니다.

301 vs 302 리디렉션 선택

프로덕션 사이트에서 HTTP에서 HTTPS로의 영구 리디렉션에는 301을 사용하세요. 브라우저와 검색 엔진은 HTTPS URL이 HTTP URL을 대체해야 함을 이해합니다.

리디렉션이 임시적인 경우에만 302 또는 307을 사용하세요. 예를 들어, 인증서와 호스트 이름이 최종 확정되기 전에 테스트 중에 임시 리디렉션을 사용할 수 있습니다. HTTPS 사이트가 준비되면 영구 리디렉션으로 전환하세요.

초기 설정 중에는 주의하세요. 브라우저는 301 리디렉션을 적극적으로 캐시할 수 있습니다. 실수로 잘못된 호스트 이름으로 리디렉션하면 Nginx를 수정한 후에도 브라우저가 잘못된 리디렉션을 계속 사용할 수 있습니다. 가능하면 curl, 개인 브라우저 창, 비프로덕션 호스트 이름으로 테스트하세요.

실용적인 배포 흐름은 다음과 같습니다:

  1. HTTPS 서버 블록이 직접 작동하는지 확인합니다.
  2. 인증서가 모든 호스트 이름과 일치하는지 확인합니다.
  3. HTTP 리디렉션 서버 블록을 추가합니다.
  4. 여러 경로와 쿼리 문자열을 테스트합니다.
  5. 동작이 올바른 경우에만 임시 리디렉션을 영구 리디렉션으로 변경합니다.

또한 표준 호스트 이름을 결정해야 합니다. example.comwww.example.com이 모두 작동하는 경우 선호하는 공개 호스트 이름을 하나 선택하세요. 그렇지 않으면 사용자가 호스트 이름 사이를 오가거나 검색 엔진이 둘 다 인덱싱할 수 있습니다.

예를 들어, 모든 HTTP 트래픽을 www가 없는 HTTPS 호스트 이름으로 리디렉션하려면:

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

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

이는 사용자가 요청한 호스트를 유지하는 https://$host$request_uri와 다릅니다.

동작을 명시적으로 만들고 싶다면 호스트 이름 리디렉션을 스킴 리디렉션과 분리할 수도 있습니다:

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

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

이것은 더 장황하지만 최종 대상을 명확하게 합니다. 작은 사이트에서는 두 스타일 모두 괜찮습니다. 많은 별칭이 있는 큰 사이트에서는 명시적인 서버 블록이 나중에 변경할 때 혼란을 줄일 수 있습니다.

리디렉션 루프 및 인증서 문제 방지

리디렉션 루프는 Nginx, 로드 밸런서 또는 애플리케이션이 다른 리디렉션을 트리거하는 URL로 요청을 계속 보낼 때 발생합니다. 이는 TLS가 Nginx 앞에서 종료될 때(예: 클라우드 로드 밸런서 또는 CDN) 흔히 발생합니다.

간단한 단일 서버 설정에서는 Nginx가 HTTPS를 직접 수신하므로 리디렉션이 간단합니다. 프록시 체인에서는 사용자가 HTTPS로 연결했더라도 Nginx가 로드 밸런서로부터 일반 HTTP를 수신할 수 있습니다. 그런 다음 애플리케이션이 로컬 연결 스킴을 기반으로 HTTPS를 강제하려고 하면 루프가 발생할 수 있습니다.

해결 방법은 아키텍처에 따라 다릅니다. 일반적으로 로드 밸런서는 X-Forwarded-Proto와 같은 헤더를 전달해야 하며, 애플리케이션이나 Nginx 구성은 알려진 프록시 주소에서만 이를 신뢰해야 합니다.

예를 들어, Nginx가 신뢰할 수 있는 로드 밸런서 뒤에 있고 내부 HTTP만 수신하는 경우 Nginx가 모든 로컬 HTTP 요청을 리디렉션하지 않도록 할 수 있습니다. 대신 로드 밸런서가 공개 HTTP에서 HTTPS로의 리디렉션을 처리하고, Nginx는 개인 네트워크에서 트래픽을 제공합니다. Nginx가 결정을 내려야 하는 경우 앞에 있는 프록시로부터 신뢰할 수 있는 전달 프로토콜 정보가 필요합니다. 임의의 인터넷 클라이언트로부터의 X-Forwarded-Proto를 신뢰하지 마세요.

또한 인증서가 모든 리디렉션된 호스트 이름을 포함하는지 확인하세요. 사용자가 http://www.example.com을 방문하여 https://www.example.com으로 리디렉션하는 경우 인증서가 www.example.com에 유효해야 합니다. 모든 것을 https://example.com으로 리디렉션하는 경우 최종 사이트의 인증서가 example.com을 포함해야 합니다.

다음으로 테스트하세요:

curl -I http://example.com/some/path?x=1

다음을 확인하세요:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/some/path?x=1

그런 다음 HTTPS URL을 테스트하세요:

curl -I https://example.com/some/path?x=1

HTTPS 응답은 HTTP로의 또 다른 리디렉션이 아닌 페이지의 실제 상태를 반환해야 합니다.

또한 두 호스트 이름이 모두 존재하는 경우 둘 다 테스트하세요:

curl -I http://www.example.com/
curl -I https://www.example.com/
curl -I http://example.com/
curl -I https://example.com/

짧고 예측 가능한 체인을 찾고 있습니다. HTTP에서 표준 HTTPS URL로의 한 번의 리디렉션이 좋습니다. 여러 홉(예: HTTP non-www에서 HTTPS non-www, HTTPS www, 다시 돌아오기)은 Nginx, 앱, CDN 규칙 또는 DNS 수준 전달이 서로 충돌하고 있다는 신호입니다.

전체 체인을 검사하려면:

curl -IL http://www.example.com/some/path

-L 플래그는 리디렉션을 따릅니다. 깔끔한 설정에서는 초기 HTTP 응답과 최종 HTTPS 응답이 표시되어야 합니다. 세 개 또는 네 개의 Location 헤더가 표시되면 표준 URL로 가는 명확한 경로가 하나 있을 때까지 규칙을 단순화하세요.

HSTS 및 기타 모범 사례

HTTPS 설정이 안정화된 후에는 HTTP Strict Transport Security(HSTS)를 고려할 수 있습니다. HSTS는 브라우저에 향후 방문 시 자동으로 HTTPS를 사용하도록 지시합니다.

일반적인 헤더는 다음과 같습니다:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

이것을 가볍게 추가하지 마세요. 하위 도메인을 포함하는 경우 모든 하위 도메인이 HTTPS를 지원해야 합니다. 나중에 HTTPS를 중단하면 HSTS 헤더를 본 브라우저가 HTTP 버전에 액세스하지 못할 수 있습니다. 테스트 중에는 더 짧은 max-age로 시작하고, 확신이 생기면 늘리세요.

기타 모범 사례:

  • 포트 80 블록을 단순하게 유지하세요.
  • 이유가 없는 한 경로와 쿼리 문자열을 유지하세요.
  • 하나의 표준 호스트 이름을 선택하세요.
  • 브라우저뿐만 아니라 curl로 리디렉션을 테스트하세요.
  • 인증서를 자동으로 갱신하고 갱신 실패를 모니터링하세요.
  • 가능하면 리디렉션 로직을 앱에서 복제하지 말고 Nginx에 유지하세요.

간단한 리디렉션 규칙은 추론하기 쉽고 나중에 사이트 변경 시 깨질 가능성이 적습니다.

일반적인 실수

가장 흔한 실수는 간단한 리디렉션에 rewrite를 사용하는 것입니다:

rewrite ^ https://example.com$request_uri permanent;

작동할 수 있지만 return 301 ...이 더 명확하고 불필요한 재작성 처리를 피합니다. 기본 스킴 리디렉션이 아닌 실제로 패턴 일치가 필요한 경우에만 rewrite를 사용하세요.

또 다른 실수는 $server_name이 무엇을 포함하는지 이해하지 않고 리디렉션하는 것입니다. $host는 요청 호스트 헤더에서 오는 반면, $server_name은 일치하는 Nginx 서버 이름을 기반으로 합니다. 표준 리디렉션의 경우 리터럴 호스트 이름이 가장 덜 놀라운 옵션인 경우가 많습니다:

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

또한 인증서 도구가 포트 80에서 필요로 하는 경우 ACME HTTP 챌린지 경로를 리디렉션하지 않도록 해야 합니다. 많은 Let's Encrypt 설정이 이를 자동으로 처리하지만 사용자 정의 구성에는 예외가 필요할 수 있습니다:

location /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
}

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

인증서 클라이언트가 이를 사용하는 경우에만 해당 예외를 추가하세요. 인증서가 DNS 검증 또는 도구 관리 임시 서버를 통해 갱신되는 경우 리디렉션 블록을 단순하게 유지하세요.

안전한 롤아웃 패턴

프로덕션 사이트의 경우 변경을 단계적으로 수행하세요:

  1. HTTPS 서버 블록이 사이트를 올바르게 제공하는지 확인합니다.
  2. 인증서 갱신이 작동하거나 모니터링되는지 확인합니다.
  3. 아직 호스트 이름을 테스트 중인 경우 포트 80에 임시 리디렉션을 추가합니다.
  4. curl -Icurl -IL로 일반적인 URL을 테스트합니다.
  5. 리디렉션 대상이 최종 확정되면 301로 전환합니다.
  6. 장기 HSTS를 활성화하기 전에 기다립니다.

이 대기 기간은 유용합니다. 잊혀진 하위 도메인, 오래된 웹훅 URL, 하드코딩된 http:// 링크 또는 첫 번째 테스트 머신에서 보이지 않았던 CDN 규칙을 발견할 시간을 줍니다.

또한 모니터링을 염두에 두세요. 가동 시간 확인이 여전히 http://example.com을 가리키는 경우 301을 예상해야 하는지 아니면 리디렉션을 따라 최종 HTTPS 페이지를 확인해야 하는지 결정하세요. 둘 다 유효할 수 있지만 모니터는 실제로 원하는 동작과 일치해야 합니다.

예: 정적 사이트 및 리버스 프록시

정적 사이트의 경우 HTTPS 블록은 문서 루트만 있을 수 있습니다:

server {
    listen 443 ssl http2;
    server_name example.com;

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

    root /var/www/example.com;
    index index.html;
}

Nginx 뒤에 있는 애플리케이션의 경우 리디렉션 블록은 동일하게 유지되지만 HTTPS 블록은 트래픽을 프록시합니다:

server {
    listen 443 ssl http2;
    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_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

중요한 점은 분리입니다. 포트 80은 브라우저가 어디로 가야 하는지 결정합니다. 포트 443은 사이트를 제공합니다. 이러한 작업을 혼합하면 특히 나중에 다른 프록시나 CDN이 추가될 때 리디렉션 동작을 추론하기가 더 어려워집니다.

편집 후에는 항상 다음을 실행하세요:

sudo nginx -t
sudo systemctl reload nginx

테스트가 실패하면 다시 로드하지 마세요. 먼저 구문 오류를 수정한 다음 다시 테스트하세요.

서버에서 SSH를 통해서뿐만 아니라 외부에서도 최종 확인을 수행하는 것이 좋습니다. 방화벽, CDN 또는 로드 밸런서는 실제 사용자가 보는 것을 변경할 수 있습니다. 노트북, 모니터링 위치 또는 임시 클라우드 인스턴스에서 동일한 curl -I 검사를 실행하세요. 외부 결과가 localhost와 다른 경우 리디렉션 문제는 Nginx 앞의 네트워크 계층에 있을 가능성이 높으며 서버 블록 자체에 있는 것이 아닙니다. 작동 중인 구성을 다시 작성하기 전에 이를 확인하세요.

도움을 받아야 할 때

사이트가 CDN, 클라우드 로드 밸런서, Kubernetes 인그레스 또는 여러 리버스 프록시 뒤에 있는 경우 DevOps 엔지니어의 도움을 받으세요. 계층화된 인프라에서 HTTPS 리디렉션은 TLS가 종료되는 위치와 신뢰되는 헤더에 따라 달라집니다.

또한 여러 하위 도메인에 걸쳐 장기 HSTS를 활성화하기 전에 도움을 요청해야 합니다. 잘못된 설정은 HTTPS를 사용할 준비가 되지 않은 서비스에서 사용자를 잠글 수 있습니다.

Nginx에서 HTTP를 HTTPS로 리디렉션하는 것은 작은 구성 변경이지만 보안에 큰 영향을 미칩니다. 전용 포트 80 리디렉션 블록을 사용하고, 요청 URI를 유지하며, 인증서를 확인하고, 완료했다고 말하기 전에 루프를 테스트하세요.