
NGINX SSL Termination
이 문서에서는 NGINX 및 NGINX Plus 에서 HTTPS 서버를 구성하는 방법을 설명합니다.
클라이언트의 HTTPS 트래픽을 Terminate하여 Upstream 웹 및 애플리케이션 서버의 SSL/TLS 암호화에 따른 연산 부하를 덜어줍니다.
목차
1. HTTPS 서버 설정
1-1. 클라이언트 인증서의 OCSP 유효성 검사
2. HTTPS 서버 최적화
3. SSL 인증서 체인
4. 단일 HTTP/HTTPS 서버
5. 이름 기반 HTTPS 서버
5-1. 여러 이름을 가진 SSL 인증서
5-2. 서버 이름 표시
6. 호환성 참고사항
1
1. HTTPS 서버 설정
HTTPS 서버를 설정하려면 nginx.conf 파일에서 server 블록의 listen 지시문에 ssl 매개변수를 추가한 다음 서버 인증서 및 비공개 키 파일의 위치를 지정합니다.
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}
서버 인증서는 공개 Entity입니다. 이 인증서는 NGINX 또는 NGINX Plus 서버에 연결하는 모든 클라이언트에 전송됩니다. Private Key는 보안 Entity이며 액세스가 제한된 파일에 저장해야 합니다. 하지만 NGINX Master Process는 이 파일을 읽을 수 있어야 합니다. 또는 Private Key를 인증서와 같은 파일에 저장할 수도 있습니다:
ssl_certificate www.example.com.cert;
ssl_certificate_key www.example.com.cert;
이 경우 파일에 대한 액세스를 제한하는 것이 중요합니다. 인증서와 키는 하나의 파일에 저장되지만 클라이언트에게는 인증서만 전송된다는 점에 유의하세요.
ssl_protocols 및 ssl_ciphers 지시문을 사용하여 클라이언트가 연결을 설정할 때 강력한 SSL/TLS 버전과 암호만 사용하도록 요구할 수 있습니다.
버전 1.9.1부터 NGINX는 다음과 같은 기본값을 사용합니다.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
구형 암호의 설계에서 취약점이 발견되는 경우가 있으며, 최신 NGINX 구성에서는 이를 비활성화하는 것이 좋습니다(안타깝게도 기존 NGINX 배포의 하위 호환성 때문에 기본 구성은 쉽게 변경할 수 없습니다). CBC 모드 암호는 여러 공격(특히 CVE-2011-3389에 설명된 BEAST 공격)에 취약할 수 있으며, 레거시 클라이언트를 지원해야 하는 경우가 아니라면 POODLE 공격으로 인해 SSLv3를 사용하지 않는 것이 좋습니다.
1-1. 클라이언트 인증서의 OCSP 유효성 검사
NGINX Plus 는 온라인 인증서 상태 프로토콜(OCSP)을 사용하여 X.509 클라이언트 인증서가 제시될 때 유효성을 확인하도록 구성할 수 있습니다. 클라이언트 인증서 상태에 대한 OCSP 요청은 인증서 유효성을 확인하고 인증서 상태와 함께 응답을 반환하는 OCSP 응답자에게 전송됩니다.
Good
– 인증서가 해지되지 않은 경우Revoked
– 인증서가 해지된 경우Unknown
– 클라이언트 인증서에 대한 정보를 사용할 수 없는 경우
SSL 클라이언트 인증서의 OCSP 유효성 검사를 사용하려면 인증서 확인을 활성화하는 ssl_verify_client
지시문과 함께 ssl_ocsp
지시문을 지정합니다.
server {
listen 443 ssl;
ssl_certificate /etc/ssl/foo.example.com.crt;
ssl_certificate_key /etc/ssl/foo.example.com.key;
ssl_verify_client on;
ssl_trusted_certificate /etc/ssl/cachain.pem;
ssl_ocsp on; # Enable OCSP validation
#...
}
ssl_ocsp_responder
지시문으로 다른 URI를 정의하지 않는 한, NGINX는 클라이언트 인증서에 내장된 OCSP URI로 OCSP 요청을 보냅니다. http://
OCSP 응답자만 지원됩니다.
#...
ssl_ocsp_responder http://ocsp.example.com/;
#...
모든 Worker Process가 공유하는 단일 메모리 영역에 OCSP 응답을 캐시하려면 ssl_ocsp_cache
지시문을 지정하여 Zone의 이름과 크기를 정의합니다. OCSP 응답의 nextUpdatevalue
가 다른 값을 지정하지 않는 한 응답은 1
시간 동안 캐시됩니다.
#...
ssl_ocsp_cache shared:one:10m;
#...
클라이언트 인증서 유효성 검사 결과는 $ssl_client_verify
변수에서 사용할 수 있으며, 여기에는 OCSP 실패의 원인이 포함되어 있습니다.
2. HTTPS 서버 최적화
SSL 작업은 추가 CPU 리소스를 소비합니다. 가장 CPU를 많이 사용하는 작업은 SSL Handshake입니다. Client 별 이러한 작업의 수를 최소화하는 방법에는 두 가지가 있습니다.
- KeepAlive 연결을 활성화하여 하나의 연결을 통해 여러 요청을 전송하기
- 병렬 및 후속 연결에 대한 SSL Handshake를 피하기 위해 SSL 세션 매개변수 재사용하기
세션은 Worker Process 간에 공유되는 SSL 세션 캐시에 저장되며 ssl_session_cache
지시문으로 구성됩니다. 1MB의 캐시에는 약 4000개의 세션이 포함됩니다. 기본 캐시 시간제한은 5분입니다. 이 시간제한은 ssl_session_timeout
지시문을 사용하여 늘릴 수 있습니다. 다음은 10MB 공유 세션 캐시가 있는 멀티코어 시스템에 최적화된 샘플 구성입니다.
worker_processes auto;
http {
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
server {
listen 443 ssl;
server_name www.example.com;
keepalive_timeout 70;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers HIGH:!aNULL:!MD5;
#...
}
}
3. SSL 인증서 체인
일부 브라우저에서는 잘 알려진 인증 기관에서 서명한 인증서에 대해 불만을 제기할 수 있는 반면, 다른 브라우저에서는 문제없이 인증서를 수락할 수 있습니다. 이는 발급 기관이 특정 브라우저에 배포된 잘 알려진 신뢰할 수 있는 인증 기관의 기반에 없는 중간 인증서를 사용하여 서버 인증서에 서명했기 때문에 발생합니다. 이 경우 인증 기관은 서명된 서버 인증서에 연결해야 하는 체인 인증서 번들을 제공합니다. 서버 인증서는 결합된 파일에서 체인된 인증서 앞에 나타나야 합니다.
$ cat www.example.com.crt bundle.crt > www.example.com.chained.crt
결과 파일은 ssl_certificate
지시문에 사용해야 합니다.
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.chained.crt;
ssl_certificate_key www.example.com.key;
#...
}
서버 인증서와 번들을 잘못된 순서로 연결한 경우, NGINX가 시작하지 못하고 다음 오류 메시지를 표시합니다.
SSL_CTX_use_PrivateKey_file(" ... /www.example.com.key") failed
(SSL: error:0B080074:x509 certificate routines:
X509_check_private_key:key values mismatch)
이 오류는 NGINX가 서버 인증서 대신 번들의 첫 번째 인증서와 함께 Private Key를 사용하려고 했기 때문에 발생합니다.
브라우저는 일반적으로 신뢰할 수 있는 기관이 서명하고 수신한 중간 인증서를 저장합니다. 따라서 활발하게 사용되는 브라우저는 이미 필요한 중간 인증서를 가지고 있을 수 있으며 체인 번들 없이 전송된 인증서에 대해 불평하지 않을 수 있습니다. 서버가 완전한 인증서 체인을 보내도록 하기 위해 openssl Command-Line 유틸리티를 사용할 수 있습니다:
$ openssl s_client -connect www.godaddy.com:443
...
Certificate chain
0 s:/C=US/ST=Arizona/L=Scottsdale/1.3.6.1.4.1.311.60.2.1.3=US
/1.3.6.1.4.1.311.60.2.1.2=AZ/O=GoDaddy.com, Inc
/OU=MIS Department/CN=www.GoDaddy.com
/serialNumber=0796928-7/2.5.4.15=V1.0, Clause 5.(b)
i:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
/OU=http://certificates.godaddy.com/repository
/CN=Go Daddy Secure Certification Authority
/serialNumber=07969287
1 s:/C=US/ST=Arizona/L=Scottsdale/O=GoDaddy.com, Inc.
/OU=http://certificates.godaddy.com/repository
/CN=Go Daddy Secure Certification Authority
/serialNumber=07969287
i:/C=US/O=The Go Daddy Group, Inc.
/OU=Go Daddy Class 2 Certification Authority
2 s:/C=US/O=The Go Daddy Group, Inc.
/OU=Go Daddy Class 2 Certification Authority
i:/L=ValiCert Validation Network/O=ValiCert, Inc.
/OU=ValiCert Class 2 Policy Validation Authority
/CN=http://www.valicert.com//emailAddress=info@valicert.com
...
이 예에서 www.GoDaddy.com
서버 인증서 #0의 주체(“s
“)는 인증서 #1의 주체인 발급자(“i
“)가 서명합니다. 인증서 #1은 인증서 #2의 주체가 되는 발급자에 의해 서명되었습니다. 그러나 이 인증서는 브라우저 자체에 인증서가 저장되어 있는 잘 알려진 발급자인 ValiCert, Inc.
가 서명했습니다.
4. 단일 HTTP/HTTPS 서버
동일한 가상 서버에 ssl
매개변수가 있는 listen
지시문과 없는 지시문을 각각 하나씩 배치하여 HTTP 및 HTTPS 요청을 모두 처리하는 단일 서버를 구성할 수 있습니다.
server {
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
#...
}
NGINX Plus 버전 0.7.13 이하에서는 위와 같이 개별 수신 소켓에 대해 SSL을 선택적으로 활성화할 수 없습니다. SSL은 ssl
지시문을 사용하여 전체 서버에 대해서만 활성화할 수 있으므로 단일 HTTP/HTTPS 서버를 설정할 수 없습니다. 이 문제를 해결하기 위해 listen
지시문에 ssl
매개변수가 추가되었습니다. 따라서 ssl
지시문은 버전 0.7.14 이상에서 더 이상 사용되지 않습니다.
5. 이름 기반 HTTPS 서버
두 개 이상의 HTTPS 서버가 단일 IP 주소에서 수신하도록 구성된 경우 일반적으로 문제가 발생합니다.
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}
server {
listen 443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}
이 구성을 사용하면 브라우저는 기본 서버의 인증서를 받습니다. 이 경우 요청된 서버 이름에 관계없이 www.example.com
입니다. 이는 SSL 프로토콜 자체의 동작으로 인해 발생합니다. 브라우저가 HTTP 요청을 보내기 전에 SSL 연결이 설정되며 NGINX는 요청된 서버의 이름을 알지 못합니다. 따라서 기본 서버의 인증서만 제공할 수 있습니다.
이 문제를 해결하는 가장 좋은 방법은 모든 HTTPS 서버에 별도의 IP 주소를 할당하는 것입니다.
server {
listen 192.168.1.1:443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
#...
}
server {
listen 192.168.1.2:443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
#...
}
HTTPS Upstream에 대한 몇 가지 특정 Proxy 설정(proxy_ssl_ciphers, proxy_ssl_protocols, proxy_ssl_session_reuse)이 있으며, 이는 NGINX Plus 와 Upstream 서버 간의 SSL을 미세 조정하는 데 사용할 수 있다는 것을 알아두세요. 이에 대한 자세한 내용은 HTTP Proxy 모듈 문서에서 확인할 수 있습니다.
5-1. 여러 이름을 가진 SSL 인증서
여러 HTTPS 서버에서 단일 IP 주소를 공유하는 다른 방법도 있습니다. 하지만 모두 단점이 있습니다. 한 가지 방법은 SubjectAltName
인증서 필드에 여러 개의 이름을 가진 인증서를 사용하는 것입니다(예: www.example.com
및 www.example.org
). 그러나 SubjectAltName
필드의 길이는 제한되어 있습니다.
또 다른 방법은 와일드카드 이름(예: *.example.org
)이 있는 인증서를 사용하는 것입니다. 와일드카드 인증서는 지정된 도메인의 모든 하위 도메인을 보호하지만 한 수준에서만 보호합니다. 이 인증서는 www.example.org
와 일치하지만 example.org
또는 www.sub.example.org
와 일치하지 않습니다. 이 두 가지 방법을 결합할 수도 있습니다. 인증서는 SubjectAltName
필드에 정확한 이름 및 와일드카드 이름을 포함할 수 있습니다. 예를 들어, example.org
및 *.example.org
.
모든 서버에서 단일 메모리 복사본을 상속하도록 여러 이름의 인증서 파일과 해당 Private Key 파일을 구성의 http Level에 배치하는 것이 좋습니다.
ssl_certificate common.crt;
ssl_certificate_key common.key;
server {
listen 443 ssl;
server_name www.example.com;
#...
}
server {
listen 443 ssl;
server_name www.example.org;
#...
}
5-2. 서버 이름 표시
단일 IP 주소에서 여러 HTTPS 서버를 실행하기 위한 보다 일반적인 솔루션은 브라우저가 SSL Handshake 중에 요청된 서버 이름을 전달할 수 있도록 하는 TLS Server Name Indication(SNI) 확장(RFC 6066)을 사용하는 것입니다. 이 솔루션을 사용하면 서버는 연결에 어떤 인증서를 사용해야 하는지 알 수 있습니다. 하지만 SNI는 브라우저 지원이 제한적입니다. 현재 다음 브라우저 버전부터 지원됩니다.
- Opera 8.0
- MSIE 7.0 (단, Windows Vista 이상에서만 사용 가능)
- Mozilla 플랫폼 rv:1.8.1을 사용하는 Firefox 2.0 및 기타 브라우저
- Safari 3.2.1(Windows 버전은 Vista 이상에서 SNI 지원)
- Chrome (Windows 버전은 Vista 이상에서 SNI를 지원합니다.)
SNI에서는 도메인 이름만 전달할 수 있습니다. 그러나 일부 브라우저는 요청에 리터럴 IP 주소가 포함된 경우 서버의 IP 주소를 이름으로 전달합니다. 이에 의존하지 않는 것이 가장 좋습니다.
NGINX에서 SNI를 사용하려면 NGINX Binary가 빌드된 OpenSSL 라이브러리와 런타임에 동적으로 연결되는 라이브러리 모두에서 지원되어야 합니다. OpenSSL은 구성 옵션 --enable-tlsext
로 빌드된 경우 버전 0.9.8f부터 SNI를 지원합니다. OpenSSL 버전 0.9.8j부터 이 옵션은 기본적으로 활성화되어 있습니다. SNI를 지원하도록 빌드된 경우, -V
스위치로 실행하면 다음과 같이 표시됩니다.
$ nginx -V
...
TLS SNI support enabled
...
그러나 SNI를 지원하는 NGINX가 SNI를 지원하지 않는 OpenSSL 라이브러리에 동적으로 연결되는 경우, NGINX는 경고를 표시합니다:
NGINX was built with SNI support, however, now it is linked
dynamically to an OpenSSL library which has no tlsext support,
therefore SNI is not available
6. 호환성 참고사항
SNI 지원 상태는 버전 0.8.21 및 0.7.62부터 -V
스위치로 표시됩니다.
listen 지시문에 대한 ssl
매개변수는 버전 0.7.14부터 지원되었습니다. 버전 0.8.21 이전에는 default
매개변수와 함께만 지정할 수 있었습니다.
SNI는 버전 0.5.23부터 지원되었습니다.
공유 SSL 세션 캐시는 버전 0.5.6부터 지원되었습니다.
버전 1.9.1 이상: 기본 SSL 프로토콜은 TLSv1
, TLSv1.1
및 TLSv1.2
(OpenSSL 라이브러리에서 지원하는 경우)입니다.
버전 0.7.65 및 0.8.19 이상부터 기본 SSL 프로토콜은 SSLv3
, TLSv1
, TLSv1.1
및 TLSv1.2
(OpenSSL 라이브러리에서 지원하는 경우)입니다.
0.7.64 및 0.8.18 이하 버전에서 기본 SSL 프로토콜은 SSLv2
, SSLv3
및 TLSv1
입니다.
버전 1.0.5 이상에서 기본 SSL 암호는 HIGH:!aNULL:!MD5
입니다.
0.7.65 및 0.8.20 이상 버전에서 기본 SSL 암호는 HIGH:!ADH:!MD5
입니다.
버전 0.8.19부터 기본 SSL 암호는 ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM
입니다.
버전 0.7.64, 0.8.18 및 이전 버전부터 기본 SSL 암호는 ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP
입니다.