Issuing a Certificate From a Private CA and Applying It to nginx
한국어 원문으로 표시합니다.
목표
사설 CA 를 만들어 SAN 이 포함된 서버 인증서를 발급하고, 체인 파일을 구성해 nginx 에 HTTPS 를 적용하고, 만료 감시 스크립트까지 만들 수 있게 됩니다.
왜 중요한가
인증서 장애의 대부분은 암호학이 아니라 체인 누락과 만료 두 가지입니다. 중간 인증서를 빼먹으면 브라우저에서는 잘 되는데 연동 상대 시스템에서만 실패합니다. 브라우저는 중간 인증서를 캐시하거나 알아서 받아 오지만, 서버 간 HTTP 클라이언트는 그러지 않기 때문입니다. "개발자 PC 에서는 되는데요"는 이 증상의 전형입니다. 그리고 만료는 예고 없이 오지 않는데도 담당자 교체와 알림 누락으로 매년 사고가 납니다. 사설 CA 로 한 번 직접 만들어 보면 이 구조가 손에 남습니다.
단계
/root/ng/tls디렉터리를 만들고 RSA 2048비트 개인키를/root/ng/tls/server.key로 생성합니다.- 그 키로 CSR 을 만들어
/root/ng/tls/server.csr로 저장합니다. 주체는CN=labhub.local,O=LabHub,C=KR을 포함해야 합니다. - 사설 CA 를 만듭니다.
/root/ng/tls/ca.key와/root/ng/tls/ca.crt. CA 인증서의 주체는CN=LabHub Root CA이고 유효기간은 3650일 이상,basicConstraints에CA:TRUE가 있어야 합니다. - CA 로 CSR 에 서명해
/root/ng/tls/server.crt를 만듭니다. SAN 에DNS:labhub.local과IP:127.0.0.1이 모두 들어가야 하고, 발급자(issuer)가 CA 의 주체와 같아야 합니다. /root/ng/tls/fullchain.pem을 만듭니다. 서버 인증서 → CA 인증서 순서로 이어 붙이며, 파일 안에 인증서가 정확히 2개여야 합니다.openssl verify -CAfile ca.crt server.crt가 성공해야 합니다.- nginx 에 8443 포트 HTTPS 서버 블록을 추가합니다.
ssl_certificate는 fullchain,ssl_certificate_key는 server.key,ssl_protocols는TLSv1.2 TLSv1.3만 허용합니다.openssl s_client -connect 127.0.0.1:8443 -CAfile /root/ng/tls/ca.crt의 결과에Verify return code: 0이 나와야 합니다. - 8088 포트의 모든 요청이
https://로 301 리다이렉트되게 합니다.curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://127.0.0.1:8088/x결과가301이고 리다이렉트 URL 이https://로 시작하며8443을 포함해야 합니다. /root/ng/certcheck.sh를 만듭니다. 인자 두 개(인증서파일 임계일수)를 받아 남은 유효기간이 임계일수보다 많으면 종료코드 0, 임계일수 이내로 임박했으면 0 이 아닌 종료코드로 끝냅니다. 인증서 파일이 없으면 역시 0 이 아닌 종료코드로 끝나야 합니다.
참고
- 키 생성:
openssl genrsa -out server.key 2048 - CSR:
openssl req -new -key server.key -out server.csr -subj "/C=KR/O=LabHub/CN=labhub.local" - 자체 서명 CA:
openssl req -x509 -new -key ca.key -days 3650 -out ca.crt -subj "..."(CA 확장은-addext "basicConstraints=critical,CA:TRUE"로 줄 수 있습니다) - 서명 시 SAN 주입: 확장 파일을 만들어
openssl x509 -req ... -extfile <파일> - 만료 확인:
openssl x509 -in <파일> -noout -checkend <초> - 흔한 실수 1: SAN 없이 CN 만 넣고 "왜 브라우저가 거부하지"를 반복하는 것.
- 흔한 실수 2: fullchain 의 순서를 CA 먼저로 만드는 것. 서버 인증서가 먼저입니다.
- 흔한 실수 3:
-checkend의 인자를 '일'로 주는 것. 초 단위입니다.
서버 개인키 생성
/root/ng/tls 디렉터리를 만들고 RSA 2048비트 개인키를
/root/ng/tls/server.key 로 생성합니다.
RSA 2048비트가 아직 표준적인 선택입니다. 생성한 키가 정상인지 확인하는 openssl 서브커맨드도 함께 익혀 두세요.
CSR 생성
그 키로 CSR 을 만들어 /root/ng/tls/server.csr 로 저장합니다.
주체는 CN=labhub.local, O=LabHub, C=KR 을 포함해야 합니다.
CSR 의 주체 정보는 -subj 로 한 번에 줄 수 있습니다. 국가 코드는 두 글자 대문자입니다.
사설 CA 만들기
사설 CA 를 만듭니다. /root/ng/tls/ca.key 와 /root/ng/tls/ca.crt.
CA 인증서의 주체는 CN=LabHub Root CA 이고 유효기간은 3650일 이상,
basicConstraints 에 CA:TRUE 가 있어야 합니다.
CA 인증서는 일반 서버 인증서와 달라야 합니다. 어떤 확장이 CA 임을 표시하는지 확인하세요. openssl req 에 -x509 를 주면 자체 서명 인증서가 나옵니다.
CA 로 서버 인증서 서명
CA 로 CSR 에 서명해 /root/ng/tls/server.crt 를 만듭니다.
SAN 에 DNS:labhub.local 과 IP:127.0.0.1 이 모두 들어가야 하고,
발급자(issuer)가 CA 의 주체와 같아야 합니다.
요즘 브라우저와 라이브러리는 CN 이 아니라 SAN 을 봅니다. openssl x509 로 서명할 때 확장을 파일로 넘겨야 SAN 이 들어갑니다.
체인 파일 구성
/root/ng/tls/fullchain.pem 을 만듭니다.
서버 인증서 → CA 인증서 순서로 이어 붙이며, 파일 안에 인증서가 정확히 2개여야 합니다.
openssl verify -CAfile ca.crt server.crt 가 성공해야 합니다.
서버 인증서와 CA 인증서를 이어 붙입니다. 순서가 중요합니다. 만든 뒤에는 검증 명령으로 실제로 신뢰 경로가 서는지 확인하세요.
nginx HTTPS 적용
nginx 에 8443 포트 HTTPS 서버 블록을 추가합니다.
ssl_certificate 는 fullchain, ssl_certificate_key 는 server.key,
ssl_protocols 는 TLSv1.2 TLSv1.3 만 허용합니다.
openssl s_client -connect 127.0.0.1:8443 -CAfile /root/ng/tls/ca.crt 의 결과에
Verify return code: 0 이 나와야 합니다.
인증서와 키가 짝이 아니면 nginx 는 기동조차 안 됩니다. 프로토콜은 폐기된 버전을 빼고 지정하세요.
HTTP → HTTPS 리다이렉트
8088 포트의 모든 요청이 https:// 로 301 리다이렉트되게 합니다.
curl -s -o /dev/null -w '%{http_code} %{redirect_url}' http://127.0.0.1:8088/x
결과가 301 이고 리다이렉트 URL 이 https:// 로 시작하며 8443 을 포함해야 합니다.
rewrite 보다 return 이 명확하고 빠릅니다. 상태코드와 Location 헤더가 모두 맞아야 합니다.
만료 감시 스크립트
/root/ng/certcheck.sh 를 만듭니다. 인자 두 개(인증서파일 임계일수)를 받아
남은 유효기간이 임계일수보다 많으면 종료코드 0,
임계일수 이내로 임박했으면 0 이 아닌 종료코드로 끝냅니다.
인증서 파일이 없으면 역시 0 이 아닌 종료코드로 끝나야 합니다.
openssl x509 에는 '남은 유효기간이 N초 이상인가'를 종료코드로 알려 주는 옵션이 있습니다. 감시 스크립트는 이걸 쓰면 날짜 파싱이 필요 없습니다.