이 오류가 정확히 말하는 것

모든 인증서에는 notBefore와 notAfter라는 두 시각이 적혀 있습니다. 그 사이를 벗어나면 브라우저는 인증서를 거부합니다. NET::ERR_CERT_DATE_INVALID는 크롬이 “이 기기의 시계로 보면 인증서가 유효 기간 밖에 있다”고 알려 주는 오류입니다. 이미 만료됐거나, 아직 유효 기간이 시작되지 않았다는 뜻입니다.

“이 기기의 시계로 보면”이 핵심입니다. 브라우저에는 다른 시간 정보가 없습니다. 2031년이라고 믿는 컴퓨터에서는 멀쩡한 인증서도 만료된 것으로 보이고, 2019년이라고 믿는 컴퓨터에서는 오늘 아침 발급된 인증서가 아직 유효하지 않은 것으로 보입니다.

파이어폭스는 같은 상황을 SEC_ERROR_EXPIRED_CERTIFICATE(만료), MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE(아직 유효하지 않음)로 보여 주고, 사이트 인증서가 아니라 중간 인증서가 만료됐을 때는 SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE로 보여 줍니다.

먼저 물어볼 것: 이 기기만인가, 모두인가

이 질문 하나로 문제가 둘로 갈립니다. 다른 무엇보다 먼저 답해 볼 가치가 있습니다.

  • 한 기기에서 모든 사이트가 안 됨(구글, 네이버, 은행까지): 기기 시계가 틀렸습니다. 사이트들은 정상입니다.
  • 한 사이트가 모든 기기에서 안 됨(모바일 데이터로 연결한 휴대폰 포함): 사이트 인증서가 만료됐거나 사이트가 예전 인증서를 내보내고 있습니다. 운영자가 고쳐야 합니다.
  • 한 사이트가 한 기기에서만 안 됨: 이것도 대개 시계 문제입니다. 몇 년이 아니라 몇 시간 틀린 시계가, 막 발급됐거나 곧 만료될 인증서와 만난 경우입니다.

SSL 점검은 우리 네트워크 밖에서 본 인증서 유효 기간을 보여 줍니다. 점검 결과는 유효한데 내 브라우저만 오류를 낸다면 내 시계를 보세요.

방문자라면: 시계를 고치세요

크롬이 기기 시계가 느리거나 빠르다고 직접 알려 줄 때도 있습니다. 그렇지 않더라도 오류 화면의 세부 정보에 컴퓨터가 현재 날짜로 알고 있는 값이 나옵니다. 실제 날짜와 비교해 보세요.

  • Windows: 설정 → 시간 및 언어 → 날짜 및 시간. ‘자동으로 시간 설정’을 켜고 표준 시간대가 ‘(UTC+09:00) 서울’인지 확인한 뒤 ‘지금 동기화’를 누릅니다. 관리자 명령 프롬프트에서 w32tm /resync를 실행해도 같습니다.
  • macOS: 시스템 설정 → 일반 → 날짜 및 시간 → ‘날짜 및 시간 자동으로 설정’.
  • Linux: NTP가 동작 중이면 timedatectl에 System clock synchronized: yes가 나옵니다. 아니라면 sudo timedatectl set-ntp true.
  • 안드로이드·아이폰: 설정에서 날짜와 시간 자동 설정을 켭니다. 휴대폰에서만 안 될 때는 날짜를 수동으로 바꿔 둔 경우가 가장 흔합니다.

알아 두면 좋은 패턴이 두 가지 있습니다.

  • 전원을 끌 때마다 시계가 옛날 날짜로 돌아감. 데스크톱 메인보드의 CMOS 배터리가 다 된 것입니다. 동기화하면 다음에 전원을 완전히 끌 때까지는 괜찮고, 코인 전지를 바꾸면 완전히 해결됩니다. 라즈베리 파이 대부분의 모델도 배터리로 유지되는 시계가 없어서, NTP 서버에 닿기 전까지는 오래된 시간으로 부팅합니다.
  • 시각은 맞는데 시간대가 틀림. 화면의 시각은 맞지만 시간대가 서울이 아니라 미국 태평양 시간 등으로 돼 있으면, 컴퓨터가 아는 UTC가 몇 시간씩 어긋납니다. 이 경우 몇 시간 전에 발급됐거나 몇 시간 뒤 만료되는 인증서에서만 문제가 생겨서, 증상이 들쭉날쭉해 보입니다.

