用 Caddy 做反向代理 + 用 Authentik 管理帳號(以 Gitea 為例)

2026-02-01
5 min read
Featured Image

本篇會帶你用 Caddy 當反向代理(Reverse Proxy)與自動 TLS,並用 Authentik 當身分提供者(IdP / SSO)來管理帳號;最後以 Gitea 當範例,完成「反向代理 + 單一登入(OIDC)」整合。

這張可愛的圖是 Gemini 生成的。

本文參考文件:

範例網域:

  • Authentik:auth.www.myapp.example
  • Gitea:git.www.myapp.example

(以上都以 www.myapp.example 為「示意域名」。實作時請換成你自己的可解析網域,並確保 DNS A/AAAA 指向你的主機 IP。)

本文所有服務都以 Docker 方式部署,並以「分開三份 docker-compose」為主。為了降低複雜度,本文不使用共用 Docker network;Caddy 會透過 host.docker.internal 反向代理到宿主機上已 publish 的服務埠。


1) Caddy 的優點與功能

Caddy 是現代化的 Web Server / 反向代理,特別適合「自架服務入口」:

  • 自動 HTTPS:內建 ACME(例如 Let’s Encrypt),只要網域與 80/443 可用,Caddy 會自動申請與續期憑證。
  • 設定檔簡潔:Caddyfile 可讀性高,反向代理、gzip/zstd、header、redirect 等都很直覺。
  • 反向代理體驗好reverse_proxy 直接把請求轉送到後端服務(HTTP/HTTPS/Unix socket 都可)。
  • 可擴充:模組化(插件)設計,常見需求如 forward auth、rate limit、WAF 等都有社群方案(是否採用依你需求而定)。
  • 適合容器化:用 Docker 跑 Caddy 非常普遍,資料(憑證、設定)用 volume 掛載即可。

在這篇架構裡,Caddy 的角色很單純:

  • 對外只有一個入口(80/443)
  • 負責 TLS、HTTP→HTTPS、與把流量分流到 AuthentikGitea

2) Authentik 的優點與功能

Authentik 是一個自架的身分與存取管理(IAM)/ 單一登入(SSO)平台,你可以把它當成「你自己的 Google/Microsoft/Okta Login」。

常見價值:

  • 統一帳號來源:帳號、群組、權限集中管理,不用每個服務都自己養帳。
  • 多種整合方式:OIDC(OpenID Connect)、OAuth2、SAML、LDAP、Proxy/ForwardAuth 等。
  • 安全能力:MFA、條件式存取、登入流程(flow)可控。
  • 自架友善:Docker 部署成熟,社群整合文件多。

在這篇裡 Authentik 的角色是:

  • OIDC Provider(發行 token / 提供 userinfo)
  • Gitea 透過 OIDC 把「登入」交給 Authentik

3) 以 Gitea 為例:Caddy、Authentik、Gitea 的角色與登入流程

三方關係(誰做什麼)

  • Caddy(反向代理 / 門口)

    • 接收瀏覽器對 auth.www.myapp.examplegit.www.myapp.example 的連線
    • 依網域把請求轉送到對應容器
  • Authentik(身分提供者 IdP / SSO)

    • 管理使用者
    • 提供 OIDC discovery / authorization / token / userinfo
  • Gitea(應用服務 / Relying Party)

    • 提供 Git service 與 Web UI
    • 使用者點「用 Authentik 登入」→ 把驗證交給 Authentik

登入流程(OIDC 概念流程)

以使用者從 git.www.myapp.example 進入為例:

  1. 使用者進入 Gitea,選擇「用 Authentik 登入」(OIDC/OAuth2)。
  2. Gitea 將瀏覽器導向 Authentik 的授權端點(authorize),並帶上 client_idredirect_uriscopestate 等參數。
  3. 使用者在 Authentik 完成登入(可能含 MFA)。
  4. Authentik 將瀏覽器導回 Gitea 的 redirect_uri(callback),並附上 code。
  5. Gitea 以 server-to-server 方式向 Authentik 的 token endpoint 換取 token。
  6. Gitea 用 token 取得使用者資訊(或驗證 ID Token),建立/綁定本地帳號並完成登入。

重點:

  • Caddy 不負責「帳號驗證」,它只是把流量導到 Authentik/Gitea。
  • Authentik 负责身份,Gitea 負責「使用 Authentik 身份」來登入。

4) 實作:用 Docker 部署三套服務並完成整合

