- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 1. PEM, Key, CRT 파일 형식 이해
- 2. Nginx 기본 TLS 설정
- 3. TLS 버전 설정
- 4. 암호화 스위트 설정
- 5. DH 파라미터
- 6. HSTS (HTTP Strict Transport Security)
- 7. OCSP Stapling
- 8. SSL 세션 캐싱
- 9. HTTP/2 설정
- 10. mTLS (Mutual TLS) 설정
- 11. 자체 서명 인증서 생성 (개발용)
- 12. 인증서 변환
- 13. 인증서 검증 명령어
- 14. 보안 점검 도구
- 15. Let's Encrypt 자동 갱신 연동
- 16. 설정 체크리스트
- 17. 결론
1. PEM, Key, CRT 파일 형식 이해
1.1 PEM (Privacy Enhanced Mail)
PEM은 Base64로 인코딩된 인증서/키 형식이다. 텍스트 에디터로 열어볼 수 있다.
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
... (Base64 인코딩된 데이터)
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7o4qne60TB3pO
... (Base64 인코딩된 데이터)
-----END PRIVATE KEY-----
1.2 DER (Distinguished Encoding Rules)
바이너리 형식이다. PEM에서 Base64 인코딩을 제거한 것과 동일하다.
1.3 키 타입
| 알고리즘 | 키 크기 | 성능 | 보안 | 권장 |
|---|---|---|---|---|
| RSA 2048 | 2048 bit | 느림 | 충분 | 호환성 중시 |
| RSA 4096 | 4096 bit | 매우 느림 | 높음 | 높은 보안 필요 시 |
| ECDSA P-256 | 256 bit | 빠름 | 충분 | 권장 (Modern) |
| ECDSA P-384 | 384 bit | 빠름 | 높음 | 높은 보안 + 성능 |
ECDSA는 RSA 대비 동일 보안 수준에서 키 크기가 훨씬 작아 TLS 핸드셰이크 성능이 우수하다.
1.4 Let's Encrypt 발급 파일 매핑
/etc/letsencrypt/live/example.com/
fullchain.pem = 서버 인증서 + 중간 CA 인증서
privkey.pem = 개인키
chain.pem = 중간 CA 인증서만
cert.pem = 서버 인증서만
Nginx에서 사용하는 파일:
ssl_certificate -> fullchain.pem
ssl_certificate_key -> privkey.pem
2. Nginx 기본 TLS 설정
2.1 최소 설정
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;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
2.2 HTTP에서 HTTPS로 리다이렉트
server {
listen 80;
server_name example.com www.example.com;
# ACME 챌린지용 (Let's Encrypt 갱신)
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# 나머지 모든 요청을 HTTPS로 리다이렉트
location / {
return 301 https://$host$request_uri;
}
}
2.3 완전한 프로덕션 설정 예제
# /etc/nginx/conf.d/ssl-params.conf
# 공통 SSL 파라미터 (모든 서버 블록에서 include)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# DH 파라미터
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
# HSTS
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# SSL 세션 캐싱
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# 보안 헤더
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# /etc/nginx/sites-available/example.com
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
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;
include /etc/nginx/conf.d/ssl-params.conf;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# 정적 파일 캐싱
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
3. TLS 버전 설정
3.1 TLS 버전별 보안 상태
| 버전 | 상태 | 권장 |
|---|---|---|
| SSL 2.0 | 폐기 (1996년) | 절대 사용 금지 |
| SSL 3.0 | 폐기 (POODLE 취약점) | 절대 사용 금지 |
| TLS 1.0 | 폐기 (2021년) | 비활성화 |
| TLS 1.1 | 폐기 (2021년) | 비활성화 |
| TLS 1.2 | 현재 표준 | 사용 |
| TLS 1.3 | 최신 표준 | 사용 (권장) |
3.2 TLS 1.2/1.3만 허용
# TLS 1.2와 1.3만 허용 (권장)
ssl_protocols TLSv1.2 TLSv1.3;
3.3 TLS 1.3 전용 (Modern)
# TLS 1.3만 허용 (최신 클라이언트만 지원)
ssl_protocols TLSv1.3;
TLS 1.3의 장점:
- 핸드셰이크 라운드 트립 감소 (1-RTT, 0-RTT)
- 취약한 레거시 암호화 스위트 제거
- 전방 보안(Forward Secrecy) 기본 적용
- 핸드셰이크 암호화 (서버 인증서 포함)
4. 암호화 스위트 설정
4.1 Mozilla SSL Configuration Generator 프로파일
Mozilla는 세 가지 구성 프로파일을 제공한다.
Modern (TLS 1.3 전용):
ssl_protocols TLSv1.3;
# TLS 1.3은 별도 cipher 설정 불필요 (프로토콜에 내장)
ssl_prefer_server_ciphers off;
Intermediate (TLS 1.2 + 1.3, 권장):
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
Old (레거시 호환, 비권장):
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA;
ssl_prefer_server_ciphers on;
4.2 암호화 스위트 이름 해석
ECDHE-RSA-AES256-GCM-SHA384 의 의미:
ECDHE = 키 교환 알고리즘 (Elliptic Curve Diffie-Hellman Ephemeral)
→ Forward Secrecy 제공
RSA = 인증 알고리즘 (서버 인증서 서명)
AES256 = 대칭 암호화 (256비트 AES)
GCM = 암호화 모드 (Galois/Counter Mode, AEAD)
SHA384 = 해시 함수 (무결성 검증)
4.3 ssl_prefer_server_ciphers
# TLS 1.2: on으로 설정하면 서버가 선호하는 cipher를 우선 사용
# TLS 1.3: 클라이언트가 결정 (이 설정 무시됨)
# Mozilla Intermediate에서는 off 권장
ssl_prefer_server_ciphers off;
5. DH 파라미터
Diffie-Hellman 키 교환에 사용되는 파라미터다. DHE 암호화 스위트를 사용할 때 필요하다.
# DH 파라미터 생성 (2048비트 이상 권장)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
# 더 강력한 4096비트 (생성 시간이 오래 걸림)
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
참고: TLS 1.3에서는 DHE 대신 ECDHE만 사용하므로 DH 파라미터가 필요 없다. TLS 1.2의 DHE 암호화 스위트를 제거하면 DH 파라미터도 불필요하다.
6. HSTS (HTTP Strict Transport Security)
6.1 HSTS란
HSTS는 브라우저에게 해당 도메인에 항상 HTTPS로 접속하도록 지시하는 HTTP 헤더다.
# max-age: HSTS 유지 기간 (초 단위, 63072000 = 2년)
# includeSubDomains: 모든 서브도메인에도 적용
# preload: 브라우저 Preload List 등록 의사 표시
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
6.2 HSTS Preload List
HSTS Preload List에 등록하면 브라우저가 첫 접속부터 HTTPS를 강제한다.
등록 조건:
- 유효한 TLS 인증서
- 포트 80에서 443으로 리다이렉트
- 모든 서브도메인에 HTTPS 적용
- HSTS 헤더에 max-age 최소 31536000(1년), includeSubDomains, preload 포함
등록 사이트: hstspreload.org
6.3 주의사항
- HSTS를 설정하면 되돌리기 어려움 (max-age 동안 브라우저가 HTTP 접속 차단)
- 처음에는 작은 max-age (예: 300초)로 시작해서 점진적으로 늘리는 것을 권장
- 모든 서브도메인이 HTTPS를 지원하는지 확인 후 includeSubDomains 추가
7. OCSP Stapling
7.1 OCSP Stapling이란
OCSP(Online Certificate Status Protocol)는 인증서가 폐기(revoke)되었는지 확인하는 프로토콜이다. OCSP Stapling은 서버가 CA에서 OCSP 응답을 미리 받아서 TLS 핸드셰이크 시 클라이언트에게 전달하는 기술이다.
OCSP Stapling 없이:
Client → Server (TLS 핸드셰이크)
Client → CA OCSP Server (인증서 유효성 확인) ← 추가 지연
OCSP Stapling 있을 때:
Server → CA OCSP Server (주기적으로 OCSP 응답 갱신)
Client → Server (TLS 핸드셰이크 + OCSP 응답 포함) ← 빠름
7.2 설정
ssl_stapling on;
ssl_stapling_verify on;
# 중간 CA 인증서 (chain.pem)
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# OCSP 응답 서버의 DNS 해석용 리졸버
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
7.3 OCSP Stapling 확인
# OCSP Stapling 동작 확인
openssl s_client -connect example.com:443 -servername example.com -status < /dev/null 2>/dev/null | grep -A 20 "OCSP Response"
# 정상 응답 예시:
# OCSP Response Status: successful (0x0)
# OCSP Response Data:
# OCSP Response Status: successful (0x0)
# Cert Status: good
8. SSL 세션 캐싱
8.1 세션 캐시
TLS 핸드셰이크는 비용이 크다. 세션 캐시를 사용하면 재연결 시 전체 핸드셰이크를 생략할 수 있다.
# 공유 메모리 캐시 (여러 worker 프로세스 간 공유)
# 10MB 캐시 = 약 40,000 세션 저장
ssl_session_cache shared:SSL:10m;
# 세션 유효 기간
ssl_session_timeout 1d;
# 세션 티켓 비활성화 (Forward Secrecy 보장을 위해)
ssl_session_tickets off;
8.2 왜 Session Tickets를 비활성화하는가
Session Tickets는 서버 메모리 대신 암호화된 티켓을 클라이언트에 저장한다. 문제점:
- 티켓 암호화 키가 모든 세션의 보안을 좌우
- 키 로테이션이 제대로 되지 않으면 Forward Secrecy가 깨짐
- TLS 1.3에서는 0-RTT 재연결로 대체 가능
9. HTTP/2 설정
9.1 HTTP/2 활성화
server {
listen 443 ssl http2;
server_name example.com;
# ... SSL 설정 ...
}
HTTP/2의 장점:
- 멀티플렉싱: 하나의 TCP 연결에서 여러 요청을 동시 처리
- 헤더 압축: HPACK으로 HTTP 헤더 압축
- 서버 푸시: 클라이언트가 요청하기 전에 리소스 전송
- 우선순위: 리소스별 전송 우선순위 설정
9.2 HTTP/3 (QUIC) 준비
# Nginx 1.25.0+ (또는 nginx-quic)
server {
listen 443 ssl;
listen 443 quic reuseport;
http2 on;
http3 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# QUIC 광고 헤더
add_header Alt-Svc 'h3=":443"; ma=86400' always;
}
10. mTLS (Mutual TLS) 설정
10.1 mTLS란
일반 TLS는 서버만 인증서를 제시하지만, mTLS는 클라이언트도 인증서를 제시하여 양방향 인증을 수행한다.
일반 TLS:
Client ──── 서버 인증서 확인 ───> Server
Mutual TLS:
Client <─── 서버 인증서 확인 ───> Server
Client ──── 클라이언트 인증서 ──> Server (서버가 클라이언트 인증서 확인)
사용 사례:
- 마이크로서비스 간 통신 (서비스 메시)
- API 인증 (API Gateway)
- IoT 디바이스 인증
- 내부 관리 도구 접근 제어
10.2 CA 인증서로 클라이언트 인증서 발급
# 1. CA 개인키 생성
openssl genrsa -out ca.key 4096
# 2. CA 인증서 생성 (자체 서명)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/C=KR/ST=Seoul/O=MyOrg/CN=Internal CA"
# 3. 클라이언트 개인키 생성
openssl genrsa -out client.key 2048
# 4. 클라이언트 CSR 생성
openssl req -new -key client.key -out client.csr \
-subj "/C=KR/ST=Seoul/O=MyOrg/CN=service-a"
# 5. CA로 클라이언트 인증서 서명
openssl x509 -req -days 365 -in client.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt
# 6. PFX/PKCS12 번들 생성 (브라우저 임포트용)
openssl pkcs12 -export -out client.pfx \
-inkey client.key -in client.crt -certfile ca.crt
10.3 Nginx mTLS 설정
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/server.fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/server.privkey.pem;
# 클라이언트 인증서 검증
ssl_client_certificate /etc/nginx/ssl/ca.crt;
ssl_verify_client on; # on: 필수, optional: 선택적
ssl_verify_depth 2; # 인증서 체인 검증 깊이
# CRL (Certificate Revocation List) 설정
# ssl_crl /etc/nginx/ssl/ca.crl;
# 클라이언트 인증서 정보를 백엔드로 전달
location / {
proxy_pass http://backend;
proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn;
proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
proxy_set_header X-SSL-Client-Serial $ssl_client_serial;
}
}
10.4 ssl_verify_client 옵션
# on: 클라이언트 인증서 필수 (없으면 400 에러)
ssl_verify_client on;
# optional: 인증서 선택적 (없어도 접속 가능, 인증서가 있으면 검증)
ssl_verify_client optional;
# optional_no_ca: 인증서 검증 없이 전달만 (백엔드에서 검증)
ssl_verify_client optional_no_ca;
10.5 mTLS 테스트
# 클라이언트 인증서로 요청
curl --cert client.crt --key client.key \
--cacert ca.crt \
https://api.example.com/data
# 인증서 없이 요청 (ssl_verify_client on이면 실패)
curl --cacert ca.crt https://api.example.com/data
# 400 Bad Request - No required SSL certificate was sent
11. 자체 서명 인증서 생성 (개발용)
11.1 단일 도메인
# 개인키 + 자체 서명 인증서 한 번에 생성
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout selfsigned.key \
-out selfsigned.crt \
-days 365 \
-subj "/C=KR/ST=Seoul/O=Dev/CN=localhost"
11.2 SAN (Subject Alternative Name) 포함
여러 도메인이나 IP를 포함하는 인증서:
# SAN 설정 파일 생성
cat > san.cnf << 'SANEOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = KR
ST = Seoul
O = Dev
CN = localhost
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = myapp.local
DNS.3 = *.myapp.local
IP.1 = 127.0.0.1
IP.2 = 192.168.1.100
SANEOF
# 자체 서명 인증서 생성
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout selfsigned.key \
-out selfsigned.crt \
-days 365 \
-config san.cnf \
-extensions v3_req
11.3 ECDSA 자체 서명 인증서
# ECDSA P-256 키 생성
openssl ecparam -genkey -name prime256v1 -out ecdsa.key
# 자체 서명 인증서
openssl req -x509 -new -key ecdsa.key \
-out ecdsa.crt \
-days 365 \
-subj "/C=KR/ST=Seoul/O=Dev/CN=localhost"
12. 인증서 변환
12.1 PEM에서 DER로
# 인증서 변환
openssl x509 -in cert.pem -outform DER -out cert.der
# 개인키 변환
openssl rsa -in key.pem -outform DER -out key.der
12.2 DER에서 PEM으로
openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem
12.3 PFX/PKCS12에서 PEM으로
# PFX에서 인증서 추출
openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out cert.pem
# PFX에서 개인키 추출
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out key.pem
# PFX에서 CA 체인 추출
openssl pkcs12 -in certificate.pfx -cacerts -nokeys -out chain.pem
12.4 PEM에서 PFX/PKCS12로
openssl pkcs12 -export \
-out certificate.pfx \
-inkey key.pem \
-in cert.pem \
-certfile chain.pem
13. 인증서 검증 명령어
13.1 인증서 내용 확인
# 인증서 전체 정보
openssl x509 -in cert.pem -text -noout
# 발급자 정보
openssl x509 -in cert.pem -issuer -noout
# 주체 정보
openssl x509 -in cert.pem -subject -noout
# 만료일
openssl x509 -in cert.pem -enddate -noout
# SAN (Subject Alternative Names)
openssl x509 -in cert.pem -noout -ext subjectAltName
# 시리얼 번호
openssl x509 -in cert.pem -serial -noout
13.2 원격 서버 인증서 확인
# 서버 인증서 조회
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
openssl x509 -text -noout
# 인증서 체인 확인
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>/dev/null
# TLS 버전 및 cipher 확인
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
grep -E "Protocol|Cipher"
13.3 인증서-키 매칭 확인
# 인증서와 키의 modulus 비교 (같아야 매칭)
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in key.pem | openssl md5
# 두 값이 동일하면 인증서와 키가 매칭
14. 보안 점검 도구
14.1 SSL Labs
온라인으로 TLS 설정을 점검하는 서비스다.
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
등급 기준:
A+ = 최상 (HSTS + 강력한 설정)
A = 우수
B = 양호 (개선 권장)
C = 미흡 (보안 위험)
F = 위험 (즉시 조치 필요)
14.2 testssl.sh
커맨드라인에서 TLS 설정을 점검하는 오픈소스 도구다.
# 설치
git clone --depth 1 https://github.com/drwetter/testssl.sh.git
# 전체 점검
./testssl.sh/testssl.sh example.com
# 특정 항목만 점검
./testssl.sh/testssl.sh --protocols example.com
./testssl.sh/testssl.sh --ciphers example.com
./testssl.sh/testssl.sh --headers example.com
./testssl.sh/testssl.sh --vulnerabilities example.com
14.3 Nginx 설정 검증
# Nginx 설정 문법 검사
sudo nginx -t
# Nginx 설정 전체 출력
sudo nginx -T
# Gixy (Nginx 보안 분석 도구)
pip install gixy
gixy /etc/nginx/nginx.conf
15. Let's Encrypt 자동 갱신 연동
15.1 Certbot + Nginx Reload Hook
# certbot 갱신 후 Nginx reload
sudo certbot renew --deploy-hook "systemctl reload nginx"
# 또는 renewal 설정 파일에 직접 추가
# /etc/letsencrypt/renewal/example.com.conf
# [renewalparams]
# ...
# renew_hook = systemctl reload nginx
15.2 Docker Compose에서 자동 갱신
version: '3.8'
services:
nginx:
image: nginx:latest
ports:
- '80:80'
- '443:443'
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
restart: unless-stopped
command: '/bin/sh -c ''while :; do sleep 6h & wait; nginx -s reload; done & nginx -g "daemon off;"'''
certbot:
image: certbot/certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait; done'"
16. 설정 체크리스트
프로덕션 배포 전 확인해야 할 항목:
| 항목 | 명령어 / 확인 방법 | 기준 |
|---|---|---|
| TLS 버전 | ssl_protocols | TLSv1.2 TLSv1.3만 |
| 인증서 체인 | fullchain.pem 사용 | 중간 CA 포함 |
| HSTS | 헤더 확인 | max-age 1년 이상 |
| OCSP Stapling | openssl s_client -status | Cert Status: good |
| HTTP 리다이렉트 | curl -I http://domain | 301 to HTTPS |
| SSL Labs 등급 | ssllabs.com | A+ 목표 |
| 개인키 권한 | ls -la privkey.pem | 600 (root only) |
| 자동 갱신 | certbot renew --dry-run | 성공 |
| DH 파라미터 | 2048비트 이상 | 생성 완료 |
| Session Tickets | off | Forward Secrecy |
17. 결론
Nginx TLS 설정은 단순히 인증서를 적용하는 것을 넘어서 종합적인 보안 최적화가 필요하다. 핵심 정리:
- fullchain.pem + privkey.pem을 사용하여 인증서 체인을 완성
- TLS 1.2/1.3만 허용하고 레거시 프로토콜 비활성화
- Mozilla Intermediate 프로파일의 암호화 스위트 권장
- HSTS로 HTTPS 접속 강제, OCSP Stapling으로 성능 향상
- SSL 세션 캐싱으로 핸드셰이크 오버헤드 감소
- HTTP/2로 웹 성능 최적화
- mTLS로 마이크로서비스 간 양방향 인증 구현
- SSL Labs에서 A+ 등급을 목표로 설정 최적화
- Let's Encrypt 인증서 자동 갱신을 반드시 설정