HTTPS 서버 구성: SSL 터미네이션과 성능 최적화
HTTPS 서버 구성: SSL 터미네이션과 성능 최적화
클라이언트와 NGINX 사이의 트래픽을 TLS로 암호화해 받는 것을 HTTPS 서버 구성이라고 해요. NGINX 앞에서 TLS를 끝내는 방식(SSL 터미네이션)을 쓰면 백엔드 서버는 암호화를 신경 쓰지 않아도 돼요. 인증서를 지정하는 기본 설정에서부터 SSL 세션 캐시 같은 성능 최적화, 여러 도메인을 처리하는 SNI까지 함께 살펴볼게요.
기본 HTTPS 서버
HTTPS 서버를 구성하려면 server 블록의 리슨 소켓에 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.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
...
}
서버 인증서는 공개적인 개체라서 서버에 접속하는 모든 클라이언트에게 보내져요. 반면 개인 키는 비공개 개체라서 접근 권한이 제한된 파일에 저장해야 해요. 다만 NGINX의 마스터 프로세스는 이를 읽을 수 있어야 합니다.
ssl_certificate www.example.com.cert;
ssl_certificate_key www.example.com.cert;
인증서와 키를 한 파일에 넣을 수도 있는데, 이 경우에도 파일 접근 권한을 제한해야 해요. 두 개가 한 파일에 있어도 클라이언트에는 인증서만 전송됩니다.
ssl_protocols 와 ssl_ciphers 지시어로 강력한 버전·암호화 방식만 허용하도록 연결을 제한할 수 있어요. 다만 NGINX의 기본값이 이미 ssl_protocols TLSv1.2 TLSv1.3 과 ssl_ciphers HIGH:!aNULL:!MD5 이라서, 명시적으로 지정하지 않아도 보통은 충분해요.
HTTPS 서버 성능 최적화
SSL 연산은 추가 CPU 자원을 소모해요. 멀티프로세서 시스템에서는 가용 CPU 코어 수 이상으로 워커 프로세스를 돌리는 게 좋아요. 가장 CPU 집약적인 연산은 SSL 핸드셰이크인데, 클라이언트당 이 연산 횟수를 줄이는 방법이 두 가지 있어요.
첫째는 keepalive 연결을 켜서 하나의 연결로 여러 요청을 보내는 것이고, 둘째는 SSL 세션 파라미터 재사용으로 병렬·이후 연결에서 핸드셰이크를 피하는 거예요. 세션은 워커 간에 공유되는 SSL 세션 캐시에 저장되고, ssl_session_cache 지시어로 구성해요. 캐시 1메가바이트는 약 4000개의 세션을 담을 수 있어요. 기본 캐시 타임아웃은 5분인데, ssl_session_timeout 지시어로 늘릴 수 있어요.
멀티코어 시스템에서 10메가바이트 공유 세션 캐시를 쓰도록 최적화한 예시 설정을 볼게요.
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.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
...
}
}
worker_processes auto 로 CPU 코어 수에 맞춰 워커를 자동 조정하고, keepalive_timeout 70 으로 연결 재사용을 돕는 구성이에요.
인증서 체인 (certificate chain)
상위 인증기관(CA)이 서명한 인증서라면 참조하는 루트·중간 인증서들을 한 파일로 묶은 체인(번들)이 필요할 수 있어요. cat 으로 인증서와 번들을 연결해 체인 파일을 만들 수 있어요.
$ 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:05800074:x509 certificate routines::key values mismatch)
HTTP와 HTTPS를 함께 처리하는 서버
80 포트와 443 포트를 한 server 블록에 함께 넣을 수도 있어요.
server {
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
ssl_certificate_key www.example.com.key;
...
}
이름 기반 HTTPS 서버
server_name 만 다른 여러 HTTPS 서버를 정의할 수 있어요. 다만 HTTPS는 SNI(Server Name Indication)가 있어야 어떤 인증서를 내줄지 정할 수 있어요.
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;
...
}
SNI를 쓰려면 NGINX 바이너리가 빌드된 OpenSSL 라이브러리와 런타임에 동적 연결되는 라이브러리가 모두 SNI를 지원해야 해요. OpenSSL은 --enable-tlsext 옵션으로 빌드했다면 0.9.8f 버전부터 SNI를 지원합니다.