4.1 先準備:DNS 與防火牆

  • DNS:

    • auth.www.myapp.example → 你的主機 IP
    • git.www.myapp.example → 你的主機 IP
  • 對外開放:

    • TCP 80、443(給 Caddy 申請/使用 TLS)
  • 建議不要「對外網」放行(但會在主機上 publish 給 Caddy 用):

    • Authentik 的 9000(本文用它讓 Caddy 反向代理)
    • Authentik 的 9443(通常不需要;除非你自己另外 publish 來除錯)
    • Gitea 的 8020(本文用它讓 Caddy 反向代理)
    • 2244/22(SSH)是否對外開放依你需求(可先不開)

如果你用「Caddy 反向代理」模式,後端服務的 port 原則上不應對外網可達,避免繞過入口直接連到後端。

本文為了不使用共用 network,後端服務會用 ports: publish 到主機;因此「不要對外放行」指的是:不要在路由器/NAT 或雲端安全群組把這些埠開給 Internet。


5) Caddy:docker-compose 與 Caddyfile

5.1 Caddy 的 docker-compose.yml(範例)

檔案位置示意:docker/caddy/docker-compose.yml

services:
	caddy:
		image: caddy:2.10.2
		container_name: caddy
		restart: unless-stopped
		ports:
			- "80:80"
			- "443:443"
		extra_hosts:
			- "host.docker.internal:host-gateway"
		volumes:
			- ./config/Caddyfile:/etc/caddy/Caddyfile:ro
			- ./data/data:/data
			- ./data/config:/config

說明:

  • host.docker.internal 在 Docker Desktop(Windows/macOS)通常可用。
  • 在 Linux/Ubuntu 上常見需要加 extra_hosts: host.docker.internal:host-gateway,才能讓容器用 host.docker.internal 連到宿主機。

5.2 Caddyfile(範例)

檔案位置示意:docker/caddy/config/Caddyfile

{
	# 可選:管理者信箱,用於 ACME
	email admin@www.myapp.example
}

# Authentik(對外入口)
auth.www.myapp.example {
	encode zstd gzip
	# 反向代理到宿主機上 publish 的 Authentik 9000(HTTP)
	reverse_proxy host.docker.internal:9000
}

# Gitea(對外入口)
git.www.myapp.example {
	encode zstd gzip
	# 反向代理到宿主機上 publish 的 Gitea 8020
	reverse_proxy host.docker.internal:8020
}

說明:

  • 這份教學為了降低複雜度,讓 TLS 只由 Caddy 對外負責
  • 所以 Caddy 反向代理到 Authentik 的 9000(HTTP)即可,避免 9443 牽涉到後端 TLS 憑證驗證問題。

如果你堅持要反向代理到 Authentik 的 9443(HTTPS),才需要在 Caddy 端處理後端 TLS(例如自簽憑證可能需要 tls_insecure_skip_verify)。但這會讓設定更複雜,也比較不建議。


6) Authentik:docker-compose 與 .env

6.1 Authentik 的 .env(範例,請自行填值)

檔案位置示意:docker/authentik/.env

# Database
PG_PASS=請填入強密碼
PG_USER=authentik
PG_DB=authentik

# Authentik
AUTHENTIK_SECRET_KEY=請填入長度足夠的隨機字串

# 版本(可固定,避免 latest 帶來不可預期變更)
AUTHENTIK_TAG=2025.12.2

安全提醒:

  • .env 不要上傳到公開 repo。
  • PG_PASSAUTHENTIK_SECRET_KEY 請使用密碼管理器產生。

6.2 Authentik 的 docker-compose.yml(範例)

檔案位置示意:docker/authentik/docker-compose.yml

services:
	postgresql:
		image: postgres:16-alpine
		restart: unless-stopped
		healthcheck:
			test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
			start_period: 20s
			interval: 30s
			retries: 5
			timeout: 5s
		volumes:
			- ./data/postgresql_data:/var/lib/postgresql/data
		environment:
			POSTGRES_PASSWORD: ${PG_PASS:?database password required}
			POSTGRES_USER: ${PG_USER:-authentik}
			POSTGRES_DB: ${PG_DB:-authentik}
		env_file:
			- .env

	redis:
		image: redis:alpine
		command: --save 60 1 --loglevel warning
		restart: unless-stopped
		healthcheck:
			test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
			start_period: 20s
			interval: 30s
			retries: 5
			timeout: 3s
		volumes:
			- redis:/data

	server:
		image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.12.2}
		container_name: authentik-server
		restart: unless-stopped
		command: server
		environment:
			AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
			AUTHENTIK_REDIS__HOST: redis
			AUTHENTIK_POSTGRESQL__HOST: postgresql
			AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
			AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
			AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
		volumes:
			- ./data/authentik/media:/media
			- ./data/authentik/custom-templates:/templates
		env_file:
			- .env
		# 透過 ports publish 到宿主機,讓 Caddy 用 host.docker.internal 反向代理
		ports:
			- "9000:9000"
		depends_on:
			postgresql:
				condition: service_healthy
			redis:
				condition: service_healthy

	worker:
		image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2025.12.2}
		container_name: authentik-worker
		restart: unless-stopped
		command: worker
		environment:
			AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
			AUTHENTIK_REDIS__HOST: redis
			AUTHENTIK_POSTGRESQL__HOST: postgresql
			AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
			AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
			AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
		user: root
		volumes:
			- /var/run/docker.sock:/var/run/docker.sock
			- ./data/authentik/media:/media
			- ./data/authentik/certs:/certs
			- ./data/authentik/custom-templates:/templates
		env_file:
			- .env
		depends_on:
			postgresql:
				condition: service_healthy
			redis:
				condition: service_healthy

