Files
keycloak-pattern/docs/evidence/d4-certificate-renewal
DongHyeonkaandClaude Opus 5 faf55dc1be docs(d4): the renewal succeeded and was not served for 36 minutes 39 seconds
The forced renewal did work. What did not work was anything making nginx
notice.

  disk     cert2.pem written 2026-09-04 17:22:13 KST
  network  old serial through 08:58:47 UTC, new serial from 08:58:52 UTC
  gap      2199 seconds, and the old certificate was observed 428 times in it

The gap closed only because a person ran `nginx -s reload`. Nothing else
would have: certbot-renew.service is just `ExecStart=/usr/bin/certbot -q
renew` with no ExecStartPost, all three of renewal-hooks/{deploy,post,pre}
are empty, and certbot 5.7.0 here has no nginx plugin (dns-cloudflare,
manual, null, standalone, webroot). Three paths, all empty. Any one of them
would have been enough.

nginx holds the certificate in memory from startup, and certbot swaps the
live/ symlink rather than the path — so the config still looks right while
the served certificate is stale. Master 585 and worker 586 sharing a start
time 22.4 hours old is what proved no reload had happened.

This is invisible for 88 days. The timer ran twice today and exited
0/SUCCESS both times, because a renewal is not due yet. The day it becomes
due, the symptom is an expired certificate and the log still says SUCCESS.

The other half of the plan's question now has a measurement rather than an
expectation. The reload is genuinely graceful: 8856 polled connections, zero
non-200, p95 205.7ms before against 204.3ms after. And an in-flight request
— 845KB pulled at 20k/s, so still transferring 12 seconds in when the reload
landed — completed with all 845361 bytes over a single connection.

The earlier note that nginx reload "is graceful by design but must not be
written that way without checking" was right to hold back. The neighbouring
belief, that renewing gets the new certificate served, was the one that was
false, and there was no way to tell in advance which it would be.

Adds diagrams/d4-renewal-gap.svg and evidence 09-13.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 18:04:44 +09:00
..

D-4 — 인증서 갱신 증거

2026-09-04 17:25 18:02 KST 해설: docs/experiment-d4-certificate-renewal.md

파일 무엇을 보여주는가
01-certificate-state.txt SAN 3개(와일드카드 아님) · 체인 4단계, Verify return code: 0 · certbot-renew.timer enabled·active, 11시간 전 실행 · 88일 남음 · sudo: a password is required 로 강제 갱신 불가
05-control-no-injection.txt 대조군 1 — 잡음 바닥. 0.2초 × 900회 / 180초 동안 900전부 200, 오류 0. 중앙 98ms · p95 195ms. TLS 핸드셰이크 900/900 = 매 요청이 새 연결이다
06-inflight-control.txt 대조군 2 — '진행 중이던 요청' 측정 장치. 845KB 번들을 --limit-rate 20k 로 받아 요청을 42초간 살려 둔다. 무주입 시 코드 200 · 845361바이트 · 연결수 1

| 05-control-no-injection.txt | 대조군 1 — 잡음 바닥. 0.2초 × 900회 / 180초 동안 900 전부 200, 오류 0. 중앙 98ms · p95 195ms. TLS 핸드셰이크 900/900 = 매 요청이 새 연결 | | 06-inflight-control.txt | 대조군 2 — '진행 중이던 요청' 장치. 845KB 를 --limit-rate 20k 로 받아 요청을 42초간 살려 둔다. 무주입 시 200 · 845361바이트 · 연결수 1 | | 07-renewal-hook-missing.txt | nginx 는 reload 된 적이 없다 — 마스터 585·워커 586 이 같은 시각 기동, 22.4시간째. 유닛은 ExecStart=certbot -q renew 가 전부. crt.sh 는 SCT 가 박힌 인증서를 0건으로 답한다 | | 08-inflight-artifact.txt | 76건 실패는 서버 탓이 아니다 — 같은 순간 폴링 49건 전부 200, 연결수=0, 50µs, 재현 0/100. 대조군이 오보를 막았다 | | 09-serial-timeline.txt | 일련번호 564표본. 08:10:51 ~ 08:58:47 옛 것 → 08:58:52 새 것 | | 10-reload-poll-window.txt | reload 전후 60초 새 연결 원문 — 비200 0건, 최대 373ms | | 11-inflight-full.txt | in-flight 전체 50건. 08:58:40 시작 요청이 08:58:52 reload 를 관통해 845361바이트 전량 수신 | | 12-certbot-state.txt | cert2.pem 09-04 17:22:13 기록됨 · renewal-hooks/{deploy,post,pre}/ 셋 다 비었음 · 플러그인 목록에 nginx 없음 | | 13-verdict.txt | 판정 전문 — 36분 39초 공백(428회 관측) + reload 무중단(8856건 0실패) |

핵심 다섯 줄

  1. 「갱신 성공」과 「새 인증서 서빙」은 다른 사건이다. 새 인증서가 디스크에 있는 채로 36분 39초 동안 옛 인증서를 서빙했고, 그 구간에서 428번 관측했다.
  2. 그 36분은 우연히 짧았다. reload 를 시킨 것은 사람이다. 아무도 안 했다면 다음 nginx 재시작까지 무기한이었다.
  3. 원인이 셋 겹쳤다. 유닛에 ExecStartPost 없음 · 훅 디렉터리 3개 전부 비었음 · certbot 에 nginx 플러그인 없음. 하나라도 있었으면 자동 반영됐다.
  4. reload 자체는 무중단이었다. 새 연결 8856건 전부 200, p95 205.7 → 204.3ms, 그리고 전송 12초째에 reload 를 맞은 42초 요청이 845361바이트를 온전히 받았다(연결수 1).
  5. 이 결함은 88일 동안 안 보인다. 타이머는 오늘도 두 번 SUCCESS 로 끝났다. 만료 30일 전까지는 갱신 자체를 하지 않으므로 발현할 기회가 없고, 발현하는 날의 증상은 인증서 만료다 — 그날에도 로그는 SUCCESS 라고 적혀 있다.