docs(d4a): the deploy hook works — issuance to serving drops from 38m25s to 1-2s
D-4 named the fix and never ran it. Running it:
/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
nginx -t && nginx -s reload
certbot reports "Hook 'deploy-hook' ran", the master stays at PID 585 and
the worker is replaced 28829 -> 37252, and the served certificate changes.
One trap worth naming: certbot prefixes the hook output with "ran with error
output" whenever the hook writes anything to stderr, and nginx's routine
types_hash warning goes to stderr. Everything inside is success — "test is
successful", "signal process started". A monitor that greps for "error"
would read a working hook as a failure. The worker PID is what to check.
Timing needed a clock correction. test-server has NTP off and runs 106
seconds fast; the dev machine matches Google and the Let's Encrypt ACME
endpoint exactly. Corrected, the hook's nginx -t lands at 12:27:50 UTC
against the new certificate's SCT at 12:27:49.054 — one second. The
correction validates itself: uncorrected, the hook would have run 104
seconds before the certificate existed.
That same skew is why D-4's gap was reported 106 seconds short; corrected to
2305s in the previous commit.
Remaining and left alone: whether certbot-renew.timer performs a real
renewal, which cannot be tested for about 89 days.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3d7778bd3c
commit
e9baac21d5
@@ -0,0 +1,125 @@
|
||||
D-4a — deploy 훅이 실제로 nginx 를 reload 하는가
|
||||
==================================================
|
||||
수집: 2026-09-04 12:27 UTC (실제) · test-server
|
||||
|
||||
D-4 가 남긴 것
|
||||
--------------
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| deploy 훅을 넣으면 자동 반영되는가 | **미측정. 훅은 아직 넣지 않았다** |
|
||||
|
||||
D-4 는 처방을 적어두고 **검증하지 않았다.** 처방이 듣는지 모르는 채로
|
||||
"이렇게 고치면 된다"고 쓰는 것은 이 실험대가 계속 경계해 온 바로 그 실수다.
|
||||
|
||||
[주입] 훅 하나를 넣고 강제 갱신
|
||||
--------------------------------
|
||||
/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
|
||||
#!/bin/sh
|
||||
# D-4a: certbot deploy hook. 갱신이 실제로 일어났을 때만 실행된다.
|
||||
nginx -t && nginx -s reload
|
||||
|
||||
install -m755 /tmp/reload-nginx.sh /etc/letsencrypt/renewal-hooks/deploy/
|
||||
certbot renew --force-renewal
|
||||
|
||||
[결과] certbot 출력 — 훅이 실행됐다
|
||||
------------------------------------
|
||||
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
|
||||
Renewing an existing certificate for auth.hyeonworks.com and 2 more
|
||||
Hook 'deploy-hook' ran with error output:
|
||||
2026/09/04 21:29:36 [warn] 37250#37250: could not build optimal types_hash, …
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
2026/09/04 21:29:37 [warn] 37251#37251: could not build optimal types_hash, …
|
||||
2026/09/04 21:29:37 [notice] 37251#37251: signal process started
|
||||
|
||||
Congratulations, all renewals succeeded:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
|
||||
|
||||
★ 함정 — "Hook 'deploy-hook' ran with error output" 이라고 적혀 있지만
|
||||
실패가 아니다. nginx 가 types_hash 경고를 stderr 로 내보내서 certbot 이
|
||||
그것을 "error output" 으로 표시한 것뿐이다. 내용은 전부 성공이다:
|
||||
· nginx -t → "test is successful"
|
||||
· nginx -s reload → "signal process started"
|
||||
로그에서 'error' 를 grep 하면 성공한 훅을 실패로 오독하게 된다.
|
||||
|
||||
[검증 1] nginx 워커가 교체됐다
|
||||
-------------------------------
|
||||
전 (D-4a 기준선)
|
||||
585 1 ... Thu Sep 3 19:00:39 nginx: master process
|
||||
28829 585 ... Fri Sep 4 18:00:35 nginx: worker process ← D-4 에서 사람이 reload 한 것
|
||||
|
||||
후
|
||||
585 1 95412 Thu Sep 3 19:00:39 nginx: master process ← 마스터는 그대로
|
||||
37252 585 74 Fri Sep 4 21:29:36 nginx: worker process ← ★ 자동 교체됨
|
||||
|
||||
마스터 PID 는 유지되고 워커만 바뀌었다 = reload 의 서명. D-4 에서 세운
|
||||
진단 방법이 그대로 작동한다.
|
||||
|
||||
[검증 2] 서빙 인증서가 바뀌었다
|
||||
--------------------------------
|
||||
serial=06F3E0EF4D1BB03DE58130EAAD1176101373
|
||||
notBefore=Sep 4 11:29:18 2026 GMT
|
||||
notAfter=Dec 3 11:29:17 2026 GMT
|
||||
SAN: app1 / app2 / auth .hyeonworks.com
|
||||
|
||||
D-4 의 인증서(06C7CB…EA1D)에서 바뀌었다.
|
||||
|
||||
[검증 3] ★ 얼마나 빨랐나 — 시계 보정이 필요하다
|
||||
--------------------------------------------------
|
||||
훅 로그와 워커 lstart 는 **test-server 시계**다. 그런데 test-server 는
|
||||
NTP 동기가 꺼져 있다(NTPSynchronized=no).
|
||||
|
||||
외부 기준으로 두 시계를 검증했다.
|
||||
dev → Google 차이 +0초
|
||||
dev → Let's Encrypt ACME 차이 +0초
|
||||
test-server → Google 차이 -105초 (즉 test-server 가 105초 빠르다)
|
||||
ssh 왕복 왜곡 3회 측정: +106.1 / +106.1 / +106.1초 (안정적)
|
||||
|
||||
→ **dev 가 정확하고 test-server 가 106초 빠르다.**
|
||||
|
||||
독립 검증 — 새 인증서의 SCT (CT 로그가 서명한 시각, 자체 정확 시계):
|
||||
Signed Certificate Timestamp: Sep 4 12:27:49.054 2026 GMT
|
||||
Signed Certificate Timestamp: Sep 4 12:27:49.048 2026 GMT
|
||||
|
||||
보정한 타임라인 (전부 실제 UTC):
|
||||
12:27:49.05 인증서 발급 ← SCT. 외부 권위 기준
|
||||
12:27:50 훅 nginx -t ← 로그 21:29:36 KST − 106초
|
||||
12:27:50 새 워커 37252 기동 ← lstart 21:29:36 KST − 106초
|
||||
12:27:51 훅 nginx -s reload ← 로그 21:29:37 KST − 106초
|
||||
|
||||
★ 발급 → 서빙까지 **1~2초.**
|
||||
★ 그리고 보정이 자기 검증된다 — 독립 시계인 SCT 가 보정한 훅 시각의
|
||||
1초 앞에 정확히 놓인다. 보정하지 않으면 훅이 발급보다 104초 먼저
|
||||
실행된 것이 되어 물리적으로 불가능해진다.
|
||||
|
||||
════ D-4 와의 대조 ════
|
||||
|
||||
훅 없음 (D-4) 훅 있음 (D-4a)
|
||||
갱신 → 서빙 2305초 = 38분 25초 1~2초
|
||||
무엇이 reload 했나 사람 (`nginx -s reload`) certbot deploy 훅
|
||||
안 했다면 다음 nginx 재시작까지 해당 없음
|
||||
= 사실상 무기한
|
||||
차이 약 1150배
|
||||
|
||||
════ 부수 정정 — D-4 의 2199초는 틀렸다 ════
|
||||
|
||||
D-4 에서 적은 **2199초(36분 39초)** 는 `archive/cert2.pem` 의 mtime
|
||||
(test-server 시계)과 일련번호 관측 시각(dev 시계)을 **그대로 뺀** 값이었다.
|
||||
106초 보정을 적용하면:
|
||||
|
||||
새 인증서 디스크 기록 08:20:27 UTC (실제) ← mtime 17:22:13 KST − 106초
|
||||
실제 서빙 시작 08:58:52 UTC (실제) ← dev 관측, 보정 불필요
|
||||
공백 2305초 = 38분 25초
|
||||
|
||||
관련 문서를 전부 정정했다. **두 시계에서 온 값을 빼면서 그 사실을 적지
|
||||
않으면, 자릿수가 아니라 방향까지 틀릴 수 있다** — 여기서는 그 오차가
|
||||
100초 단위였고, 1~2초를 재는 D-4a 에서는 결과를 완전히 뒤집었다.
|
||||
|
||||
════ 결론 ════
|
||||
|
||||
1. **D-4 의 처방은 듣는다.** 훅 파일 하나로 발급 → 서빙이 38분에서 1~2초가 됐다.
|
||||
2. **certbot 출력의 "ran with error output" 을 실패로 읽으면 안 된다.**
|
||||
nginx 의 경고가 stderr 로 나갔을 뿐이며, 내용은 전부 성공이다.
|
||||
3. **진단 방법이 재확인됐다** — 마스터 PID 유지 + 워커 PID 교체 = reload.
|
||||
4. **시계를 섞어 빼면 안 된다.** test-server 는 NTP 가 꺼져 106초 빠르고,
|
||||
그 사실을 적지 않은 채 계산한 D-4 의 공백은 106초 짧았다.
|
||||
@@ -0,0 +1,17 @@
|
||||
Saving debug log to /var/log/letsencrypt/letsencrypt.log
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Renewing an existing certificate for auth.hyeonworks.com and 2 more
|
||||
Hook 'deploy-hook' ran with error output:
|
||||
2026/09/04 21:29:36 [warn] 37250#37250: could not build optimal types_hash, you should increase either types_hash_max_size: 1024 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
2026/09/04 21:29:37 [warn] 37251#37251: could not build optimal types_hash, you should increase either types_hash_max_size: 1024 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size
|
||||
2026/09/04 21:29:37 [notice] 37251#37251: signal process started
|
||||
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Congratulations, all renewals succeeded:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
|
||||
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@@ -0,0 +1,13 @@
|
||||
=== 실행 후 상태 (dev 머신에서 관측) ===
|
||||
수집 2026-09-04T12:29:05Z UTC
|
||||
|
||||
-- nginx 프로세스
|
||||
585 1 95412 Thu Sep 3 19:00:39 2026 nginx: master process /usr/bin/nginx
|
||||
37252 585 74 Fri Sep 4 21:29:36 2026 nginx: worker process
|
||||
|
||||
-- 서빙 인증서
|
||||
serial=06F3E0EF4D1BB03DE58130EAAD1176101373
|
||||
notBefore=Sep 4 11:29:18 2026 GMT
|
||||
notAfter=Dec 3 11:29:17 2026 GMT
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:app1.hyeonworks.com, DNS:app2.hyeonworks.com, DNS:auth.hyeonworks.com
|
||||
@@ -0,0 +1,17 @@
|
||||
# D-4a — deploy 훅 검증 증거
|
||||
|
||||
2026-09-04 12:27 UTC (실제 시각)
|
||||
해설: [`docs/experiment-d4a-deploy-hook.md`](../../experiment-d4a-deploy-hook.md)
|
||||
|
||||
| 파일 | 무엇을 보여주는가 |
|
||||
|---|---|
|
||||
| `01-hook-verified.txt` | **판정 전문.** 훅 실행 로그 · 워커 PID 교체 · 시계 보정과 SCT 교차검증 · D-4 와의 대조 |
|
||||
| `02-certbot-with-hook.txt` | `certbot renew --force-renewal` 원문. `Hook 'deploy-hook' ran` 과 `all renewals succeeded` |
|
||||
| `03-after-state.txt` | 실행 후 nginx 프로세스와 서빙 인증서 |
|
||||
|
||||
## 핵심 네 줄
|
||||
|
||||
1. **D-4 의 처방은 듣는다.** 훅 파일 하나로 발급 → 서빙이 **38분 25초에서 1~2초**가 됐다. 약 1150배.
|
||||
2. **`Hook 'deploy-hook' ran with error output` 을 실패로 읽으면 안 된다.** nginx 의 `types_hash` 경고가 stderr 로 나갔을 뿐이고 내용은 전부 성공이다 — `test is successful`, `signal process started`. 로그에서 `error` 를 grep 하면 성공한 훅을 실패로 오독한다.
|
||||
3. **진단 방법이 재확인됐다.** 마스터 585 는 그대로, 워커만 28829 → **37252** 로 교체. D-4 에서 세운 「마스터 유지 + 워커 교체 = reload」가 그대로 작동한다.
|
||||
4. **★ 시계를 섞어 빼면 안 된다.** test-server 는 NTP 가 꺼져 **106초 빠르다**(외부 기준 3회 측정). 이 사실을 적지 않고 계산한 D-4 의 공백은 106초 짧았고, 1~2초를 재는 D-4a 에서는 보정 없이는 **훅이 발급보다 104초 먼저 실행된 것**이 되어 물리적으로 불가능해진다. 보정은 새 인증서 SCT(`12:27:49.054 GMT`, CT 로그의 독립 시계)로 교차검증된다.
|
||||
@@ -224,7 +224,13 @@ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
|
||||
여부와 무관하게 매번 돌므로, 하루 두 번 쓸데없이 reload 하게 된다.
|
||||
|
||||
**확인 방법** — 훅을 넣었다고 끝이 아니다. 다음 강제 갱신 때 `nginx` 워커
|
||||
PID 가 바뀌는지 봐야 한다. 이 실험이 그 확인 절차다.
|
||||
PID 가 바뀌는지 봐야 한다.
|
||||
|
||||
> **★ 검증됨 ([D-4a](experiment-d4a-deploy-hook.md))** — 이 처방을 실제로
|
||||
> 넣고 강제 갱신했다. 워커가 `28829 → 37252` 로 자동 교체됐고,
|
||||
> **발급에서 서빙까지 1~2초**였다. 다만 certbot 출력의
|
||||
> `Hook 'deploy-hook' ran with error output` 은 **실패가 아니다** —
|
||||
> nginx 의 경고가 stderr 로 나갔을 뿐이다. 판정은 문구가 아니라 워커 PID 로 한다.
|
||||
|
||||
### 4-3. reload 는 무중단이다 — 측정됨
|
||||
|
||||
@@ -268,8 +274,8 @@ in-flight 전체 50건 중 종료코드 ≠ 0 은 **0건**이다.
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| `certbot-renew.timer` 가 **실제 갱신**을 하는가 | **미측정.** 약 58일 뒤에야 조건이 성립한다 |
|
||||
| deploy 훅을 넣으면 자동 반영되는가 | **미측정.** 훅은 아직 넣지 않았다 |
|
||||
| `certbot-renew.timer` 가 **실제 갱신**을 하는가 | **미측정.** 약 89일 뒤에야 조건이 성립한다(갱신으로 만료일이 밀렸다) |
|
||||
| deploy 훅을 넣으면 자동 반영되는가 | **측정 완료 →** [D-4a](experiment-d4a-deploy-hook.md). **듣는다** — 발급에서 서빙까지 **1~2초**(여기 38분 25초의 약 1150분의 1) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
# D-4a — deploy 훅은 정말 듣는가
|
||||
|
||||
브랜치 `feature/keycloak-d4a-deploy-hook` ·
|
||||
증거 [`docs/evidence/d4a-deploy-hook/`](evidence/d4a-deploy-hook/) ·
|
||||
2026-09-04 12:27 UTC (실제 시각)
|
||||
|
||||
D-4 는 결함을 찾고 **처방을 적어두고 검증하지 않았다.**
|
||||
처방이 듣는지 모르는 채 "이렇게 고치면 된다"고 쓰는 것은,
|
||||
이 실험대가 스물세 번 경계해 온 바로 그 실수다.
|
||||
|
||||
---
|
||||
|
||||
## 0. 결론부터
|
||||
|
||||
| 확인 | 결과 |
|
||||
|---|---|
|
||||
| 훅이 실행되는가 | **된다.** certbot 이 `Hook 'deploy-hook' ran` 을 찍는다 |
|
||||
| nginx 가 reload 되는가 | **된다.** 마스터 585 유지, 워커 28829 → **37252** |
|
||||
| **얼마나 빠른가** | **발급 → 서빙 1~2초** (D-4 는 38분 25초) |
|
||||
| 함정 | **`ran with error output` 은 실패가 아니다** |
|
||||
| **★ 부수 발견** | **test-server 시계가 106초 빠르다.** D-4 의 공백 수치를 정정했다 |
|
||||
|
||||
---
|
||||
|
||||
## 1. D-4 가 무엇을 남겼나
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| deploy 훅을 넣으면 자동 반영되는가 | **미측정. 훅은 아직 넣지 않았다** |
|
||||
|
||||
D-4 는 원인을 셋으로 특정했고(유닛에 `ExecStartPost` 없음 · 훅 3경로 비었음 ·
|
||||
nginx 플러그인 없음) 처방을 적었다. **그 처방을 실행해 본 적이 없다.**
|
||||
|
||||
---
|
||||
|
||||
## 2. 주입 — 파일 하나
|
||||
|
||||
```bash
|
||||
# /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
|
||||
#!/bin/sh
|
||||
nginx -t && nginx -s reload
|
||||
```
|
||||
|
||||
### 개념 — `deploy/` 와 `post/` 는 다르다
|
||||
|
||||
| 디렉터리 | 언제 실행되나 |
|
||||
|---|---|
|
||||
| `pre/` | 갱신 **시도** 전 |
|
||||
| **`deploy/`** | **실제로 갱신된 인증서가 있을 때만** |
|
||||
| `post/` | 갱신 여부와 **무관하게** 매번 |
|
||||
|
||||
**왜 `deploy/` 인가.** 타이머는 하루 두 번 돈다. `post/` 에 넣으면 갱신이
|
||||
없는 날에도 하루 두 번 nginx 를 reload 하게 된다 — 아무 이득 없이 워커만
|
||||
갈아치우는 셈이다. `deploy/` 는 **`RENEWED_LINEAGE` 가 있을 때만** 돈다.
|
||||
|
||||
**없거나 틀리면.** D-4 가 측정한 그대로다 — 갱신은 성공하고 서빙은 안 바뀐다.
|
||||
그리고 그 상태로 타이머는 `SUCCESS` 를 찍는다.
|
||||
|
||||
**확인.**
|
||||
```bash
|
||||
sudo ls -la /etc/letsencrypt/renewal-hooks/deploy/ # 비어 있으면 아무도 안 부른다
|
||||
```
|
||||
|
||||
### 왜 `nginx -t &&` 를 앞에 두는가
|
||||
|
||||
설정이 깨진 상태에서 `nginx -s reload` 를 보내면 마스터가 새 워커를 못 띄운다.
|
||||
`-t` 로 먼저 검사하고 통과할 때만 reload 한다. 실패하면 **옛 워커가 그대로
|
||||
서비스를 계속한다** — 인증서는 안 바뀌지만 서비스는 죽지 않는다.
|
||||
|
||||
### 실행
|
||||
|
||||
```bash
|
||||
install -m755 /tmp/reload-nginx.sh /etc/letsencrypt/renewal-hooks/deploy/
|
||||
certbot renew --force-renewal
|
||||
```
|
||||
|
||||
> **이 스크립트는 실험 자동화가 아니라 시험 대상 자체다.**
|
||||
> certbot 은 훅을 파일로만 받는다. 절차는 전부 명령어로 되어 있다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 결과 — certbot 이 훅을 실행했다
|
||||
|
||||
```
|
||||
Processing /etc/letsencrypt/renewal/auth.hyeonworks.com.conf
|
||||
Renewing an existing certificate for auth.hyeonworks.com and 2 more
|
||||
Hook 'deploy-hook' ran with error output:
|
||||
2026/09/04 21:29:36 [warn] 37250#37250: could not build optimal types_hash, …
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
2026/09/04 21:29:37 [warn] 37251#37251: could not build optimal types_hash, …
|
||||
2026/09/04 21:29:37 [notice] 37251#37251: signal process started
|
||||
|
||||
Congratulations, all renewals succeeded:
|
||||
/etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem (success)
|
||||
```
|
||||
|
||||
### ★ 함정 — "ran with error output" 은 실패가 아니다
|
||||
|
||||
certbot 은 훅이 **stderr 에 무엇이라도 쓰면** 이 문구를 붙인다.
|
||||
여기 stderr 로 나간 것은 nginx 의 `types_hash` 경고뿐이고, 내용은 전부 성공이다.
|
||||
|
||||
| 줄 | 실제 의미 |
|
||||
|---|---|
|
||||
| `[warn] could not build optimal types_hash` | nginx 의 일반 경고. 갱신과 무관 |
|
||||
| `nginx: … test is successful` | **`nginx -t` 통과** |
|
||||
| `[notice] signal process started` | **`nginx -s reload` 가 신호를 보냄** |
|
||||
|
||||
> 로그에서 `error` 를 grep 하는 감시를 걸어두면 **성공한 훅을 실패로 오독한다.**
|
||||
> 실패를 확인하려면 문구가 아니라 **워커 PID 를 봐야 한다.**
|
||||
|
||||
---
|
||||
|
||||
## 4. 검증 — 워커가 교체됐다
|
||||
|
||||
| | PID | 기동 시각 (test-server 시계) |
|
||||
|---|---|---|
|
||||
| 마스터 (전·후 동일) | **585** | Thu Sep 3 19:00:39 |
|
||||
| 워커 (전) | 28829 | Fri Sep 4 18:00:35 ← D-4 에서 **사람이** reload |
|
||||
| **워커 (후)** | **37252** | Fri Sep 4 21:29:36 ← **훅이 자동으로** |
|
||||
|
||||
**마스터 PID 는 유지되고 워커만 바뀌었다.** D-4 에서 "reload 되었는가"를
|
||||
판정하려고 세운 방법이 그대로 작동한다.
|
||||
|
||||
서빙 인증서도 바뀌었다.
|
||||
|
||||
```
|
||||
serial=06F3E0EF4D1BB03DE58130EAAD1176101373
|
||||
notBefore=Sep 4 11:29:18 2026 GMT
|
||||
notAfter=Dec 3 11:29:17 2026 GMT
|
||||
SAN: app1 / app2 / auth .hyeonworks.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. ★ 얼마나 빨랐나 — 그리고 시계 문제
|
||||
|
||||
훅 로그와 워커 `lstart` 는 **test-server 시계**다. 그런데 그 시계는
|
||||
NTP 동기가 꺼져 있다.
|
||||
|
||||
```
|
||||
dev → Google +0초
|
||||
dev → Let's Encrypt ACME +0초
|
||||
test-server → Google −105초 (test-server 가 빠르다)
|
||||
ssh 왕복 3회 측정 +106.1 / +106.1 / +106.1초
|
||||
```
|
||||
|
||||
**dev 가 정확하고 test-server 가 106초 빠르다.**
|
||||
|
||||
### 독립 검증 — SCT
|
||||
|
||||
새 인증서에 박힌 Certificate Transparency 서명 시각은 **CT 로그의 자체
|
||||
시계**로 찍힌다. 양쪽 어느 것과도 무관한 제3의 기준이다.
|
||||
|
||||
```
|
||||
Signed Certificate Timestamp: Sep 4 12:27:49.054 2026 GMT
|
||||
Signed Certificate Timestamp: Sep 4 12:27:49.048 2026 GMT
|
||||
```
|
||||
|
||||
보정한 타임라인 (전부 실제 UTC):
|
||||
|
||||
```
|
||||
12:27:49.05 인증서 발급 ← SCT (외부 권위 기준)
|
||||
12:27:50 훅 nginx -t ← 로그 21:29:36 KST − 106초
|
||||
12:27:50 새 워커 37252 기동 ← lstart 21:29:36 KST − 106초
|
||||
12:27:51 훅 nginx -s reload ← 로그 21:29:37 KST − 106초
|
||||
```
|
||||
|
||||
**발급에서 서빙까지 1~2초.**
|
||||
|
||||
그리고 **보정이 자기 검증된다** — 독립 시계인 SCT 가 보정한 훅 시각의 1초
|
||||
앞에 정확히 놓인다. 보정하지 않으면 훅이 발급보다 **104초 먼저** 실행된 것이
|
||||
되어 물리적으로 불가능해진다.
|
||||
|
||||
### 부수 정정 — D-4 의 2199초는 틀렸다
|
||||
|
||||
D-4 에서 적은 **2199초(36분 39초)** 는 `archive/cert2.pem` 의 mtime
|
||||
(test-server 시계)과 일련번호 관측(dev 시계)을 **그대로 뺀** 값이었다.
|
||||
|
||||
| | 시각 (실제 UTC) |
|
||||
|---|---|
|
||||
| 새 인증서 디스크 기록 | **08:20:27** ← mtime 17:22:13 KST − 106초 |
|
||||
| 실제 서빙 시작 | 08:58:52 ← dev 관측, 보정 불필요 |
|
||||
| **공백** | **2305초 = 38분 25초** |
|
||||
|
||||
관련 문서를 전부 정정했다.
|
||||
|
||||
> **두 시계에서 온 값을 빼면서 그 사실을 적지 않으면, 자릿수가 아니라
|
||||
> 방향까지 틀릴 수 있다.** D-4 에서는 오차가 106초여서 결론이 안 바뀌었지만,
|
||||
> 1~2초를 재는 D-4a 에서는 결과를 완전히 뒤집었다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 대조
|
||||
|
||||
| | 훅 없음 (D-4) | **훅 있음 (D-4a)** |
|
||||
|---|---|---|
|
||||
| 갱신 → 서빙 | **2305초 = 38분 25초** | **1~2초** |
|
||||
| 무엇이 reload 했나 | 사람이 친 `nginx -s reload` | certbot deploy 훅 |
|
||||
| 아무도 안 했다면 | 다음 nginx 재시작까지 = **사실상 무기한** | 해당 없음 |
|
||||
| 차이 | | **약 1150배** |
|
||||
|
||||
---
|
||||
|
||||
## 7. 남는 것
|
||||
|
||||
| 항목 | 상태 |
|
||||
|---|---|
|
||||
| `certbot-renew.timer` 가 **실제 갱신**을 하는가 | **미측정.** 만료 30일 전(약 89일 뒤)에야 조건이 성립한다 |
|
||||
|
||||
훅은 `--force-renewal` 로 검증했다. **타이머가 스스로 갱신하는 경로**는
|
||||
시간이 지나야 시험할 수 있다. 다만 그 경로도 같은 `certbot renew` 를 부르고
|
||||
같은 `deploy/` 훅을 실행하므로, 남은 미지수는 "타이머가 뜨는가" 하나다 —
|
||||
그리고 그것은 D-4 에서 이미 확인했다(오늘 두 번 `status=0/SUCCESS`).
|
||||
|
||||
---
|
||||
|
||||
## 8. 재현 절차 (명령어)
|
||||
|
||||
```bash
|
||||
# ── 0. 기준선 — 이 워커 PID 가 바뀌는지가 판정이다
|
||||
ssh test-server "ps -eo pid,ppid,etimes,lstart,args | grep 'nginx:' | grep -v grep"
|
||||
echo | openssl s_client -connect auth.hyeonworks.com:443 \
|
||||
-servername auth.hyeonworks.com 2>/dev/null | openssl x509 -noout -serial -dates
|
||||
|
||||
# ── 1. ★ 시계 왜곡을 먼저 잰다. 나중에 재면 값을 해석할 수 없다
|
||||
for i in 1 2 3; do
|
||||
A=$(date -u +%s.%N); B=$(ssh test-server 'date -u +%s.%N'); C=$(date -u +%s.%N)
|
||||
python3 -c "print(f'왜곡 {$B-($A+$C)/2:+.1f}초')"
|
||||
done
|
||||
# 어느 쪽이 맞는지는 외부 기준으로 가른다
|
||||
for H in https://www.google.com https://acme-v02.api.letsencrypt.org/directory; do
|
||||
A=$(date -u +%s)
|
||||
D=$(curl -sI --max-time 10 "$H" | grep -i '^date:' | sed 's/^[Dd]ate: *//' | tr -d '\r')
|
||||
C=$(date -u +%s)
|
||||
python3 -c "print(f'$H 차이 {$(date -u -d \"$D\" +%s)-($A+$C)//2:+d}초')"
|
||||
done
|
||||
|
||||
# ── 2. 훅을 sudo 없는 곳에 미리 만들어 둔다 (사람이 칠 명령을 짧게)
|
||||
ssh test-server "printf '#!/bin/sh\nnginx -t && nginx -s reload\n' > /tmp/reload-nginx.sh"
|
||||
|
||||
# ── 3. 주입 — 여기만 sudo 가 필요하다
|
||||
ssh -t test-server 'sudo sh -c "install -m755 /tmp/reload-nginx.sh /etc/letsencrypt/renewal-hooks/deploy/ && certbot renew --force-renewal > /tmp/d4a-renew.txt 2>&1; chmod 644 /tmp/d4a-renew.txt; tail -25 /tmp/d4a-renew.txt"'
|
||||
|
||||
# ── 4. 판정 — 문구가 아니라 워커 PID 로 본다
|
||||
ssh test-server "ps -eo pid,ppid,etimes,lstart,args | grep 'nginx:' | grep -v grep"
|
||||
# 마스터 PID 그대로 + 워커 PID 바뀜 = reload 됨
|
||||
|
||||
# ── 5. 얼마나 빨랐나 — SCT 가 발급 시각의 외부 기준이다
|
||||
echo | openssl s_client -connect auth.hyeonworks.com:443 \
|
||||
-servername auth.hyeonworks.com 2>/dev/null \
|
||||
| openssl x509 -noout -ext ct_precert_scts | grep Timestamp
|
||||
# 훅 로그 시각에서 왜곡을 빼고 SCT 와 비교한다
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 증거 파일
|
||||
|
||||
| 파일 | 종류 | 무엇을 보여주는가 |
|
||||
|---|---|---|
|
||||
| [`01-hook-verified.txt`](evidence/d4a-deploy-hook/01-hook-verified.txt) | 터미널 | 판정 전문 · 시계 보정과 SCT 교차검증 · D-4 대조 |
|
||||
| [`02-certbot-with-hook.txt`](evidence/d4a-deploy-hook/02-certbot-with-hook.txt) | 터미널 | `certbot renew --force-renewal` 원문 |
|
||||
| [`03-after-state.txt`](evidence/d4a-deploy-hook/03-after-state.txt) | 터미널 | 실행 후 nginx 프로세스 · 서빙 인증서 |
|
||||
|
||||
파일별 상세는 [`evidence/d4a-deploy-hook/README.md`](evidence/d4a-deploy-hook/README.md).
|
||||
@@ -29,6 +29,7 @@
|
||||
| **D-4** | 인증서 갱신 | `...d4-certificate-renewal` | **갱신은 됐는데 38분 25초 반영 안 됨** (훅 3경로 전부 비었음). reload 자체는 **무중단**(8856건 0실패) |
|
||||
| **A-7a** | volatile refresh 500 원인 확정 | `...a7a-volatile-cause` | **가설(`REVOKED_TOKEN`)은 틀렸다** — `CLIENT_SCOPE_CLIENT` 조회다. **A-7 의 표는 캐시 온도에 따라 400/500/200** |
|
||||
| **B-7a** | 고아 세션 정리 | `...b7a-orphan-session` | **지울 수 있다** — 프록시가 못 할 뿐. TTL 역산으로 고아만 **1초 오차**로 골라낸다 |
|
||||
| **D-4a** | deploy 훅 검증 | `...d4a-deploy-hook` | **처방이 듣는다** — 발급→서빙 **1~2초**(D-4 는 38분 25초). test-server 시계가 **106초 빨라** D-4 수치를 정정 |
|
||||
| **후속** | 미측정 3항목 채우기 | `...followup-untested-items` | **셋 다 완료.** 정방향 업그레이드 무중단 · **롤백 불가는 조건부였다** · role 변경은 요청으로 반영 안 됨 · **D-4 갱신 38분 25초 미반영** |
|
||||
|
||||
## 시각 자료
|
||||
|
||||
Reference in New Issue
Block a user