volumes:
	redis:
		driver: local

小提醒:

  • 這個「不共用 network」的做法,後端服務需要透過 ports: publish 到宿主機,才能讓 Caddy 容器透過 host.docker.internal 連到它。
  • 安全性上要靠「不要在路由器/NAT/雲端安全群組把 9000/8020 對外放行」,只放行 80/443 給 Caddy。

7) Gitea:docker-compose(範例)

檔案位置示意:docker/gitea/docker-compose.yml

services:
	gitea:
		image: gitea/gitea:latest
		container_name: gitea
		restart: unless-stopped
		environment:
			- USER_UID=1000
			- USER_GID=1000
		volumes:
			- ./data:/data
			- /etc/timezone:/etc/timezone:ro
			- /etc/localtime:/etc/localtime:ro
		ports:
			- "8020:3000"
			- "2244:22"  # SSH 是否對外開放依你需求;如果你要用 git@... 的方式推送才需要

反向代理補充(很重要,避免 OIDC callback 出現網址不一致):

  • 請確認 Gitea 的「對外 Base URL」是 https://git.www.myapp.example/
  • 若 Base URL 設錯,常見症狀是:Gitea 顯示的 callback URL 會是 http://... 或 host/port 不對,導致 Authentik 報 redirect_uri mismatch
  • 設定位置依你的 Gitea 部署方式而不同(例如 app.iniROOT_URL,或在管理介面可調),原則是「以使用者實際從瀏覽器進入的網址」為準。

如果你要讓 git.www.myapp.example 對外提供 SSH(22)而不是用 2244,也可以改成 "22:22",但請確認主機上沒有其他 SSH 服務衝突(例如 OpenSSH)。


8) 啟動順序與驗證

分開三份 compose 時,建議啟動順序:

  1. Authentik(含 DB/Redis)
  2. Gitea
  3. Caddy(最後再把入口打開)

範例指令(在各自資料夾):

docker compose up -d

驗證:

  • 先確認 Authentik 後台能打開:https://auth.www.myapp.example/
  • 再確認 Gitea 能打開:https://git.www.myapp.example/

9) Authentik × Gitea 整合(以官方文件流程為準)

本段建議你同時開著官方整合頁:https://integrations.goauthentik.io/development/gitea/

整體思路:

  1. 在 Authentik 建立一個「對 Gitea 用的 OIDC Provider」
  2. 建立 Application,把 provider 掛上去
  3. 把 Discovery URL / Client ID / Client Secret 填進 Gitea 的 Authentication Source

9.1 在 Authentik 建立 Provider + Application

在 Authentik 管理介面:

  1. 建立 Provider:

    • Providers → Create → OAuth2/OpenID Provider
    • 重要欄位(名稱以示意為主):
      • Name:gitea-oidc
      • Client type:通常選 Confidential(Gitea 是 server-side)
      • Redirect URIs:填入 Gitea 的 callback URL(下一小節會說怎麼確定)
      • Scopes:至少包含 openid, email, profile
  2. 建立 Application:

    • Applications → Create
    • Name:Gitea
    • Slug:gitea
    • Provider:選剛剛建立的 gitea-oidc

建立完成後,你會得到:

  • Client ID
  • Client Secret -(通常也會有)OpenID Configuration / Discovery URL

建議使用「Discovery URL」方式整合,因為端點與簽章金鑰都能自動跟上。

Discovery URL(常見樣式,請以你的 Authentik 介面顯示為準):

  • https://auth.www.myapp.example/application/o/gitea/.well-known/openid-configuration

9.2 在 Gitea 新增 Authentication Source(OIDC)

到 Gitea(需管理者權限):

  1. Site Administration → Authentication Sources → Add Authentication Source
  2. Type 選 OAuth2(或 OpenID Connect,依 Gitea 版本 UI)
  3. Provider 選 OpenID Connect
  4. 填入:
    • Auto Discovery URL:https://auth.www.myapp.example/application/o/gitea/.well-known/openid-configuration
    • Client ID:從 Authentik 複製
    • Client Secret:從 Authentik 複製
    • Scopes:openid email profile