사무실 전체나 여러 기기가 동시에, 다른 곳에서는 잘 열리는 사이트에서 이 오류를 낸다면 공용 시간 소스를 의심하세요. 시간이 틀어진 NTP 서버나, 잘못된 NTP 서버를 나눠 주는 공유기일 수 있습니다.

내 사이트라면: 서버가 실제로 보내는 날짜를 읽으세요

실패하는 바로 그 이름으로, 실제 접속에서 오가는 인증서를 확인합니다.

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

notAfter=Oct 7 00:00:00 2026 GMT는 UTC입니다. 한국 시간으로는 오전 9시입니다. 벽시계가 아니라 date -u와 비교하세요.

그다음 디스크에 있는 파일과 비교합니다.

sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -dates

두 결과를 보면 어느 경우인지 알 수 있습니다.

  • 둘 다 예전 날짜: 갱신이 실패했습니다. 갱신을 고치세요.
  • 디스크는 새것, 실제 접속은 예전 것: 갱신은 됐는데 서버가 메모리에 든 예전 인증서를 계속 내보내고 있거나, 서버 앞의 무언가가 그러고 있습니다.
  • 둘 다 새것이고 유효한데 일부 사용자만 불만: 위의 시계 이야기로 돌아가세요. 그 사용자들의 기기가 틀린 것입니다.

자동 갱신인 줄 알았는데 왜 만료됐나

갱신이 실패했는데 아무도 몰랐음

Let’s Encrypt의 기본 인증서는 90일짜리이고, certbot은 기본적으로 만료 30일쯤 전에 갱신합니다. 그래서 갱신이 깨져도 눈에 보이는 일이 생기기까지 한 달가량 아무 일도 없습니다. Let’s Encrypt는 2025년에 만료 안내 메일도 중단했으므로, 그쪽에서 경고가 오리라 기대하면 안 됩니다.

현재 상태는 이렇게 확인합니다.

sudo certbot certificates          # 모든 인증서와 만료일
sudo certbot renew --dry-run       # 지금 갱신이 되는가?
systemctl list-timers | grep -i certbot

배포판 패키지로 설치했다면 certbot.timer, snap으로 설치했다면 snap.certbot.renew.timer가 있어야 합니다. 타이머도 cron 항목도 없다면 지금까지 아무것도 갱신하지 않고 있었던 것입니다.

갱신이 실패하는 흔한 이유는 이렇습니다.

  • HTTP-01 검증 요청이 서버에 닿지 않음. 방화벽에서 80 포트가 닫혀 있거나, 리다이렉트가 /.well-known/acme-challenge/ 요청을 그 파일이 없는 곳으로 보냅니다.
  • --standalone 방식이 80 포트를 쓰지 못함. 그사이 nginx나 Apache가 80 포트를 차지했습니다.
  • DNS를 옮김. 이름이 이제 CDN이나 다른 서버를 가리켜서, 검증 요청이 certbot이 도는 서버에 오지 않습니다.
  • DNS-01 인증 정보가 만료됨. DNS 업체의 API 토큰이 교체되거나 폐기됐습니다.

어느 것인지는 /var/log/letsencrypt/letsencrypt.log에 나옵니다.

디스크에서는 갱신됐는데 실제 접속은 예전 것

nginx와 Apache는 시작하거나 리로드할 때 인증서를 읽습니다. 디스크의 파일이 바뀌어도 그 전까지는 아무 일도 일어나지 않습니다.

certbot의 nginx·Apache 플러그인은 서버를 알아서 리로드합니다. certonly --webroot나 --standalone으로 받았다면 리로드하지 않으므로 deploy hook이 필요합니다.

sudo certbot renew --deploy-hook "systemctl reload nginx"

계속 적용되게 하려면 /etc/letsencrypt/renewal-hooks/deploy/에 실행 가능한 스크립트를 두세요. 인증서를 메모리에 들고 있는 다른 프로그램도 마찬가지입니다. 메일 서버(Postfix, Dovecot), HAProxy, 자체 키스토어를 쓰는 Java 애플리케이션 등입니다.

잊고 있던 다른 사본

브라우저가 보는 인증서는 TLS를 끝내는 곳에서 나오고, 그곳은 내가 갱신한 서버가 아닌 경우가 많습니다.

  • 인증서를 직접 올린 CDN. Cloudflare는 자기 엣지 인증서는 알아서 갱신하지만, 내가 업로드한 사용자 인증서는 올린 그대로 남아 있습니다. AWS Certificate Manager에 가져온(import) 인증서도 같습니다. ACM은 자기가 발급한 인증서만 갱신하고 가져온 인증서는 갱신하지 않습니다.
  • 자기 사본을 가진 로드밸런서.
  • 한 이름 뒤에 서버가 여러 대. 한 대만 갱신하고 나머지는 예전 인증서를 내보내고 있어서, 어느 서버에 붙느냐에 따라 오류가 났다 안 났다 합니다.

