D-4 — 갱신은 되는데 nginx 가 집지 않는다: 배포 훅 부재
=========================================================
수집: 2026-09-04 17:2x KST · test-server (sudo 없이 읽을 수 있는 범위)

발단
----
사용자가 `sudo certbot renew --force-renewal` 을 실행했다고 알려왔다.
그런데 밖에서 본 인증서가 바뀌지 않았다.

  serial=0520BB6416D569E26697B1691440F523B853
  notBefore=Sep  3 00:47:23 2026 GMT     ← 어제 것 그대로
  notAfter=Dec  2 00:47:22 2026 GMT

일련번호 감시 161표본(약 13분) 동안 단 한 번도 바뀌지 않았다.

[증거 1] nginx 는 reload 된 적이 없다
--------------------------------------
  $ ps -eo pid,ppid,etimes,lstart,args | grep nginx
    585    1  80529  Thu Sep  3 19:00:39 2026  nginx: master process /usr/bin/nginx
    586  585  80529  Thu Sep  3 19:00:39 2026  nginx: worker process

읽는 법 — nginx 의 reload 는 마스터를 유지한 채 **워커만 새로 띄운다.**
  · 마스터 585, 워커 586 = 마스터 기동 직후의 첫 fork
  · 둘의 lstart 가 같고 etimes 도 같다(80529초 = 22.4시간)
  → reload 가 한 번도 없었다. 22.4시간 전 기동 그대로다.

인증서 파일이 새로 써졌더라도, nginx 는 기동 시점에 읽어 메모리에 들고 있다.
reload 가 없으면 **옛 인증서를 계속 서빙한다.**

[증거 2] ★ 갱신 유닛에 reload 가 없다
--------------------------------------
  $ systemctl cat certbot-renew.service
  # /usr/lib/systemd/system/certbot-renew.service
  [Unit]
  Description=Renew certificates acquired via Certbot
  [Service]
  Type=oneshot
  ExecStart=/usr/bin/certbot -q renew
  PrivateTmp=true

`ExecStart` 가 전부다. **ExecStartPost 도, --deploy-hook 도 없다.**
배포판(Arch)이 넣어준 기본 유닛이 그렇다.

  $ systemctl cat certbot-renew.timer
  OnCalendar=*-*-* 00/12:00:00
  RandomizedDelaySec=12h
  Persistent=true

[증거 3] 타이머는 정상 동작한다 — 그래서 더 위험하다
------------------------------------------------------
  $ systemctl status certbot-renew.service
  Active: inactive (dead) since Fri 2026-09-04 17:04:11 KST
  Process: 28452 ExecStart=/usr/bin/certbot -q renew (code=exited, status=0/SUCCESS)

  $ journalctl -u certbot-renew.service --since today
  Sep 04 03:19:39 Starting Renew certificates acquired via Certbot...
  Sep 04 03:19:41 Finished Renew certificates acquired via Certbot.
  Sep 04 17:04:09 Starting Renew certificates acquired via Certbot...
  Sep 04 17:04:11 Finished Renew certificates acquired via Certbot.

오늘 두 번 돌았고 두 번 다 status=0/SUCCESS 다. 만료까지 88일 남아 실제
갱신은 하지 않았으므로 **아직은** 아무 문제가 없다.

[증거 4] nginx 가 무엇을 물고 있나
-----------------------------------
  $ grep -rn ssl_certificate /etc/nginx/
  /etc/nginx/sites-available/keycloak-lab:18: ssl_certificate     /etc/letsencrypt/live/auth.hyeonworks.com/fullchain.pem;
  /etc/nginx/sites-available/keycloak-lab:19: ssl_certificate_key /etc/letsencrypt/live/auth.hyeonworks.com/privkey.pem;

`live/` 는 심볼릭 링크다. certbot 이 갱신하면 링크가 새 `archive/` 파일을
가리키도록 바뀐다 — **파일 경로는 그대로인데 내용이 바뀐다.** 그래서 nginx
설정은 고칠 필요가 없고, 바로 그 때문에 "설정이 그대로니 괜찮다"고 착각하기 쉽다.
필요한 것은 설정 변경이 아니라 **reload** 다.

무엇을 확인하지 못했나
----------------------
`/etc/letsencrypt/renewal-hooks/deploy/` 에 스크립트가 있으면 유닛 파일과
무관하게 실행된다. 이 디렉터리는 root 전용이라 읽지 못했다.

  $ ls -laR /etc/letsencrypt/renewal-hooks/
  ls: cannot access '/etc/letsencrypt/renewal-hooks/': Permission denied

따라서 두 갈래가 남는다.
  (a) 강제 갱신이 실패했다 → 파일도 안 바뀌었고 reload 도 없었다
  (b) 강제 갱신이 성공했다 → 파일은 바뀌었는데 훅이 없어 reload 가 안 됐다

(b) 라면 이 실험대에서 가장 값진 발견이다. **"갱신 성공"과 "새 인증서 서빙"이
다른 사건**이라는 것을, 88일 뒤가 아니라 지금 잡은 것이기 때문이다.

가리는 방법 — root 권한 한 번:
  sudo certbot certificates            # notAfter 가 오늘+90일이면 (b)
  sudo ls -la /etc/letsencrypt/archive/auth.hyeonworks.com/
  sudo tail -60 /var/log/letsencrypt/letsencrypt.log

곁다리로 확인한 것 — CT 로그로는 가릴 수 없다
-----------------------------------------------
발급 사실은 Certificate Transparency 에 남으므로 sudo 없이 확인할 수 있을 것
같았다. 실제로 서빙 중인 인증서에는 SCT 가 2개 박혀 있다.

  CT Precertificate SCTs:
    Log ID: C2:31:7E:57:...:52:CD   Timestamp: Sep  3 01:45:53.183 2026 GMT
    Log ID: 46:AF:86:3D:...:50:5F   Timestamp: Sep  3 01:45:53.352 2026 GMT

그런데 crt.sh 는 이 이름을 모른다.

  $ curl -s 'https://crt.sh/?q=auth.hyeonworks.com&output=json'
  []                                            ← 0건
  $ curl -s 'https://crt.sh/?q=hyeonworks.com&output=json'
  13건, 최신 not_before=2026-08-11              ← auth 는 없다

**인증서에 SCT 가 박혀 있다는 것과 crt.sh 가 그것을 색인했다는 것은 다르다.**
관측 도구가 진실의 부분집합만 본다는, A-2 의 `up` 지표와 같은 종류의 함정이다.
