NET::ERR_CERT_AUTHORITY_INVALID 원인별 해결

NET::ERR_CERT_AUTHORITY_INVALID 는 원인을 알려주지 않는 에러다. 화면에는 “연결이 비공개로 설정되어 있지 않습니다”만 뜨고, 그 뒤에는 서로 완전히 다른 네다섯 가지 상황이 숨어 있다. 어떤 건 서버 문제고, 어떤 건 그 PC만의 문제이며, 어떤 건 회사 보안 장비가 정상 동작한 결과다.

이 에러의 의미는 하나로 좁혀진다 — 브라우저가 이 인증서에서 출발해 자신이 신뢰하는 루트까지 경로를 만들지 못했다. 인증서가 잘못됐다는 뜻이 아니다. “내가 아는 곳에서 나온 것임을 확인하지 못했다”는 뜻이다.

먼저: 옆자리 에러와 구분

Chrome의 인증서 에러는 코드마다 원인 범위가 다르다. 여기를 헷갈리면 엉뚱한 걸 고치게 된다.

에러 코드 의미 이 글의 대상
ERR_CERT_AUTHORITY_INVALID (-202) 신뢰 경로를 못 만듦 ✅
ERR_CERT_COMMON_NAME_INVALID (-200) 접속한 도메인이 인증서에 없음 ❌ SAN 문제
ERR_CERT_DATE_INVALID 유효기간 밖 (만료 또는 시계) ❌
ERR_CERT_REVOKED 폐기된 인증서 ❌

COMMON_NAME_INVALID 은 발급자는 신뢰되는데 도메인이 안 맞는 경우다. 요즘 브라우저는 Common Name을 보지 않고 subjectAltName(SAN)만 본다. CN에 도메인이 있어도 SAN에 없으면 실패한다. 이건 인증서를 다시 발급받아야 하는 문제고, 신뢰 경로와는 무관하다.

AUTHORITY_INVALID 만 아래로 이어진다.

원인 판별 — 30초

원인마다 처리가 완전히 다르므로 발급자부터 확인한다. 주소창 → 인증서 정보 → 발급 기관(Issued by).

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer

읽는 법:

issuer 가 이렇게 나오면 원인
subject 와 동일 자기서명 (원인 1)
사내 CA 이름 (... Internal CA 등) 루트 미배포 (원인 2)
공개 CA인데 브라우저만 실패 중간 인증서 누락 (원인 3)
보안 제품 이름 (Fortinet, Zscaler, 백신 등) TLS 검사 장비 (원인 4)

issuer 가 예상과 다르면 그 지점에서 이미 답이 나온 것이다. 서버 설정을 뒤지기 전에 여기부터 본다.

원인 1 — 자기서명 인증서

subject 와 issuer 가 같으면 자기서명이다. 개발 서버, 장비 관리 페이지, 내부 도구에서 흔하다.

subject=CN = dev.internal
issuer=CN = dev.internal     ← 동일

브라우저 입장에서는 정상 동작이다. 신뢰할 근거가 없는 인증서를 거부한 것뿐이다.

처리 — 상황에 따라 갈린다.

  • 공개 서비스 — 자기서명을 쓰면 안 된다. Let’s Encrypt로 교체한다. 내부용 도메인이라 HTTP-01 챌린지가 어렵다면 DNS-01을 쓰면 공인 인증서를 받을 수 있다
  • 내부 전용 — 사내 CA를 만들어 그 루트를 배포하는 쪽이 낫다(원인 2 참고). 장비마다 예외를 클릭하게 두면, 진짜 중간자 공격이 왔을 때 아무도 알아채지 못한다
  • 잠깐 확인만 — 브라우저 예외 처리. 다만 이건 해결이 아니라 유예다

절대 하지 말 것: 사용자에게 “고급 → 계속 진행”을 안내하고 끝내는 것. 그 습관이 자리 잡으면 경고가 기능을 잃는다.

원인 2 — 사내 CA 루트 미배포

issuer 가 사내 CA인데 그 PC에 루트가 없는 경우다. 일부 PC만 실패하는 게 특징이다. 배포가 안 된 기기에서만 터진다.

Firefox가 특히 자주 튄다. OS 트러스트 스토어를 보지 않고 자체 스토어를 쓰기 때문이다. 그룹 정책으로 사내 루트를 밀어 넣어도 Firefox는 모른다.

처리 — 루트를 각 트러스트 스토어에 설치한다.