서버마다 IP를 지정하고 같은 -servername으로 openssl s_client를 돌려 보면 어느 서버가 뒤처졌는지 알 수 있습니다.

“아직 유효하지 않음”인 경우

반대 경우는 드물고, 거의 언제나 어느 한쪽의 시간 문제입니다.

  • 막 발급된 인증서와 늦게 가는 클라이언트 시계. CA는 작은 시계 오차를 흡수하려고 보통 notBefore를 조금 과거로 잡습니다(Let’s Encrypt는 약 1시간). 그래도 하루 늦은 기기는 여전히 거부합니다.
  • 시작일 전에 배포한 인증서. 일부 상용 CA는 시작일을 미래로 지정할 수 있게 해 줍니다. 일찍 설치했다면 기다리거나 재발급하세요.

여기서 상관없는 것이 하나 있습니다. 바로 서버의 시계입니다. 브라우저는 서버 시계가 아니라 자기 시계로 인증서를 판단합니다. (서버에서도 NTP는 켜 두세요. 로그, 토큰, 갱신 작업이 서버 시간에 의존합니다.)

인증서는 멀쩡한데 그 위의 것이 만료된 경우

인증서 체인은 가장 오래된 고리만큼만 유효합니다. 서버가 보내는 중간 인증서나 그 끝의 루트 인증서가 만료됐다면, 내 인증서가 유효해도 클라이언트는 체인 전체를 거부합니다.

가장 잘 알려진 사례가 있습니다. 2021년 9월 30일 DST Root CA X3 루트가 만료됐습니다. Let’s Encrypt 체인은 호환성 때문에 여전히 그 루트로 이어져 있었고, 더 새로운 ISRG Root X1을 미리 신뢰하지 않던 기기들이 아무 문제 없는 사이트에서 실패하기 시작했습니다. 업데이트가 끊긴 오래된 아이폰과 맥, 그리고 OpenSSL 1.0.x를 쓰는 일부 오래된 시스템이 주로 그랬습니다. 최신 브라우저는 새 루트로 가는 경로를 찾아 아무 일 없이 동작했습니다.

내 쪽에서 확인할 것은 이렇습니다.

  • 서버가 보내는 체인 전체를 보세요. openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null. 체인에 있는 인증서가 모두 유효 기간 안이어야 합니다.
  • 몇 년 전 손으로 만든 chain.pem에 예전 중간 인증서를 계속 묶어 두지 마세요. 갱신할 때마다 CA가 주는 체인을 쓰세요(certbot이라면 fullchain.pem).

내가 관리하지 않는 오래된 기기에서만 오류가 난다면 할 수 있는 일에 한계가 있습니다. 그 기기들은 OS 업데이트가 필요하거나, 파이어폭스처럼 자체 신뢰 저장소를 가진 브라우저를 써야 합니다.

다시 일어나지 않게 하려면

인증서 유효 기간은 계속 짧아지고 있습니다. 2026년 3월부터 공인 인증서의 유효 기간은 최대 200일이고, CA/브라우저 포럼은 2027년과 2029년에 더 줄이기로 정해 두었습니다. 1년에 한 번 달력 알림으로 갱신하던 방식은 더는 통하지 않습니다. 갱신은 자동이어야 하고, 자동 갱신이 실패했을 때 알려 주는 장치가 있어야 합니다.

  • 서버의 파일을 읽지 말고, 사용자가 보는 방식 그대로 바깥에서 만료일을 확인하세요.
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -checkend 1209600 || echo "14일 안에 만료"

-checkend는 초 단위이고 1209600초는 14일입니다. 운영하는 모든 호스트명에 대해 cron으로 돌리고 결과를 알림으로 받으세요.

  • 인증서 사본을 가진 모든 곳에 새 인증서가 들어갔는지 확인하세요. 서버, 로드밸런서, CDN에 올린 인증서까지입니다.
  • 갱신한 뒤에는 SSL 점검을 한 번 돌려 바깥에서 보이는 만료일이 새 날짜인지 확인하세요.

비슷해 보이는 다른 오류

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.