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>
Keycloak Authentication Patterns
The 39-branch implementation registry is documented in
docs/keycloak-branch-index.md.
Google brokering has a credential-free local OIDC harness and an opt-in
real-Google profile described in
docs/google-idp-brokering.md.
Keycloak을 중심으로 네 가지 브라우저 인증 통합 패턴을 같은 로컬 인프라에서 비교하는 학습 프로젝트입니다.
- AP1: Browser-based OAuth Client (SPA direct + Resource Server)
- AP2: Token-Mediating Backend
- AP3: Backend-for-Frontend (BFF)
- AP4: Edge forward-auth
세션 저장소·refresh token 경쟁·장애 복구는 네 패턴을 가로지르는 별도 축으로
develop-keycloak-session-store 브랜치에서 진행합니다. 계획과 진행 상황은
docs/session-store-lab-roadmap.md에
있습니다.
현재 develop의 공통 baseline은 Keycloak, PostgreSQL, Spring Boot API,
nginx를 Docker Compose로 실행하는 토대입니다. 패턴별 구현은 이 baseline
위에서 별도 브랜치로 진행합니다.
요구 사항
- Docker Engine
- Docker Compose
curl
로컬 Java나 Maven은 필요하지 않습니다. Spring Boot 빌드와 테스트는 Maven 컨테이너에서 수행합니다.
시작
cp .env.example .env
docker compose up --build -d
./scripts/verify-stack.sh
기본 주소는 다음과 같습니다.
| 구성 요소 | 주소 |
|---|---|
| Keycloak | http://localhost:8080 |
| Spring Boot API | http://localhost:8081 |
| nginx | http://localhost:8088 |
host의 80번 포트를 쓸 수 있는 single-EC2 환경에서는 .env의
NGINX_PORT=80으로 변경할 수 있습니다.
상태 확인
docker compose ps
docker compose logs -f keycloak
curl http://localhost:8088/api/public
curl -i http://localhost:8088/api/me
/api/public은 200, 인증 정보가 없는 /api/me는 401이 정상입니다.
환경 초기화
PostgreSQL과 Keycloak data volume을 제거하고 realm import부터 다시 검증하려면 다음 한 줄을 사용합니다.
docker compose down -v && docker compose up --build -d
start-dev와 로컬 HTTP 설정은 학습 전용입니다. 운영 환경에서는
optimized Keycloak image, HTTPS, 엄격한 hostname 및 외부 secret store를
사용해야 합니다.
Realm baseline
keycloak/import/keycloak-patterns-realm.json은 시작 시 자동 import됩니다.
하나의 keycloak-patterns realm 안에서 패턴마다 client를 분리합니다.
| Client | 유형 | 패턴 |
|---|---|---|
spa-public |
public + PKCE S256 | AP1 |
token-mediating-confidential |
confidential | AP2 |
bff-confidential |
confidential | AP3 |
edge-proxy |
confidential | AP4 |
confidential client secret과 테스트 사용자 password는 JSON에 평문으로
저장하지 않습니다. JSON에는 ${ENVIRONMENT_VARIABLE} placeholder만
커밋하고, Keycloak 26.7.0이 import 시 .env 값을 주입합니다.
python3 scripts/validate-realm.py
docker compose down -v
docker compose up -d --wait
./scripts/verify-realm.sh
실행 중인 DB에서 CLI realm export를 재현하려면 다음 명령을 사용합니다. Keycloak을 잠시 중지하고 export한 뒤 자동으로 다시 올립니다.
./scripts/export-realm.sh
runtime export에는 실제 client secret과 credential hash가 포함될 수 있어
gitignored build/keycloak-export/에 권한 0600으로만 저장됩니다.