# Windows — 로컬 컴퓨터의 신뢰할 수 있는 루트
Import-Certificate -FilePath .\corp-root-ca.crt `
  -CertStoreLocation Cert:\LocalMachine\Root
# Debian/Ubuntu
sudo cp corp-root-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

# RHEL 계열
sudo cp corp-root-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Firefox는 별도로 처리한다. 두 방법 중 하나다.

  • about:config → security.enterprise_roots.enabled 를 true — OS 스토어를 같이 참조한다. 정책 파일(policies.json)로도 배포 가능하다
  • 또는 엔터프라이즈 정책의 Certificates.Install 로 루트를 직접 배포

서버·컨테이너도 잊지 말 것. 사람이 쓰는 PC만 챙기고 배치 작업이나 컨테이너를 빼먹으면, 새벽에 도는 잡이 조용히 실패한다. 컨테이너 이미지는 빌드 단계에서 루트를 넣어야 한다.

원인 3 — 중간 인증서 누락

issuer 가 정상적인 공개 CA인데도 실패하는 경우다. 서버가 중간 인증서를 안 보내서 경로가 끊긴 것이다.

특징이 뚜렷하다. 크롬은 되는데 curl·자바·모바일 앱이 실패한다. 크롬은 AIA fetching으로 몰래 채워 넣기 때문이다.

# 서버가 보낸 것만으로 검증되는가
openssl s_client -connect example.com:443 -servername example.com \
  -verify_return_error </dev/null 2>&1 | grep "Verify return code"

Verify return code: 0 (ok) 가 아니면 서버 체인이 불완전하다. 처리는 fullchain 배포다. 판별과 수정 절차는 인증서 체인 오류가 브라우저마다 다르게 보이는 이유에서 자세히 다뤘다.

원인 4 — TLS 검사 장비 (가장 오진하기 쉬움)

issuer 에 보안 제품 이름이 나오면 여기다.

issuer=CN = FortiGate CA, O = Fortinet
issuer=CN = Zscaler Intermediate Root CA
issuer=CN = AhnLab ... 

방화벽·프록시·백신이 HTTPS 트래픽을 검사하려고 연결을 끊고 자기 인증서로 다시 맺은 것이다. 구조적으로는 중간자 공격과 동일하고, 그래서 그 장비의 루트가 설치돼 있지 않으면 브라우저가 정확히 이 에러를 낸다.

서버는 아무 잘못이 없다. 이 상태에서 서버 인증서를 재발급하면 시간만 버린다.

판별이 쉬운 이유는 서버에서 본 인증서와 클라이언트에서 본 인증서가 다르다는 것이다.

# 서버에서 (검사 장비를 안 거침)
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -fingerprint -sha256

문제가 나는 PC의 브라우저에서 본 지문과 이 값이 다르면 중간에 누가 끼어든 것이다.

처리

  • 사내 정책상 정상 동작이면 → 그 장비의 루트 CA를 클라이언트에 배포 (원인 2와 동일)
  • 특정 서비스가 검사 대상이면 안 되면 → 장비에서 해당 도메인을 검사 예외(bypass) 로 등록
  • 개인 PC의 백신이 원인이면 → 그 제품의 HTTPS 스캐닝 설정 확인
  • 인증서 피닝을 쓰는 앱은 예외 등록 외에 방법이 없다. 루트를 설치해도 실패한다

원인 5 — 시스템 시계

드물지만 고치기는 제일 쉽다. 시계가 크게 어긋나면 루트 인증서가 아직 유효하지 않거나 이미 만료된 것으로 판정된다. 보통 ERR_CERT_DATE_INVALID 로 나오지만, 경로 구성 단계에서 걸리면 AUTHORITY_INVALID 로도 뜬다.

CMOS 배터리가 죽은 PC, 초기화된 VM, 네트워크가 격리돼 NTP를 못 받는 서버에서 나온다.

timedatectl status          # Linux
w32tm /query /status        # Windows
sudo sntp -sS time.apple.com  # macOS 강제 동기화

날짜가 몇 년 단위로 틀어져 있으면 이게 원인이다. 인증서를 건드리기 전에 시계부터 맞춘다.

원인 6 — 루트 자체가 만료·제거됨

서버도 정상, 체인도 완전, 시계도 맞는데 오래된 기기에서만 실패하는 경우다.

트러스트 스토어 업데이트가 멈춘 기기는 새 루트를 모른다. 알던 루트는 만료됐다. 2021년 Let’s Encrypt DST Root CA X3 만료 때 구형 안드로이드가 일제히 끊긴 게 이 경우다.

처리 방향

  • 서버에서 내보낼 체인을 골라 구형 기기를 살릴 수 있는지 확인 (CA가 대체 체인을 제공하는 경우)
  • 안 되면 그 기기군을 지원 대상에서 제외할지 판단해야 한다. 서버 설정으로 해결되는 문제가 아니다

판별 순서 정리

1. issuer 확인
   └ subject와 같음        → 자기서명 (원인 1)
   └ 사내 CA               → 루트 배포 (원인 2)
   └ 보안 제품 이름        → TLS 검사 장비 (원인 4)
   └ 정상 공개 CA          → 2번으로

2. openssl -verify_return_error
   └ 0 (ok) 아님           → 중간 인증서 누락 (원인 3)
   └ 0 (ok)                → 3번으로

3. 실패하는 기기의 범위
   └ 특정 PC만             → 트러스트 스토어 / 시계 (원인 2, 5)
   └ 오래된 기기만         → 루트 만료 (원인 6)

서버 인증서 재발급은 이 순서의 어디에도 없다. AUTHORITY_INVALID 의 원인 대부분은 서버가 아니라 검증하는 쪽에 있기 때문이다. 재발급부터 하고 보는 습관이 가장 흔한 시간 낭비다.

원인이 중간 인증서 누락으로 좁혀졌다면 고치는 절차는 Nginx 인증서 교체와 무중단 리로드에 있다.