SSL 인증서 만료일 확인하는 방법 — 브라우저·openssl·PowerShell

인증서 만료 사고는 대부분 “만료일을 몰라서”가 아니라 어디를 봐야 하는지 착각해서 발생한다. 브라우저가 보여주는 만료일과 서버에 설치된 파일의 만료일이 다를 수 있고, 만료가 임박한 것은 서버 인증서가 아니라 중간 인증서일 수도 있다.

여기서는 세 가지 확인 방법을 다루고, 각 방법이 실제로 무엇을 검사하는지 짚는다.

1. 브라우저 — 가장 빠르지만 가장 좁다

주소창 자물쇠 아이콘 → 인증서 정보에서 유효기간을 볼 수 있다.

  • Chrome: 자물쇠 → 이 사이트에 대한 인증서가 유효합니다 → 세부 정보
  • Firefox: 자물쇠 → 연결 보안 → 자세히 보기

문제는 브라우저가 접속에 성공한 하나의 경로만 보여준다는 점이다. 로드밸런서 뒤에 서버가 여러 대이고 그중 한 대만 예전 인증서를 들고 있으면, 브라우저는 그 사실을 알려주지 않는다. 브라우저 확인은 “지금 내 눈앞의 응답”에 대한 확인이지 서비스 전체에 대한 확인이 아니다.

2. openssl — 실제로 서버가 보내주는 것을 본다

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

출력:

subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R11
notBefore=Aug 12 04:31:17 2026 GMT
notAfter=Nov 10 04:31:16 2026 GMT

-servername 을 빼면 안 된다. SNI 없이 접속하면 그 IP의 기본 vhost 인증서가 돌아오고, 확인하려던 도메인과 다른 인증서를 보게 된다. 실제로 “만료일이 왜 다르지”의 상당수가 이것이다.

만료까지 남은 일수만 필요하면:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -checkend $((30*86400)) \
  && echo "30일 이상 남음" || echo "30일 이내 만료"

-checkend 는 종료 코드로 답한다. 스크립트에 넣기 좋다.

파일로 가지고 있는 인증서 확인

openssl x509 -in /etc/ssl/certs/example.crt -noout -dates

서버가 실제로 서빙 중인 것과 디스크의 파일이 다를 수 있다. 갱신 후 리로드를 안 했다면 파일은 새 것, 서빙은 옛 것이다. 둘 다 확인해야 의미가 있다.

3. PowerShell — Windows 환경

$req = [Net.HttpWebRequest]::Create("https://example.com")
$req.GetResponse() | Out-Null
$req.ServicePoint.Certificate.GetExpirationDateString()

IIS에 설치된 인증서를 직접 보려면:

Get-ChildItem Cert:\LocalMachine\My |
  Select-Object Subject, NotAfter, Thumbprint |
  Sort-Object NotAfter

NotAfter 기준 정렬이면 곧 만료될 것이 맨 위에 온다. 인증서가 여러 장 설치된 서버에서 어떤 것이 실제 바인딩에 쓰이는지는 Get-WebBinding 으로 thumbprint를 대조해야 한다.

놓치기 쉬운 것 세 가지

중간 인증서도 만료된다. 서버 인증서만 갱신하고 체인 파일을 그대로 두면, 서버 인증서가 유효해도 검증이 깨진다. openssl s_client 출력에서 Certificate chain 섹션의 각 항목을 확인해야 한다.

만료일과 갱신 시점은 다르다. Let’s Encrypt는 90일 인증서를 60일 시점에 갱신하도록 설계돼 있다. “아직 30일 남았는데 왜 갱신 실패 알림이 오지”가 정상이다. 30일 남은 시점의 실패는 이미 두 번 이상 실패했다는 뜻이다.

서브도메인은 따로 만료된다. 와일드카드가 아니라면 www 와 api 는 별개 인증서다. 루트 도메인만 확인하고 넘어가면 놓친다.

여러 도메인을 한 번에

도메인이 열 개를 넘어가면 수동 확인은 유지되지 않는다. 최소한의 형태는 이렇다:

for d in example.com api.example.com www.example.com; do
  end=$(openssl s_client -connect "$d:443" -servername "$d" </dev/null 2>/dev/null \
        | openssl x509 -noout -enddate | cut -d= -f2)
  printf '%-28s %s\n' "$d" "$end"
done

이 정도로도 엑셀보다는 낫다. 다만 도메인 자체의 만료(레지스트라 등록 만료)는 여기서 안 잡힌다.

만료일은 정상인데 브라우저가 오류를 내는 경우라면 체인 문제일 가능성이 높다 — 크롬은 되는데 파이어폭스만 인증서 오류가 나는 이유와 NET::ERR_CERT_AUTHORITY_INVALID 원인별 해결에서 이어진다. 확인 후 실제 교체 절차는 Nginx 인증서 교체와 무중단 리로드를 참고.