Pangolin + Authentik으로 홈서버 SSO 구축하기
진짜 너무 불편하네..
홈서버에 서비스를 하나둘 올리다 보면 어느 순간 깨닫습니다. "나 지금 몇 개나 로그인하고 있는 거지?"
파일 관리자, 다운로드 클라이언트, 모니터링 대시보드, DNS 관리 패널… 각각 자체 인증을 갖고 있고, 비밀번호도 제각각이고, 세션 만료 시점도 다릅니다. 외부에서 접속할 때마다 서비스별로 로그인을 반복하는 게 점점 귀찮아졌어요.
"한 번 로그인하면 전부 통하게 할 수 없을까?" 가 출발점이었습니다.
사실 이미 OIDC 같은 개념이 있는건 충분히 알고 있으니 이제 홈서버에 적용하는 일만 남은거죠.
선택지: Authelia vs Authentik
셀프호스팅 SSO 솔루션으로 가장 많이 언급되는 두 가지가 있습니다.
Authelia — 가볍고 설정이 YAML 기반. 2FA 지원. 단순한 ForwardAuth 용도로는 최적. 하지만 OIDC Provider 기능이 제한적이고, 관리 UI가 없어서 설정 변경할 때마다 파일을 수정하고 재시작해야 합니다.
Authentik — 무겁지만 풀스택 IdP. OIDC/SAML/LDAP 전부 지원. 웹 UI에서 Provider, Application, Flow를 관리할 수 있고, 사용자/그룹 관리도 됩니다. 대신 PostgreSQL이 필요하고 메모리를 꽤 먹어요.
저는 Authentik을 골랐습니다. 이유는:
- Pangolin이 OIDC IdP 연동을 네이티브로 지원함
- 나중에 서비스별로 세밀한 접근 제어가 필요할 수 있음 (그룹별 허용 등)
- 관리 UI가 있어야 설정 바꿀 때 SSH 안 타도 됨
메모리 부담이 걱정이었는데, server + worker + postgres 합쳐서 약 400~500MB 정도. 8GB RAM 서버에서 감당 가능한 수준이었습니다.
전체 구조

