Fullmoon System

Certbot 자동 갱신 실패 해결: 인증서가 계속 만료로 보일 때

AI Editter

Certbot에서 인증서 갱신이 성공했다고 나왔는데 브라우저에는 만료된 인증서가 보일 수 있습니다. 새 인증서 파일을 받은 과정과 웹 서버가 그 파일을 읽어 서비스하는 과정이 다르기 때문입니다. 검증 challenge, 갱신 실행, 배포·reload, 외부에서 보이는 인증서를 나눠 확인해야 재발을 막을 수 있습니다.

아래는 Linux에서 Certbot과 Nginx를 사용하는 관리자를 위한 절차입니다. 경로와 서비스 이름은 설치 방식에 따라 다르며, CDN이나 로드밸런서가 TLS를 종료하면 그 앞단의 인증서도 별도로 확인해야 합니다.

1. 저장된 인증서와 실제 서비스 인증서를 비교한다

sudo certbot certificates

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

첫 명령은 Certbot이 관리하는 인증서 이름·경로·유효기간을 보여 줍니다. 두 번째는 해당 도메인의 TLS 연결에서 받은 인증서 정보를 보여 줍니다. 출력이 비었다면 인증서 날짜 문제로 단정하지 말고 DNS·연결·TLS 오류부터 확인하세요. 도메인은 실제 값으로 바꾸며 SNI를 맞추기 위해 -servername을 사용합니다.

CDN을 쓰면 외부 연결은 CDN의 인증서를 보여 줄 수 있습니다. 원본을 검사하려면 허용된 관리 경로에서 실제 원본 주소로 연결하되 SNI는 서비스 도메인을 유지해야 합니다. 여러 로드밸런서·서버 중 한 대만 이전 인증서를 제공하는지도 확인합니다.

2. dry-run으로 갱신 경로를 시험한다

sudo certbot renew --dry-run
systemctl list-timers --all | grep -Ei 'certbot|letsencrypt'

--dry-run은 테스트용 갱신을 수행합니다. 인증서가 아직 갱신 대상 시기가 아니라는 정상 메시지와 검증 실패를 구분하세요. 같은 서버에서 cron, snap timer, systemd timer를 중복 등록하지 말고 설치된 방식의 실행 경로를 찾습니다. 타이머 이름과 로그 위치는 패키지에 따라 달라질 수 있습니다.

기본 dry-run이 성공했다고 실제 deploy hook까지 검증된 것은 아닙니다. Certbot 버전과 옵션을 확인한 뒤 필요한 경우 --run-deploy-hooks를 사용해 hook 동작을 별도로 시험합니다. hook은 서비스를 reload하는 실제 작업을 할 수 있으므로 내용을 먼저 읽고 테스트해야 합니다. Certbot 갱신 문서가 실행 범위를 설명합니다.

3. HTTP-01 실패라면 80번과 challenge 경로를 본다

HTTP-01 검증은 http://도메인/.well-known/acme-challenge/... 경로에 접근합니다. HTTPS 사이트라도 검증 방식에 따라 80번 포트가 필요합니다. A·AAAA 레코드가 맞는지, 80번 요청이 올바른 서버로 들어오는지, 인증 요구나 접근 차단 규칙이 해당 경로를 막는지 확인하세요. Let’s Encrypt challenge 설명에 포트와 방식의 제약이 나옵니다.

webroot 방식은 Certbot이 파일을 쓰는 디렉터리와 Nginx가 URL로 제공하는 디렉터리가 같아야 합니다. 다중 서버라면 검증 요청이 어느 서버로 가도 challenge 파일을 읽을 수 있어야 합니다. standalone 방식은 자체 검증 서버를 띄우므로 기존 프로세스가 포트를 점유하는지도 확인합니다. 포트 충돌을 해결하려고 운영 웹 서버를 예고 없이 내리지 마세요.

DNS-01은 TXT 값보다 자동화 주체와 권한이 중요하다

와일드카드 인증서는 DNS-01 검증을 사용합니다. 갱신 실패 시 _acme-challenge TXT 레코드의 권한 있는 DNS 응답, 전파 시간, DNS API 자격 증명 유효 여부를 확인합니다. 수동 DNS 검증으로 발급했다면 자동 갱신용 hook이나 DNS 플러그인이 마련되어 있는지 먼저 봅니다.

DNS 토큰은 필요한 영역과 레코드 변경 권한으로 제한하고 root만 읽는 자격 증명 파일 등 설치 방식에 맞게 보관합니다. 로그나 블로그 질문에 토큰을 붙이지 마세요. DNS 공급자의 제약에 맞춘 공식 Certbot 플러그인을 사용하고, 다른 zone에 레코드를 쓰고 있지 않은지도 확인합니다.

4. 갱신 성공 후 Nginx가 새 파일을 읽었는지 본다

Nginx 설정의 ssl_certificate와 ssl_certificate_key가 갱신되는 파일을 가리키는지 확인합니다. 인증서 파일을 다른 디렉터리에 수동 복사해 쓰면 Certbot의 원본만 새로워지고 서비스 파일은 그대로일 수 있습니다. 설정 검사 후 승인된 배포 절차에서 reload합니다.

sudo nginx -t
sudo systemctl reload nginx

첫 명령이 실패하면 두 번째를 실행하지 않고 오류를 해결합니다. 여러 서비스가 인증서를 쓰면 Nginx 한 개만 reload해 끝내지 않습니다. 성공할 때만 실행하는 deploy hook을 두고, 그 hook의 실패도 로그와 알림으로 확인하도록 구성하세요.

정상 여부는 외부에서 보는 만료일로 마무리한다

갱신 후 다시 TLS 연결을 열어 serial과 만료일이 바뀌었는지 확인합니다. 중간 인증서 체인이 올바른지도 검사하고, 로드밸런서 뒤의 모든 대상에 동일하게 반영됐는지 봅니다. 단순히 timer가 active라는 사실은 최근 갱신 성공의 증거가 아닙니다.

운영 알림은 갱신 작업 실패와 외부 인증서 만료 임박을 함께 감시하는 편이 좋습니다. 검사 주기는 인증서 수명과 대응 시간에 맞추고, 정해진 만료일을 가정하기보다 실제 notAfter 값을 사용하세요. DNS·방화벽·웹 서버 설정을 변경한 뒤에도 dry-run을 다시 수행하면 다음 갱신일까지 숨어 있을 문제를 미리 찾을 수 있습니다.

CDN 앞단에서만 문제가 보이는 경우에는 Cloudflare와 원본 서버를 나누는 진단으로 확인 위치를 정리할 수 있습니다.