반응형
Linux 사설 SSL 인증서 작업에서 자주 발생하는 오류와 해결 방법
사설 CA 기반 SSL은 “만드는 것”보다 적용·배포·검증에서 더 자주 문제가 납니다.
이 글은 사설 인증서를 만들고 적용하는 과정에서 반복적으로 터지는 오류를 증상 → 원인 → 확인 명령 → 해결 순서로 정리합니다.
실제 사용 시, 관리자 입장에서 장애 시간을 줄이려면 “에러 메시지 하나를 보면 어디를 의심해야 하는지”가 바로 떠올라야 합니다.
장애 대응 기본 도구 세트
아래 3개만 제대로 쓰면, 대부분의 TLS 오류는 원인까지 바로 좁혀집니다.
- openssl s_client: 서버가 실제로 내보내는 인증서/체인/검증 결과 확인
- openssl x509/req: 파일 자체(인증서/CSR)의 SAN/용도/issuer 확인
- curl -v / -I: 애플리케이션 요청이 실제로 성공하는지 확인
- openssl s_client: 서버가 실제로 내보내는 인증서/체인/검증 결과 확인
- openssl x509/req: 파일 자체(인증서/CSR)의 SAN/용도/issuer 확인
- curl -v / -I: 애플리케이션 요청이 실제로 성공하는지 확인
# 서버가 내보내는 인증서/체인 확인(SNI 포함)
openssl s_client -connect example.internal:443 -servername example.internal -showcerts
# 로컬에서 특정 CA로 검증 강제(신뢰 저장소 반영 전 테스트)
openssl s_client -connect example.internal:443 -servername example.internal \
-CAfile /path/to/rootCA.crt -verify_return_error
# 인증서/CSR에서 SAN/용도/issuer 확인
openssl x509 -in /path/to/server.crt -noout -text | sed -n '1,220p'
openssl req -in /path/to/server.csr -noout -text | sed -n '1,220p'
# curl로 실제 요청(신뢰 저장소 미반영이면 --cacert 사용)
curl -Iv https://example.internal/
curl -Iv --cacert /path/to/rootCA.crt https://example.internal/
오류 1) certificate verify failed / unable to get local issuer certificate
대표 증상
curl: (60) SSL certificate problem: unable to get local issuer certificate
주요 원인
- 클라이언트가 사설 루트 CA를 신뢰하지 않음(가장 흔함)
- 서버가 중간 CA 체인을 내보내지 않음(중간 CA 구조일 때)
- 클라이언트가 바라보는 CA 파일이 잘못됐거나(다른 루트), 오래된 CA를 신뢰 중
빠른 확인
# 서버가 어떤 체인을 내보내는지 확인
openssl s_client -connect example.internal:443 -servername example.internal -showcerts
# 로컬에서 루트 CA를 직접 지정해 검증해보기
openssl s_client -connect example.internal:443 -servername example.internal \
-CAfile /root/pki/ca/rootCA.crt -verify_return_error
해결 방법
1) “클라이언트 신뢰 저장소”에 루트 CA를 넣는 것이 정석입니다.
2) 중간 CA 구조라면 서버는 “서버 인증서 + 중간 CA”를 함께 제공해야 합니다.
2) 중간 CA 구조라면 서버는 “서버 인증서 + 중간 CA”를 함께 제공해야 합니다.
# Debian/Ubuntu: 루트 CA 신뢰 등록
sudo cp rootCA.crt /usr/local/share/ca-certificates/mycompany-rootca.crt
sudo update-ca-certificates
# RHEL 계열: 루트 CA 신뢰 등록
sudo cp rootCA.crt /etc/pki/ca-trust/source/anchors/mycompany-rootca.crt
sudo update-ca-trust
중간 CA 체인 누락일 때(서버 설정)
# 예: 서버가 내보낼 체인 파일 생성(서버 인증서 + 중간 CA)
cat server.crt intermediateCA.crt > fullchain.crt
# Nginx 예시
# ssl_certificate /etc/ssl/certs/fullchain.crt;
# ssl_certificate_key /etc/ssl/private/server.key;
# Apache 예시(버전에 따라 다름)
# SSLCertificateFile /etc/ssl/certs/server.crt
# SSLCertificateChainFile /etc/ssl/certs/intermediateCA.crt (구버전에서 사용)
# 또는 SSLCertificateFile에 fullchain을 쓰는 방식으로 통일
오류 2) hostname mismatch / no alternative certificate subject name matches
대표 증상
curl: (60) SSL: no alternative certificate subject name matches target host name 'example.internal'
주요 원인
- SAN에 접속한 도메인이 없음(예: CN만 넣고 SAN을 안 넣음)
- 도메인 오타 또는 별칭(server_name/ServerAlias) 누락
- IP로 접속하는데 SAN에 IP가 없음
빠른 확인
# 인증서의 SAN 확인
openssl x509 -in /etc/ssl/certs/example.internal.crt -noout -text | grep -n "Subject Alternative Name" -A2
해결 방법
SAN은 “나중에 추가”가 아니라, CSR 생성 단계부터 정확히 넣어야 합니다.
이미 발급된 인증서에 SAN을 덧붙일 수는 없고, 보통 재발급이 정답입니다.
이미 발급된 인증서에 SAN을 덧붙일 수는 없고, 보통 재발급이 정답입니다.
# CSR config에서 alt_names를 올바르게 구성한 뒤 CSR 재생성 → 재서명
# (요약)
# 1) example.internal.csr.cnf 수정(SAN의 DNS/IP)
# 2) openssl req로 CSR 재생성
# 3) openssl x509 -req 로 재서명해 새 server.crt 발급
오류 3) openssl x509: Error adding extensions / unknown extension name
대표 증상
Error checking extension section default
Error adding extensions from section v3_req
주요 원인
- extfile(확장 파일) 문법 오류(섹션 이름/키 이름 오타)
- subjectAltName 섹션 참조(@alt_names) 오타
- 줄 끝 공백, 잘못된 콤마, 잘못된 키워드(예: serverAuth를 serverauth로)
빠른 확인
# 확장 파일을 열어 섹션/키 이름 점검
cat /root/pki/ca/example.internal.v3.ext
해결 방법(안전한 템플릿)
# 최소한의 안정 템플릿(서버 인증서)
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = example.internal
실무 기준으로 보면 “확장 파일은 템플릿을 고정”하고 값만 바꾸는 편이 장애를 크게 줄입니다.
오류 4) KEY_VALUES_MISMATCH / key does not match the certificate
대표 증상
nginx: [emerg] SSL_CTX_use_PrivateKey(...) failed (SSL: error:... key values mismatch)
주요 원인
- 서버가 참조하는 개인키(.key)와 인증서(.crt)가 서로 다른 쌍
- 파일 이름만 비슷하고 실제로는 다른 키/인증서
- 갱신 과정에서 새 인증서를 올렸는데 키는 예전 것을 유지
빠른 확인(지문 비교)
# 인증서와 키의 모듈러스 해시가 같은지 비교(같아야 정상)
openssl x509 -in /etc/ssl/certs/example.internal.crt -noout -modulus | openssl md5
openssl rsa -in /etc/ssl/private/example.internal.key -noout -modulus | openssl md5
해결 방법
동일한 CSR을 만든 키로 서명된 인증서를 써야 합니다.
매칭이 안 되면 보통 “인증서를 다시 발급(올바른 CSR/키 기준)”하거나 “키를 올바른 것으로 교체”해야 합니다.
매칭이 안 되면 보통 “인증서를 다시 발급(올바른 CSR/키 기준)”하거나 “키를 올바른 것으로 교체”해야 합니다.
오류 5) PEM routines: no start line / bad base64 decode / unable to load certificate
대표 증상
unable to load certificate
PEM routines:get_name:no start line
주요 원인
- 파일이 PEM 형식이 아님(DER 파일을 PEM으로 착각)
- 인증서/키 파일이 중간에 깨짐(복붙/인코딩/개행 손상)
- 파일 내용이 인증서가 아니라 다른 텍스트(잘못된 파일 경로)
빠른 확인
# PEM이면 보통 이런 헤더가 있어야 함
head -n 3 /etc/ssl/certs/example.internal.crt
# -----BEGIN CERTIFICATE-----
해결 방법
# DER(바이너리) → PEM 변환(가능한 경우)
openssl x509 -inform DER -in server.der -out server.pem
# 파일 경로/권한/내용을 다시 확인
file /etc/ssl/certs/example.internal.crt
ls -l /etc/ssl/certs/example.internal.crt
오류 6) Nginx/Apache가 키 파일을 읽지 못함(권한/SELinux)
대표 증상
nginx: [emerg] cannot load certificate key "/etc/ssl/private/xxx.key": Permission denied
주요 원인
- 파일 권한이 너무 빡빡해서 웹서버 프로세스가 읽지 못함
- 반대로 권한을 열어두는 실수(보안 사고 위험)
- SELinux가 접근을 차단(RHEL 계열에서 특히 흔함)
빠른 확인
# 프로세스 계정 확인(nginx/apache)
ps aux | egrep "nginx|httpd|apache2" | head
# 파일 권한/소유권 확인
ls -l /etc/ssl/private/example.internal.key
# SELinux 상태 확인
getenforce 2>/dev/null || true
해결 방법(권한)
운영 환경에서는 “키는 root만 소유 + 웹서버가 읽을 수 있는 최소 권한”이 핵심입니다.
배포판/구성에 따라 방법이 달라서, 아래 중 하나를 선택해 표준화하는 편이 좋습니다.
배포판/구성에 따라 방법이 달라서, 아래 중 하나를 선택해 표준화하는 편이 좋습니다.
# 방법 A) 웹서버 그룹에만 읽기 허용(예: nginx 그룹)
sudo chown root:nginx /etc/ssl/private/example.internal.key
sudo chmod 640 /etc/ssl/private/example.internal.key
# 방법 B) ACL로 특정 사용자/그룹에만 권한 부여
sudo setfacl -m u:nginx:r /etc/ssl/private/example.internal.key
sudo getfacl /etc/ssl/private/example.internal.key
해결 방법(SELinux)
# SELinux가 Enforcing이면 컨텍스트 문제일 수 있음
# 배포판 정책에 맞는 컨텍스트로 복구
sudo restorecon -v /etc/ssl/private/example.internal.key
sudo restorecon -Rv /etc/ssl/certs
# 그래도 막히면 audit 로그를 보고 원인 식별
sudo ausearch -m avc -ts recent 2>/dev/null | tail -n 30
# 또는 /var/log/audit/audit.log 확인
오류 7) openssl s_client에서 wrong version number
대표 증상
1408F10B:SSL routines:ssl3_get_record:wrong version number
주요 원인
- 443 포트에서 TLS가 아닌 서비스가 응답(HTTP가 그대로 나오는 경우)
- 로드밸런서/리버스 프록시 포트 매핑이 꼬여서 다른 포트로 연결됨
- 중간 장비에서 TLS 종료/재암호화 구성이 예상과 다름
빠른 확인
# 443이 실제로 무엇을 내보내는지 확인(평문이 섞여 나오면 설정 문제)
curl -v http://example.internal:443/ || true
# 포트 리스닝 확인
sudo ss -lntp | grep ':443' || true
해결 방법
“누가 TLS를 종료하고 있는지(종단 지점)”를 먼저 확정해야 합니다.
로드밸런서가 종료하는데 백엔드에 또 TLS를 걸거나, 반대로 백엔드는 TLS인데 LB가 평문으로 붙는 형태가 흔한 실수입니다.
로드밸런서가 종료하는데 백엔드에 또 TLS를 걸거나, 반대로 백엔드는 TLS인데 LB가 평문으로 붙는 형태가 흔한 실수입니다.
오류 8) handshake failure / no shared cipher / protocol version
대표 증상
sslv3 alert handshake failure
no shared cipher
protocol version
주요 원인
- 서버가 허용하는 TLS 버전이 너무 제한적(TLS1.2만 허용 vs 클라이언트가 TLS1.0)
- 암호군(ciphersuites) 정책 충돌(레거시 장비/구형 Java가 자주 문제)
- SNI 기반 가상호스트인데 -servername 없이 테스트
빠른 확인
# TLS 버전별로 강제 시도
openssl s_client -connect example.internal:443 -servername example.internal -tls1_2
openssl s_client -connect example.internal:443 -servername example.internal -tls1_3
해결 방법
운영 환경에서는 “최신 보안 정책”과 “레거시 클라이언트 지원”이 충돌할 수 있습니다.
내부 서비스라도 호출 주체(구형 JVM, 오래된 에이전트)가 무엇인지 먼저 파악한 뒤 정책을 잡는 편이 안전합니다.
내부 서비스라도 호출 주체(구형 JVM, 오래된 에이전트)가 무엇인지 먼저 파악한 뒤 정책을 잡는 편이 안전합니다.
오류 9) 인증서 용도(EKU) 문제: serverAuth 없음
대표 증상
브라우저/클라이언트가 인증서 자체는 신뢰하지만 “서버 인증서로서 적합하지 않다”는 계열의 오류를 내기도 합니다.
특히 내부에서 임의로 확장 값을 잘못 넣었을 때 발생합니다.
특히 내부에서 임의로 확장 값을 잘못 넣었을 때 발생합니다.
주요 원인
- extendedKeyUsage에 serverAuth가 없음
- keyUsage가 비정상(예: digitalSignature 누락)
- CA:TRUE로 잘못 발급(서버 인증서인데 CA로 설정)
빠른 확인
openssl x509 -in /etc/ssl/certs/example.internal.crt -noout -text | egrep -n "Extended Key Usage|Key Usage|Basic Constraints" -A2
해결 방법
# v3 ext 파일을 올바르게 구성하고 재서명/재발급
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
오류 10) 클라이언트는 신뢰했는데 특정 앱(Java/Node/Python)만 실패
대표 증상
OS에서 curl은 성공하는데, Java 앱(또는 특정 런타임)에서만 PKIX 에러가 나는 상황이 흔합니다.
이유는 각 런타임이 OS 신뢰 저장소를 그대로 쓰지 않고 “자체 truststore”를 쓰는 경우가 있기 때문입니다.
이유는 각 런타임이 OS 신뢰 저장소를 그대로 쓰지 않고 “자체 truststore”를 쓰는 경우가 있기 때문입니다.
주요 원인
- JVM truststore(cacerts)에 사설 CA가 없음
- 컨테이너 이미지에 CA를 넣지 않음(호스트만 등록됨)
- 애플리케이션이 별도 CA 번들을 지정하도록 구성됨
빠른 확인
# 컨테이너 내부에서 curl 테스트(호스트와 결과 비교)
docker exec -it <container> sh -lc 'curl -Iv https://example.internal/ || true'
# 앱 런타임 문서에서 "OS CA를 읽는지" 확인 필요
해결 방법(방향성)
- 컨테이너: 이미지에 rootCA.crt를 넣고 update-ca-certificates/update-ca-trust 수행
- Java: 전용 truststore에 사설 CA를 임포트하거나, OS truststore 연동 방식으로 표준화
- Node/Python: 실행 환경이 참조하는 CA 번들 경로를 정책으로 통일
- Java: 전용 truststore에 사설 CA를 임포트하거나, OS truststore 연동 방식으로 표준화
- Node/Python: 실행 환경이 참조하는 CA 번들 경로를 정책으로 통일
오류 11) 인증서 갱신 후에도 예전 인증서가 보임(캐시/재시작/다중 인스턴스)
대표 증상
파일은 교체했는데 s_client로 보면 예전 만료일/지문이 그대로인 경우가 있습니다.
주요 원인
- 웹서버가 reload가 아니라 restart가 필요한 상태
- 로드밸런서 뒤 여러 인스턴스 중 일부만 교체됨(롤링 누락)
- 실제 TLS 종료 지점이 다른 곳(LB/Ingress)인데 백엔드만 교체
빠른 확인
# 서버가 내보내는 인증서의 지문/만료일 확인
openssl s_client -connect example.internal:443 -servername example.internal 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256 -dates -subject -issuer
해결 방법
“교체한 파일”이 아니라 “실제로 네트워크로 나가는 인증서”를 기준으로 확인해야 합니다.
특히 LB/Ingress 사용 환경에서는 종료 지점을 먼저 확정하고 그 지점에서 교체해야 합니다.
특히 LB/Ingress 사용 환경에서는 종료 지점을 먼저 확정하고 그 지점에서 교체해야 합니다.
장애를 줄이는 운영 체크리스트
- SAN 목록은 표준 템플릿으로 관리(도메인/IP 누락 방지)
- 발급 후 바로 openssl x509 -text로 EKU/KeyUsage/BasicConstraints 확인
- 중간 CA 구조라면 서버는 fullchain 제공을 기본으로
- 클라이언트 신뢰 등록은 호스트 + 컨테이너 + 런타임까지 포함
- 갱신은 만료 전 자동 알림/모니터링을 걸어 “만료 장애”를 예방
- 키 권한은 최소화(640/600) + 필요 시 ACL/SELinux까지 표준 절차로 문서화
실무 기준으로 보면 TLS 장애는 “기술 문제”라기보다 “구성의 불일치(체인/신뢰/종단 지점/도메인)”에서 시작합니다.
위 오류 패턴과 확인 명령을 루틴으로 만들면 복구 속도가 크게 빨라집니다.
위 오류 패턴과 확인 명령을 루틴으로 만들면 복구 속도가 크게 빨라집니다.
2026.03.01 - [지식 공유/ETC] - Linux 서버에서 SSL 사설 인증서 만들고 적용·검증하는 방법
Linux 서버에서 SSL 사설 인증서 만들고 적용·검증하는 방법
Linux 서버에서 SSL 사설 인증서 만들고 적용·검증하는 방법 내부망(사내 서비스, 개발/스테이징, 프라이빗 API)에서는 공인 인증서 대신 사설 CA로 서버 인증서를 발급해 쓰는 경우가 많습니다. 이
one-day-growth.com
반응형
'지식 공유 > ETC' 카테고리의 다른 글
| Linux 서버에서 SSL 사설 인증서 만들고 적용·검증하는 방법 (0) | 2026.03.01 |
|---|---|
| 메모장 활용법: .LOG, F5 날짜, 빠른 검색 단축키까지 (0) | 2026.02.27 |
| 윈도우 11 군더더기 제거: 기본 앱·광고 설정을 윈핸스로 안전하게 정리 (0) | 2026.02.25 |
| 엑셀 실무 다음 단계: 필터·정렬·피벗 단축키와 데이터 정리 루틴 (1) | 2026.01.03 |
| 엑셀 꿀팁: 자주 쓰는 단축키와 복사·붙여넣기 정리 (1) | 2026.01.03 |
