| 종류 | 발급자 | 브라우저 신뢰 | 주 용도 |
|---|---|---|---|
| 공인 인증서 | 공인 CA | 자동 신뢰 | 외부 공개 서비스 |
| 사내 CA 인증서 | 사내 구축 CA | 단말에 루트 CA 배포 필요 | 사내 다수 서버 |
| 자체 서명 인증서 | 서버 자신 | 브라우저 경고 표시 | 내부망·테스트 |
이 레슨은 세 번째인 자체 서명 인증서를 실제로 만듭니다. 사내 CA 나 공인 인증서를 받아도 nginx 에 배치하고 설정하는 방식은 동일합니다.
개인 키(private key)는 openssl genrsa 로 만들고, 이 키로 서명 요청서인 CSR(Certificate Signing Request)을 openssl req -new 로 만듭니다. CSR 에는 도메인 이름(CN)과 조직 정보가 들어갑니다.
-subj 옵션으로 값을 고정하면 대화형 질문 없이 자동 실행됩니다. 재현 가능한 스크립트를 만들 때 필수입니다.
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
-subj "/C=KR/ST=Seoul/L=Seoul/O=StudyCo/OU=IT/CN=app.example.local"공인 CA 에 CSR 을 보내는 대신, 같은 키로 CSR 에 직접 서명하면 자체 서명 인증서가 됩니다. openssl x509 -req -signkey 가 이 역할을 합니다.
일련번호를 지정하지 않으면 실행할 때마다 난수로 바뀌어 출력이 재현되지 않습니다. -set_serial 로 고정합니다.
openssl x509 -req -in server.csr -signkey server.key \
-days 3650 -set_serial 0x1000 -out server.crt인증서·키는 $BASE/nginx/ssl 아래에 둡니다. 개인 키는 유출되면 위장 서버를 만들 수 있어 소유자만 읽도록 권한 600 으로 제한합니다.
인증서 파일(server.crt)은 공개해도 되는 정보라 644 로 둬도 됩니다. 둘을 같은 폴더에 두되 권한만 다르게 관리합니다.
HTTPS 를 받는 server 블록은 listen 443 ssl http2; 로 시작하고, 인증서·키 경로를 지정합니다. http2 는 같은 포트에서 HTTP/2 도 함께 처리합니다.
server {
listen 443 ssl http2;
server_name app.example.local;
ssl_certificate /app/nginx/ssl/server.crt;
ssl_certificate_key /app/nginx/ssl/server.key;
}ssl_protocols TLSv1.2 TLSv1.3; 로 오래된 프로토콜(SSLv3, TLSv1.0, TLSv1.1)을 막습니다. 취약점이 알려진 구버전을 허용하면 다운그레이드 공격에 노출됩니다.
ssl_ciphers 는 허용할 암호화 방식 목록입니다. HIGH:!aNULL:!MD5 처럼 안전도가 낮은 방식을 제외하는 표기를 씁니다.
TLS 는 처음 연결할 때 키 교환 비용이 큽니다. ssl_session_cache shared:SSL:10m; 으로 세션 정보를 캐시해 두면 재접속 시 이 비용을 건너뜁니다.
shared 는 여러 워커 프로세스가 캐시를 함께 쓴다는 뜻이고, 10m 은 캐시 크기입니다. ssl_session_timeout 으로 캐시 유효 시간도 함께 정합니다.
80 번 포트로 온 요청을 443 으로 넘기려면 return 301 https://$host$request_uri; 를 씁니다. 301 은 영구 이동을 뜻해 브라우저가 다음부터는 바로 https 로 요청합니다.
server {
listen 80;
server_name app.example.local;
return 301 https://$host$request_uri;
}리다이렉트만으로는 처음 한 번은 여전히 평문 http 요청이 나갑니다. Strict-Transport-Security 응답 헤더를 주면 브라우저가 이후 요청을 아예 https 로만 보냅니다.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;max-age 는 초 단위 유지 기간이고, always 는 오류 응답에도 헤더를 붙이라는 뜻입니다. 한 번 켜면 만료 전까지 http 로 되돌리기 어려우니 인증서가 안정된 뒤에 켭니다.
nginx 가 TLS 를 종료하면 Tomcat 입장에서는 모든 요청이 평문 http 로 보입니다. proxy_set_header X-Forwarded-Proto $scheme; 로 원래 스킴을 알려줍니다.
Tomcat 쪽은 server.xml 에 RemoteIpValve 를 등록해야 이 헤더를 신뢰하고 request.isSecure() 나 리다이렉트 로직이 올바르게 동작합니다. 등록 방법은 레슨 02 를 참조합니다.
openssl x509 -in server.crt -noout -enddate 로 만료일만 뽑아 볼 수 있습니다. 만료가 가까우면 새 키·인증서를 만들어 같은 파일 이름으로 덮어씁니다.
교체 순서는 파일을 바꾸고, nginx -t 로 문법을 확인하고, systemctl reload 로 반영하는 순서입니다. reload 는 기존 연결을 끊지 않아 서비스 중단 없이 교체됩니다.
공인 인증서는 서버 인증서 하나만으로 부족하고 중간(intermediate) 인증서가 필요할 때가 많습니다. ssl_certificate 파일에는 서버 인증서를 맨 위에, 중간 인증서를 그 아래에 이어 붙입니다.
순서가 뒤집히면 일부 브라우저는 열리지만 모바일 앱이나 오래된 클라이언트는 인증서 검증에 실패합니다. 루트(root) 인증서는 체인 파일에 넣지 않습니다.