建立後,Gitea 通常會顯示 callback URL 類似:

  • https://git.www.myapp.example/user/oauth2/<SOURCE_NAME>/callback

把這個 callback URL 回填到 Authentik provider 的 Redirect URIs(兩邊要一致)。

9.3 測試登入

回到 Gitea 登入頁:

  • 看到「用 Authentik 登入」的按鈕/選項
  • 點下去會跳到 auth.www.myapp.example 完成登入
  • 成功後回到 Gitea,首次登入可能會建立/連結本地使用者

10) 常見踩雷整理(避免再踩一次)

你提供的 Perplexity 連結在這個環境下會遇到 403(無法直接讀取頁面內容),所以我沒辦法逐字引用你當時的對話;但我可以把「你目前設定中已經出現/很常遇到」的坑整理成一章,讓你下次少繞路。

10.1 host.docker.internal 在 Linux/Ubuntu 不一定可用

  • 在 Docker Desktop(Windows/macOS)通常可以用 host.docker.internal
  • 在 Linux 常見情況是 預設沒有 這個 DNS 名稱。

可行解法(本文採用):在 Caddy container 加上:

  • extra_hosts: ["host.docker.internal:host-gateway"]

10.2 反向代理到 Authentik 的 9443 可能遇到 TLS 驗證問題

如果你用:

reverse_proxy https://...:9443

而 Authentik 端使用自簽憑證或內部憑證,Caddy 會因為無法驗證而拒絕。

解法:

  • 最簡單:反向代理到 host.docker.internal:9000(HTTP),把 TLS 全交給 Caddy 對外處理。
  • 若必須反向代理到 :9443:才處理後端 TLS(可能需要 tls_insecure_skip_verify),但要理解它是「跳過驗證」。

10.3 後端服務不要再對外暴露 port(避免繞過 Caddy)

在本文「不共用 network」的簡化架構裡,Authentik/Gitea 必須 publish port 到宿主機,Caddy 才連得到。

建議的安全作法是:

  • 路由器/NAT:只轉發 80/443 到這台機器
  • 雲端安全群組(Security Group):只開 80/443 inbound
  • 讓 9000/8020 只留在「主機本地用途」(給 Caddy 反向代理),不要對外網暴露

10.4 OIDC 回呼網址(Redirect URI)最容易填錯

症狀通常是:

  • 登入後跳轉回來直接錯誤(redirect_uri mismatch / invalid redirect)

原則:

  • Gitea 顯示的 callback URL 為準
  • 100% 原樣貼到 Authentik Provider 的 Redirect URIs
  • domain、path、https 都要一致

10.5 反向代理後網址不對(http/host/port 亂跳)

如果你發現登入流程中 URL 變成 http://、或 host/port 被改掉,通常是「應用程式不知道自己在反向代理後面」造成的。

  • Gitea:先回頭檢查 Base URL/ROOT_URL 是否為 https://git.www.myapp.example/
  • Authentik:確保你是透過 https://auth.www.myapp.example/ 存取管理介面,並檢查是否有「反向代理/受信任代理(trusted proxy)」或「外部 URL(external host/url)」相關設定需要調整。

這一段在不同 Authentik 版本/部署方式名稱會不一樣,遇到時建議直接依官方文件用關鍵字搜尋:reverse proxytrusted proxyX-Forwarded-Proto


11) 小結

Authentik 解決了自己架站一個站就要建一次帳號的問題。

  1. Caddy:負責自動化 TLS 與反向代理,解決了惱人的憑證問題,並統一了對外入口(80/443)。
  2. Authentik:作為核心的身分驗證中心,未來若要新增其他服務(如 Portainer、Grafana、Nextcloud 等),只要它們支援 OIDC/OAuth2 或 ForwardAuth,都能輕鬆接入,實現真正的單一登入(SSO)。
  3. Gitea:成功展示了從「各服務獨立帳號」轉向「集中式帳號管理」的整合流程。

雖然初期在理解 OIDC 流程與反向代理設定上需要花點時間,但建立起這套基礎設施後,未來的維護與擴充將會變得非常輕鬆且安全。希望這篇文章能幫助你順利搭建出自己的 Authentik 單一登入環境!

希望這篇文章有幫到你,謝謝你的閱讀。

Avatar

Awin Huang

有物混成,先天地而生,寂兮寥兮,獨立而不改,周行而不殆,可以為天下母。吾不知其名,字之曰道。
comments powered by Disqus