Authelia 是 Go 語言撰寫的開源認證入口(GitHub 29,564 星、Apache-2.0、Docker 映像檔 110 MB),透過 forward-auth 幫 Caddy/Traefik/Nginx 後面的自架服務統一加上登入頁與兩步驗證,不必逐一修改每個服務的帳號系統。
為什麼自架到某個數量之後,一定會想裝它
自架好玩的地方是很自由,麻煩的地方也是自由:每個服務一套帳號。
| 階段 | 你有哪些服務 | 帳號現況 | 真正的痛點 |
|---|---|---|---|
| 剛開始(1–3 個) | Vaultwarden、Homepage | 各自一組密碼 | 還好,密碼管理器能處理 |
| 長大中(5–10 個) | 加上 Nextcloud、Paperless、Uptime Kuma、Portainer | 各自一組,有些還共用 | 誰能看哪個服務?離職/換人怎麼辦? |
| 完整自架(10+) | 加上內部 API、Dashboard、監控 | 有人沒設密碼,只靠內網 | 內網不是安全邊界,一個外洩就一串 |
真正麻煩的從來不是「多記幾組密碼」,而是**「這個服務該給誰看」這件事沒有單一控制點**。你想讓家人看 Jellyfin、不給看 Portainer;想讓同事看內部文件、不給碰監控。沒有認證層時,這些規矩只能靠「不要把網址告訴他」來實現——那不叫權限控管,那叫默契。
Authelia 解決的就是這一層:一個登入頁、一份使用者清單、一組政策規則,所有後面的服務共用。
一表看懂 Authelia
| 項目 | 內容 |
|---|---|
| 專案 | authelia/authelia(GitHub) |
| 最新版本 | v4.39.28(2026-09-17 發布) |
| 語言/授權 | Go/Apache-2.0 |
| 社群規模 | 29,564 星、1,539 forks(2026-11-05 查詢) |
| Docker 映像檔 | 110 MB(authelia/authelia:4.39.28) |
| 實測記憶體 | 閒置 117.4 MiB、登入活動中約 221.5 MiB |
| 使用者來源 | 檔案(YAML)、LDAP、AD |
| 第二因素 | TOTP、WebAuthn、Duo、行動推播 |
| 認證協定 | Forward-auth、OpenID Connect 1.0(OpenID 認證過) |
| 資料存放 | SQLite/PostgreSQL/MySQL(實測 db 304 KB) |
| 適合誰 | 已經有一組自架服務、想加統一登入與權限的人 |
它是怎麼運作的:forward-auth 的四步
Authelia 不代理你的流量,它只回答一個問題:「這個請求該不該放行?」流程如下:
| 步驟 | 誰在做 | 發生什麼事 |
|---|---|---|
| 1 | 瀏覽器 | 請求 wiki.example.com,實際上打到反向代理 |
| 2 | 反向代理 | 先問 Authelia /api/authz/forward-auth,並附上 X-Forwarded-Host、X-Forwarded-Uri 等標頭 |
| 3 | Authelia | 依 access_control 規則判斷:沒登入回 302 導向登入頁;登入但不符政策回 403;通過則回 200 並附 Remote-User、Remote-Groups 等標頭 |
| 4 | 反向代理 | 收到 200 才把請求轉給後端服務,並把 Remote-* 標頭一起帶過去 |
這裡有個關鍵差異值得先記住,後面實測會再看到一次:302 和 403 是完全不同的兩件事。302 是「身分還沒確認,先去登入」;403 是「身分確認了,但這個身分不准進來」。很多教學只講前者,實際上線後最常撞到的是後者。
實測環境與設定
測試環境:Ubuntu 主機、Docker 29.2.1、Docker Compose v5.1.0,網域假設為 most.lab(auth.most.lab 是登入入口,wiki.most.lab、app.most.lab 是被保護的服務)。
先開目錄,把設定檔與資料庫放在同一個 config/:
mkdir -p ~/authelia/config && cd ~/authelia
docker-compose.yml:
services:
authelia:
image: authelia/authelia:4.39.28
container_name: authelia
volumes:
- ./config:/config
ports:
- "9091:9091"
environment:
- TZ=Asia/Taipei
restart: unless-stopped
config/configuration.yml(這份可以直接跑,記得換掉三個 secret):
theme: 'dark'
server:
address: 'tcp://:9091'
endpoints:
authz:
forward-auth:
implementation: 'ForwardAuth'
log:
level: 'debug'
identity_validation:
reset_password:
jwt_secret: '換成你的隨機字串_0123456789abcdef'
totp:
issuer: 'most.tw'
period: 30
authentication_backend:
file:
path: '/config/users.yml'
password:
algorithm: 'argon2'
argon2:
variant: 'argon2id'
iterations: 3
memory: 65536
parallelism: 4
key_length: 32
salt_length: 16
access_control:
default_policy: 'deny'
rules:
- domain: 'auth.most.lab'
policy: 'bypass'
- domain: 'wiki.most.lab'
policy: 'one_factor'
- domain: '*.most.lab'
subject: 'group:admins'
policy: 'two_factor'
session:
name: 'authelia_session'
secret: '換成你的隨機字串_0123456789abcdef'
expiration: '1h'
inactivity: '5m'
cookies:
- domain: 'most.lab'
authelia_url: 'https://auth.most.lab'
default_redirection_url: 'https://wiki.most.lab'
regulation:
max_retries: 3
find_time: '2m'
ban_time: '5m'
storage:
encryption_key: '換成你的隨機字串_0123456789abcdefgh'
local:
path: '/config/db.sqlite3'
notifier:
filesystem:
filename: '/config/notification.txt'
config/users.yml 的密碼不能寫明碼,要用 Argon2id 雜湊。產生的指令在映像檔裡就有,不必另外裝工具:
docker run --rm authelia/authelia:4.39.28 \
authelia crypto hash generate argon2 --password '你的密碼' --variant argon2id
實測輸出長這樣($ 是雜湊的一部分,貼進 YAML 時整串用雙引號包起來):
Digest: $argon2id$v=19$m=65536,t=3,p=4$ZizE97qy3o/mfQTmRSdTdw$y/PXYYBi8HlY0nBoAT09FI1PRrB348V2ftpMUB93Yfc
users:
dennis:
displayname: "Dennis"
password: "$argon2id$v=19$m=65536,t=3,p=4$ZizE97qy3o/mfQTmRSdTdw$y/PXYYBi8HlY0nBoAT09FI1PRrB348V2ftpMUB93Yfc"
email: "dennis@most.lab"
groups:
- "admins"
啟動前先驗設定
Authelia 內建驗證器,這一步能省下大量「容器起不來但看不出原因」的時間:
docker run --rm -v ~/authelia/config:/config authelia/authelia:4.39.28 authelia config validate
docker compose config --quiet
通過時會印出 Configuration parsed and loaded successfully without errors.。實測時它立刻抓到一個設定錯誤(下一節的坑一)。
實測結果:每個端點真的回什麼
啟動後逐項打 API,結果如下(皆為本次實測原文):
| 測試 | 請求 | 實際回應 |
|---|---|---|
| 健康檢查 | GET /api/health | {"status":"OK"} |
| 登入頁 | GET / | 200,回傳前端 SPA 外殼 |
| 帳密登入 | POST /api/firstfactor | {"status":"OK","data":{"redirect":"https://auth.most.lab"}},並發出 Set-Cookie: authelia_session=...; domain=most.lab; HttpOnly; secure; SameSite=Lax |
| 使用者資訊 | GET /api/user/info | {"status":"OK","data":{"display_name":"Dennis","emails":["d****s@most.lab"],"has_totp":false,...}} |
| 認證等級 | GET /api/state | {"username":"dennis","authentication_level":1,"factor_knowledge":true,...} |
| 未登入時的 forward-auth | GET /api/authz/forward-auth + X-Forwarded-Host: wiki.most.lab | 302,Location: https://auth.most.lab/?rd=https%3A%2F%2Fwiki.most.lab%2F&rm=GET |
已登入(1FA)+ one_factor 政策 | 同上 | 200,並帶 Remote-User: viewer、Remote-Groups: users、Remote-Name、Remote-Email |
已登入但群組不符(two_factor + group:admins) | X-Forwarded-Host: app.most.lab | 403 |
未列在規則中的網域(default_policy: deny) | X-Forwarded-Host: vault.most.lab | 403 |
兩個細節值得畫線:
- 注意
emails回傳的是d****s@most.lab——Authelia 把 email 遮蔽了,不是 bug。 authentication_level: 1代表只過第一因素;完成第二因素後會變 2。這個數字就是 forward-auth 判斷「能不能放行」的依據。
接到反向代理:Caddy 的官方寫法
Authelia 官方文件給的 Caddy 設定如下(/api/authz/forward-auth 是 4.39 的現行端點,舊教學裡的 /api/verify 已經換掉):
# Authelia Portal.
auth.example.com {
reverse_proxy authelia:9091
}
# Protected Endpoint.
nextcloud.example.com {
forward_auth authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
reverse_proxy nextcloud:80
}
copy_headers 那行不能省,否則後端服務拿不到登入者身分,只能做「有登入/沒登入」的粗粒度控制。用 Traefik 的話概念一樣,把 forward-auth 換成 forwardAuth middleware 指向同一個端點;用 Nginx 則走 auth_request。站上兩篇反向代理教學可以直接對照:
五個實測踩到的坑
坑一:default_redirection_url 不能等於 authelia_url
一開始兩個都寫 https://auth.most.lab,config validate 直接擋下來:
session: domain config #1 (domain 'most.lab'): option 'default_redirection_url'
with value 'https://auth.most.lab' is effectively equal to option 'authelia_url'
with value 'https://auth.most.lab' which is not permitted
改成一個真正的受保護頁面(例如 https://wiki.most.lab)就過。這個設計是對的:登入完把你丟回登入頁本身,等於沒有預設目的地。
坑二:規則順序決定一切,第一條命中就結束
一開始把 *.most.lab 的通配規則寫在 wiki.most.lab 前面,結果明明登入了、one_factor 卻一直拿到 302。原因很簡單——Authelia 由上往下比對,wiki.most.lab 先被 *.most.lab 那條接走,套用 two_factor,而當時只有 1FA,所以被導回登入頁。
把 wiki.most.lab 那條往前搬,重新起容器後同一個請求立刻變 200 並帶 Remote-User。寫規則時把具體網域放前面、通配放後面,這一條就能省掉半天。
坑三:cookie domain 對不上時,登入成功但不給你 session
第一次用 http://127.0.0.1:9099 測,帳密明明是對的,回應卻只說認證失敗。翻日誌才看到真正的錯誤:
level=error msg="Failed to get session provider during 1FA attempt"
error="unable to retrieve session cookie domain provider: no configured session
cookie domain matches the url 'http://127.0.0.1:9099/api/firstfactor'"
因為 session.cookies[].domain 設定成 most.lab,請求的 host 卻不屬於這個網域,Authelia 找不到可用的 cookie 網域,就只能拒絕。
這件事有兩個實務意義。第一,本機測試一定要用真實網域,用 curl --resolve auth.example.com:9091:127.0.0.1 或改 /etc/hosts,不要用 IP。第二,錯誤訊息常被前端簡化成「認證失敗」,遇到「密碼明明對卻進不去」時,第一件事是去看 docker logs,不是重設密碼。
坑四:費率限制會把你鎖在門外,而且重開機也沒用
regulation 的 max_retries: 3 不是裝飾。實測連續打 4 次錯誤密碼後,第 5 次換上正確密碼仍然被拒絕,日誌寫得很白:
Unsuccessful 1FA authentication attempt by user 'dennis' and they are banned
until 4:08:03AM on November 6 2026
更關鍵的是:把容器砍掉重建之後,同一個帳號還是進不去——因為封鎖狀態存在 db.sqlite3 裡,跟著 config/ 目錄一起被保留下來。這對正式環境是好事(重啟不能繞過封鎖),對測試環境是陷阱。設定的時候記得:ban_time 太長會讓自己進不去,太短則失去防暴力破解的意義,5 到 15 分鐘是多數自架情境的合理值。
坑五:記憶體不會是「幾 MB」
自架圈很流行「輕量」這個詞,所以實測數字有意義:映像檔 110 MB、閒置記憶體 117.4 MiB、登入活動期間約 221.5 MiB。跟 Beszel 那種 11–15 MB 等級的單一功能小工具不同,Authelia 是一個完整認證入口(入口頁、規則引擎、session 儲存、費率限制),兩百多 MB 是合理代價,但 1 GB 記憶體的 VPS 上要把它算進去。
Authelia、Authentik、Keycloak 怎麼選
三套都成熟,差別在定位與複雜度。以下社群數字為 2026-11-05 以 GitHub API 查詢,功能描述依各自官方文件:
| 項目 | Authelia | Authentik | Keycloak |
|---|---|---|---|
| GitHub 星數 | 29,564 | 26,244 | 37,719 |
| 語言 | Go | Python | Java |
| 授權 | Apache-2.0 | 非標準 SPDX(GitHub 判定 NOASSERTION) | Apache-2.0 |
| 主要定位 | 反向代理前的認證層 | 完整 IdP 平台(含管理介面、流程編輯器) | 企業級 IdP/IAM |
| 使用檔設定 | YAML 為主 | 幾乎全在 Web UI | 幾乎全在 Admin Console |
| Forward-auth | 原生、設定簡單 | 支援(Proxy Provider) | 需額外元件 |
| OIDC Provider | 支援(已認證) | 支援 | 支援(最完整) |
| 記憶體實測 | 117–221 MiB | 未實測 | 未實測 |
| 適合誰 | 想用一份 YAML 管好自架服務的人 | 想要圖形化管理、多協定與 SSO 入口的人 | 企業、需要標準化合規與多租戶 |
選型的實務建議很直接:你只要「幫自架服務加一道門」就選 Authelia;你要「當全公司的身分提供者」就往 Authentik 或 Keycloak 走。 站上另有一篇 Keycloak 的完整介紹可以對照:
已知限制與這次沒做的事
依本站慣例,把「查證過」與「沒查證」分開講清楚:
- 2FA 綁定流程沒有在 CLI 完成。 以 API 直接呼叫 TOTP 註冊端點時,被要求 elevated session(先回
{"status":"KO","data":{"elevation":true,...}});補做POST /api/firstfactor/reauthenticate拿到{"status":"OK"}之後,端點仍回{"status":"KO","data":{"elevation":false,"first_factor":true,...}}。結論是:TOTP 綁定設計給瀏覽器流程,headless 環境下沒有走完。文章裡的 2FA 章節因此沒有實測成功的背書,官方 UI 步驟請以文件為準。 - 沒有接真實的反向代理。 Caddy/Traefik/Nginx 的片段引自官方文件,本次是用
curl直接模擬X-Forwarded-*標頭驗證 Authelia 端的行為(200/302/403 都是真的),但「代理層 → 後端服務」那一段沒有實際串起來測。 - 沒有測 LDAP/AD 後端,只測檔案後端。多數人從檔案開始,但要接公司目錄就得另外驗證。
- 沒有做壓力測試,所以併發下的延遲與記憶體峰值未知。
- 星數、版本、映像檔大小皆為 2026-11-05 查詢值,會變動;功能比較以官方文件描述為準,未逐項實測 Authentik 與 Keycloak。
誰適合、誰還不需要
適合現在裝的人:已經有 5 個以上自架服務、服務分散在不同網域或埠、家裡有其他使用者(家人、同事)需要分權限、而且你願意維護一份 YAML。
可以先不用的人:只有兩三個服務(服務自己的帳號系統還撐得住)、全部只在自己電腦的 VPN 內使用(Tailscale 這類 mesh VPN 本身已是一層邊界,可先參考 Tailscale 完整教學 2026)、或者你根本不打算把任何東西曝露到公網。
站內延伸閱讀
- Vaultwarden 完整教學 2026:自架 Bitwarden 相容密碼管理器
- Homepage 完整教學 2026:自架儀表板、widget 與 Docker 整合
- Portainer 完整教學 2026:Docker 圖形化管理與自架實戰
原始來源
(本文非商業合作;文中數字除特別註明外,皆為 2026-11-05 在本機以 Docker 實測所得。)