(이번에도 GPT 가 열일해서 도식도를 잘 그려줬네요...)
핵심은 Badger라는 Traefik 플러그인입니다. Pangolin이 자체적으로 만든 건데, 요청이 들어올 때 세션 쿠키를 확인하고, 유효하지 않으면 Pangolin 로그인 페이지로 보냅니다. 기존의 ForwardAuth 방식과 비슷하지만, Pangolin 생태계 안에서 더 깔끔하게 동작해요.
Step 1: Authentik 배포
Docker Compose로 올립니다. 구조는 단순해요.
# docker-compose.yml (예시)
services:
postgresql:
image: postgres:16-alpine
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: ${PG_PASS}
volumes:
- db_data:/var/lib/postgresql/data
server:
image: ghcr.io/goauthentik/server:2025.10
command: server
environment:
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${SECRET_KEY}
ports:
- "7080:9000"
depends_on:
postgresql:
condition: service_healthy
worker:
image: ghcr.io/goauthentik/server:2025.10
command: worker
environment:
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${SECRET_KEY}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
주의할 점: server 컨테이너는 리버스 프록시 네트워크에 붙여야 합니다. Traefik이 Authentik에 접근할 수 있어야 하니까요. worker는 내부 네트워크만 있으면 됩니다.
초기 설정은 http://서버IP:7080/if/flow/initial-setup/으로 접속해서 관리자 계정을 만듭니다.
Step 2: Authentik에서 OIDC Provider 생성
Authentik 관리 화면에서:
-
Provider 생성 (OAuth2/OpenID Provider)
- Name:
Pangolin SSO - Authorization flow:
default-provider-authorization-implicit-consent(매번 동의 안 물어보게) - Client ID: 자동 생성됨 (복사해둘 것)
- Client Secret: 자동 생성됨 (복사해둘 것)
- Redirect URI:
https://pangolin.example.com/api/v1/auth/idp/oidc/2/callback
- Name:
-
Application 생성
- Name:
Pangolin - Slug:
pangolin - Provider: 위에서 만든
Pangolin SSO선택
- Name:
여기서 Redirect URI가 중요합니다. Pangolin이 IdP를 등록하면 콜백 URL을 알려주는데, 그걸 정확히 맞춰야 해요. 안 맞으면 인증 후 "redirect_uri mismatch" 에러가 납니다.
Step 3: Pangolin에 IdP 등록
Pangolin 대시보드 → Settings → Identity Providers에서 OIDC를 추가합니다.
- Name:
Sign in with Authentik - Client ID / Secret: Authentik에서 복사한 값
- Auth URL:
https://auth.example.com/application/o/authorize/ - Token URL:
https://auth.example.com/application/o/token/ - Identifier Path:
email - Scopes:
openid profile email - Auto Provision: 켜기 (Authentik 계정이 있으면 Pangolin에 자동 등록)
등록하면 Pangolin 로그인 페이지에 "Sign in with Authentik" 버튼이 생깁니다.
Step 4: 리소스별 SSO 적용
Pangolin에서 리소스(서비스)를 만들 때 SSO 보호를 켜면, 해당 서브도메인에 접근할 때 Badger 플러그인이 세션을 확인합니다.
예를 들어:
files.example.com→ SSO 켜짐 → 로그인 필요blog.example.com→ SSO 꺼짐 → 누구나 접근 가능
SSO를 켜면 안 되는 서비스도 있습니다:
- Authentik 자체 (로그인 페이지에 접근을 못하면 순환 참조)
- API를 외부에서 호출하는 서비스 (Ghost API 등)
- WebDAV/SFTP처럼 브라우저가 아닌 클라이언트가 접근하는 서비스
| 서비스 | SSO | 이유 |
|---|---|---|
| 파일 관리자 | ✅ | 민감한 데이터 |
| 다운로드 클라이언트 | ✅ | 외부 노출 위험 |
| 모니터링 대시보드 | ✅ | 서버 정보 노출 |
| DNS 관리 | ✅ | 설정 변경 가능 |
| 블로그 | ❌ | 공개 콘텐츠 |
| 음악 공유 앱 | ❌ | 자체 인증 있음 |
| 이력서 | ❌ | 공개 페이지 |
하다보니.. Ghost API와 SSO 충돌
블로그에 SSO를 켰더니 Ghost Admin API 호출이 전부 막혔습니다. 자동 발행 스크립트가 401을 뱉더라고요.
Pangolin의 Resource Rules로 해결했습니다. 특정 경로만 SSO를 우회하는 규칙을 추가할 수 있어요.
규칙 1: /ghost/api/* → PASS (SSO 우회)
규칙 2: /ghost/* → ACCEPT (SSO 적용)
규칙 3: * → ACCEPT (SSO 적용)
우선순위가 중요합니다. API 경로를 먼저 매칭해서 통과시키고, 나머지 Ghost 관리 페이지는 SSO로 보호합니다. 이 순서가 바뀌면 API도 막혀버려요.
ForwardAuth vs Badger: 뭐가 다른가
Pangolin 이전에는 Traefik + ForwardAuth 미들웨어로 SSO를 구현하는 게 일반적이었습니다.
# ForwardAuth 방식 (예시)
middlewares:
authentik:
forwardAuth:
address: "http://authentik:9000/outpost.goauthentik.io/auth/traefik"
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-email
trustForwardHeader: true
이 방식도 동작하지만, 서비스마다 미들웨어를 수동으로 붙여야 합니다. Traefik 동적 설정 파일을 수정하거나, Docker 라벨을 추가하거나.
Pangolin + Badger 방식은 UI에서 토글 하나로 SSO를 켜고 끌 수 있습니다. 내부적으로는 비슷한 일을 하지만, 관리 포인트가 Pangolin 대시보드 하나로 모입니다.
다만 Pangolin의 Badger 방식은 Pangolin 생태계에 종속됩니다. Traefik을 직접 관리하는 게 편한 사람은 ForwardAuth가 나을 수도 있어요. 저는 서비스가 20개 가까이 되니까 UI에서 관리하는 게 압도적으로 편했습니다.
CrowdSec 연동: SSO 위에 한 겹 더
SSO만으로는 부족한 부분이 있습니다. 로그인 페이지 자체에 대한 브루트포스 공격이나, 알려진 악성 IP의 접근을 막으려면 별도 프로텍션 레이어가 필요해요.
CrowdSec을 Traefik 플러그인으로 붙였습니다. 커뮤니티 기반 IP 블랙리스트 + 로컬 로그 분석으로 의심스러운 접근을 차단합니다.
이것만 하더라도 귀찮은 탐지봇 녀석들 엿을 먹여줄 수 있습니다..
# Traefik 동적 설정 (예시)
middlewares:
crowdsec:
plugin:
crowdsec-bouncer:
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec:8080
crowdsecLapiKey: ${BOUNCER_API_KEY}
Pangolin의 Middleware Manager를 통해 특정 리소스에만 CrowdSec을 적용할 수도 있습니다. 저는 외부 노출되는 서비스 전부에 걸어뒀어요. 음.. 이런게 왜 필요한지에 대한 상세는
나중에 또 다른 포스팅에서 다뤄보면 좋을거같습니다.
돌이켜보면
좋았던 점: 한 번 로그인하면 SSO가 적용된 모든 서비스에 세션이 유지됩니다. 모바일에서도 브라우저 하나로 전부 접근 가능. 새 서비스 추가할 때 토글 하나면 끝.
아쉬웠던 점: Authentik이 생각보다 무겁습니다. 초기 설정도 Provider → Application → Pangolin IdP 등록 → 콜백 URL 맞추기까지 삽질이 좀 있었어요. 특히 Redirect URI 불일치 에러는 디버깅이 짜증났습니다.
다시 한다면: 서비스가 5개 이하였으면 Authelia로 갔을 겁니다. 가볍고 설정도 단순하니까요. 하지만 10개 넘어가면 Authentik의 웹 UI와 OIDC 네이티브 지원이 확실히 편합니다. 서비스 수에 따라 선택이 달라지는 문제라고 생각해요.