SSO cho startup nội bộ: Authelia self-host trên VPS

Chia sẻ bài viết

Mục lục
TL;DR
  • Authelia là SSO + 2FA mã nguồn mở, self-host được trên 1 VPS nhỏ, miễn phí.
  • Đứng trước Nginx/Caddy/Traefik làm forward-auth, bảo vệ tất cả app nội bộ chỉ với 1 lần đăng nhập.
  • Hỗ trợ TOTP, WebAuthn (passkey), push Duo, recovery code. Đủ chuẩn 2FA cho team 5-100 người.
  • Backend user có thể là file YAML đơn giản, hoặc LDAP/LLDAP/Active Directory khi đã có quy mô.
  • Cấu hình production cần Redis hoặc PostgreSQL cho session, không dùng SQLite ở môi trường real.

Startup 10 người, đang xài 8 SaaS nội bộ tự host: Gitea, Plane.so, NocoDB, n8n, Grafana, Penpot, Vaultwarden, Bitwarden Send. Mỗi đứa dev có 8 username, 8 password, vài đứa đặt password trùng nhau cho tiện. Khi 1 nhân viên nghỉ việc, admin phải vào từng app để vô hiệu hoá tài khoản. Khi sếp yêu cầu bật 2FA cho toàn bộ hệ thống, 8 app = 8 lần cấu hình 2FA khác nhau. Câu trả lời: cần SSO.

Lựa chọn SaaS: Okta, Auth0, Microsoft Entra ID. Tốt nhưng đắt, mỗi user vài USD/tháng. Với startup nhỏ, self-host SSO là phương án ngon: 1 VPS chạy Authelia, đặt trước reverse proxy, mọi truy cập vào subdomain nội bộ đều bị chặn lại đăng nhập 1 lần, sau đó truy cập tất cả app khác trong 1 session. Bài này hướng dẫn deploy Authelia thực tế từ con số 0 đến production-ready, kèm cấu hình mẫu cho Nginx và Caddy.

Authelia là gì và nó hoạt động thế nào?

Authelia là một authentication server viết bằng Go, không tự render web app mà ngồi sau reverse proxy. Khi request đến git.your-domain.com, proxy hỏi Authelia: "user này đã đăng nhập chưa, có quyền truy cập app này không?" Nếu chưa, proxy redirect user sang Authelia portal để login, sau đó set cookie session, rồi mới cho qua. Mô hình này gọi là forward-auth hoặc auth-request.

Ưu điểm: app nội bộ không cần biết gì về Authelia, chỉ cần proxy front-end. Authelia trao request đã pass kèm header Remote-User, Remote-Email, Remote-Groups để app có thể tự nhận diện. Với app hỗ trợ OIDC như Grafana hoặc Nextcloud, Authelia còn đóng vai trò OpenID Provider, không cần forward-auth.

Chuẩn bị VPS và DNS

Bạn cần 1 VPS nhỏ (2 vCPU, 2-4GB RAM là đủ cho team dưới 100 user) và 1 domain. Khuyến nghị tách subdomain riêng cho Authelia, ví dụ auth.your-domain.com. Mỗi app nội bộ là 1 subdomain khác: git.your-domain.com, plane.your-domain.com, n8n.your-domain.com. Tất cả trỏ về cùng IP của reverse proxy.

HostnameTypeValueMục đích
auth.your-domain.comA1.2.3.4Authelia portal
git.your-domain.comA1.2.3.4Gitea, có forward-auth
plane.your-domain.comA1.2.3.4Plane.so
n8n.your-domain.comA1.2.3.4n8n
*.your-domain.com (wildcard)CNAMEyour-domain.comNhanh hơn add từng record

Cấu hình firewall mở port 80 (HTTP), 443 (HTTPS), 22 (SSH). Không expose Authelia trực tiếp ra port 9091, luôn để Nginx/Caddy đứng trước. Cài Docker + Docker Compose để chạy Authelia kèm Redis cho session storage.

Docker Compose cho Authelia + Redis + Postgres

Tạo thư mục /srv/authelia/ và 2 file: docker-compose.yml + config/configuration.yml. Mở file compose:

services:
  authelia:
    image: authelia/authelia:4.38
    container_name: authelia
    restart: unless-stopped
    volumes:
      - ./config:/config
    networks:
      - auth
    expose:
      - 9091
    depends_on:
      - redis
      - postgres
    environment:
      TZ: Asia/Ho_Chi_Minh

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    volumes:
      - ./redis:/data
    networks:
      - auth

  postgres:
    image: postgres:16-alpine
    container_name: authelia-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: authelia
      POSTGRES_USER: authelia
      POSTGRES_PASSWORD: change-me-strong-password
    volumes:
      - ./postgres:/var/lib/postgresql/data
    networks:
      - auth

networks:
  auth:
    name: auth

Tách Postgres ra (không dùng SQLite mặc định) là quyết định quan trọng cho production. SQLite OK lúc test nhưng không scale khi nhiều session đồng thời. Redis lưu session, Postgres lưu user pref, 2FA device, audit log.

Cấu hình Authelia chính (configuration.yml)

File này là trái tim của Authelia. Mở config/configuration.yml:

server:
  address: tcp://:9091
  endpoints:
    authz:
      forward-auth:
        implementation: ForwardAuth

theme: dark
log:
  level: info

identity_validation:
  reset_password:
    jwt_secret: PLEASE-GENERATE-RANDOM-64-CHAR

authentication_backend:
  password_reset:
    disable: false
  file:
    path: /config/users_database.yml
    password:
      algorithm: argon2

access_control:
  default_policy: deny
  rules:
    - domain: "auth.your-domain.com"
      policy: bypass
    - domain: ["git.your-domain.com", "plane.your-domain.com"]
      policy: one_factor
      subject: ["group:devs"]
    - domain: ["n8n.your-domain.com", "grafana.your-domain.com"]
      policy: two_factor
      subject: ["group:admins"]

session:
  name: authelia_session
  secret: PLEASE-GENERATE-RANDOM-64-CHAR
  expiration: 1h
  inactivity: 15m
  remember_me: 1M
  cookies:
    - domain: your-domain.com
      authelia_url: https://auth.your-domain.com
      default_redirection_url: https://git.your-domain.com
  redis:
    host: redis
    port: 6379

regulation:
  max_retries: 3
  find_time: 2m
  ban_time: 5m

storage:
  encryption_key: PLEASE-GENERATE-RANDOM-64-CHAR
  postgres:
    address: tcp://postgres:5432
    database: authelia
    username: authelia
    password: change-me-strong-password

notifier:
  smtp:
    address: smtp://smtp.your-domain.com:587
    username: [email protected]
    password: YOUR_SMTP_PASSWORD
    sender: "Authelia "

Tạo 3 secret bằng openssl rand -hex 32 rồi paste vào jwt_secret, session.secret, storage.encryption_key. Không xài lại key giữa các môi trường dev/prod.

Tạo user và mật khẩu băm argon2

Tạo file config/users_database.yml chứa user. Hash mật khẩu bằng CLI Authelia trước rồi paste vào:

docker run --rm authelia/authelia:4.38 
  authelia crypto hash generate argon2 --password 'MatKhauManhCuaBan'

Lệnh trả về chuỗi dạng $argon2id$v=19$m=65536,t=3,p=4$.... Paste vào file users:

users:
  founder:
    displayname: "Anh Founder"
    email: [email protected]
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."
    groups: [admins]
  duy:
    displayname: "Duy Dev"
    email: [email protected]
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."
    groups: [devs]
  ha:
    displayname: "Ha PM"
    email: [email protected]
    password: "$argon2id$v=19$m=65536,t=3,p=4$..."
    groups: [pms, devs]

Quy ước group: admins mới được vào n8n/Grafana, devs được vào Gitea/Plane, pms chỉ thấy Plane. Khi nhân viên nghỉ, xóa user khỏi file là xong, không cần đăng nhập từng app để revoke.

Cấu hình Nginx làm reverse proxy với forward-auth

Đặt 2 file: auth.conf cho Authelia portal, và snippets/authelia.conf để include vào mỗi app cần bảo vệ. Trong sites-enabled:

server {
  listen 443 ssl http2;
  server_name auth.your-domain.com;
  ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

  location / {
    proxy_pass http://127.0.0.1:9091;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

File snippet /etc/nginx/snippets/authelia.conf:

auth_request /internal/authelia/authz;
auth_request_set $target_url $scheme://$http_host$request_uri;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Email $email;

error_page 401 =302 https://auth.your-domain.com/?rd=$target_url;

location = /internal/authelia/authz {
  internal;
  proxy_pass http://127.0.0.1:9091/api/authz/forward-auth;
  proxy_set_header X-Forwarded-Method $request_method;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header X-Forwarded-Host $http_host;
  proxy_set_header X-Forwarded-Uri $request_uri;
  proxy_set_header X-Forwarded-For $remote_addr;
}

Trong server block của mỗi app (ví dụ Gitea), thêm 1 dòng include /etc/nginx/snippets/authelia.conf; trước proxy_pass. Reload Nginx, từ giờ vào git.your-domain.com sẽ redirect sang portal Authelia. Đăng nhập 1 lần, cookie session set theo domain cha (your-domain.com), tất cả subdomain khác auto pass.

Bật 2FA: TOTP và Passkey

Theo cấu hình access_control, app n8n.your-domain.com yêu cầu two_factor. Lần đầu user vào, sau khi nhập password, Authelia ép họ đăng ký 1 phương thức 2FA. Bạn enable cả 2:

totp:
  issuer: your-domain.com
  algorithm: SHA1
  digits: 6
  period: 30

webauthn:
  disable: false
  display_name: your-domain.com SSO
  attestation_conveyance_preference: indirect
  user_verification: preferred
  timeout: 60s

TOTP cho user xài Google Authenticator, Authy, 1Password OTP. WebAuthn cho user xài passkey iCloud / Windows Hello / YubiKey. Passkey UX tốt hơn TOTP (1 chạm Touch ID là xong), khuyến khích team chuyển sang passkey.

Tích hợp OIDC cho Grafana, Nextcloud

Với app hỗ trợ OIDC, không cần forward-auth, dùng Authelia làm OpenID Provider luôn. Thêm vào configuration.yml:

identity_providers:
  oidc:
    hmac_secret: PLEASE-GENERATE-RANDOM
    jwks:
      - key: |
          -----BEGIN RSA PRIVATE KEY-----
          ... openssl genrsa 4096 ...
          -----END RSA PRIVATE KEY-----
    clients:
      - client_id: grafana
        client_name: Grafana
        client_secret: '$pbkdf2-sha512$...'
        public: false
        authorization_policy: two_factor
        redirect_uris:
          - https://grafana.your-domain.com/login/generic_oauth
        scopes: [openid, profile, email, groups]
        userinfo_signed_response_alg: none

Bên Grafana, vào grafana.ini cấu hình auth.generic_oauth với 3 endpoint của Authelia: auth_url, token_url, api_url. Từ đó user click "Sign in with your-domain.com" trên Grafana, redirect qua Authelia, login xong quay về với role được map từ group.

Khi nào nên chuyển từ file users sang LDAP?

File YAML đủ dùng đến khoảng 30-50 user. Sau đó nên chuyển sang LDAP để quản lý tập trung, đồng bộ với HRM, hoặc dùng cho onboarding tự động. Lựa chọn:

  • LLDAP: Light LDAP, viết bằng Rust, web UI đẹp, đủ dùng cho startup, deploy 1 container. Khuyến nghị cho team 30-200 người.
  • OpenLDAP: Veteran, cấu hình phức tạp, schema mạnh, dùng khi cần custom field nhiều.
  • Microsoft Active Directory: Khi đã có Windows domain controller, Authelia kết nối qua AD LDAP.
  • FreeIPA: Tích hợp Kerberos + LDAP + DNS + CA, dành cho team có sysadmin chuyên trách.

Khi chuyển backend, chỉ đổi block authentication_backend trong configuration.yml, restart container. User cũ trong file vẫn login được nếu bạn để cả 2 backend (file là fallback). Tốt hơn nên migrate sạch và xóa file users.

Backup, monitoring, audit log

Authelia lưu rất nhiều dữ liệu nhạy cảm: 2FA device, session, recovery code. Backup hằng ngày là bắt buộc:

  • Snapshot VPS (1-click trên dashboard TND) mỗi tuần, lưu 4 snapshot xoay vòng.
  • Dump Postgres hằng đêm: docker exec authelia-postgres pg_dump -U authelia authelia > /backup/authelia-$(date +%F).sql.
  • Sync backup ra cloud storage thứ 2 (Backblaze B2, R2, Wasabi) qua rclone hoặc restic.
  • Bật log level info hoặc debug khi cần điều tra, ship log qua Loki hoặc ELK.
  • Tích hợp Uptime Kuma ping /api/health mỗi phút, alert qua Telegram nếu down.

Authelia có audit log built-in (table authentication_logs trong Postgres) ghi mọi attempt login, useful cho compliance. Có thể query để báo cáo cho khách hàng enterprise hoặc ISO 27001 audit.

Bẫy thường gặp khi deploy lần đầu

LỗiNguyên nhânCách fix
Redirect loop khi loginCookie domain sai (đặt subdomain thay vì domain cha)Sửa session.cookies.domain về your-domain.com
"Unable to validate identity"jwt_secret bị thay khi container restartSet secret vào file, không inject runtime
2FA setup mail không tớiSMTP block port 587 của VPSĐổi sang STARTTLS port 465 hoặc dùng API như Resend
Nginx báo 500 trên forward-authThiếu header X-Forwarded-UriCopy chính xác snippet trong bài, không bỏ dòng nào
OIDC client_secret invalidAuthelia yêu cầu hash pbkdf2, không phải plain textDùng lệnh authelia crypto hash generate pbkdf2

So sánh nhanh Authelia với alternatives

ToolForward-authOIDCUIResourceHợp với
AutheliaCó (beta)Đơn giản, dark theme200MB RAMStartup 5-100 user
AuthentikĐầy đủĐẹp, kéo flow800MB RAMTeam 50-500, cần workflow
KeycloakKhông nativeEnterprise gradePhức tạp1.5GB RAMDoanh nghiệp 200+, multi-realm
Pocket-IDKhôngChỉ passkey OIDCTối giản50MB RAMTeam thuần passkey, modern stack

Authelia là điểm cân bằng tốt nhất cho startup nội bộ: nhẹ, đủ feature, doc tốt, cộng đồng active, không cần Java như Keycloak. Khi quy mô vượt 100 user và cần custom flow phức tạp, hãy migrate sang Authentik.

Cloud VPS cho vibe coder

VPS đủ chỗ chạy Authelia + Postgres + Redis + 8 app nội bộ

Cloud VPS TND sẵn AlmaLinux 9, Ubuntu 22/24, Debian 12/13. SSD CEPH, snapshot 1-click, backup hằng ngày, network 200Mbps trong nước. Gói 4GB RAM đủ chạy Authelia + LLDAP + reverse proxy cho team 50 người dùng.

Xem 8 cấu hình Cloud VPS →

FAQ

Authelia có miễn phí và mã nguồn mở không?

Có. Authelia dùng license Apache 2.0, miễn phí cho cả commercial use. Không có tier paid, không có giới hạn user, không telemetry. Bạn chỉ trả tiền cho VPS chạy nó.

Có thể dùng Authelia làm SSO cho app SaaS bên ngoài (Slack, Notion) không?

Có, nếu SaaS hỗ trợ SAML hoặc OIDC. Authelia 4.38 trở đi đã hỗ trợ OIDC server đủ tốt. Tuy nhiên SAML chưa hỗ trợ native, cần wrapper như SimpleSAMLphp. Với Slack/Notion paid plan có SSO, bạn add Authelia là IdP qua SAML hoặc Google Workspace SSO bridge.

Khi VPS Authelia chết, có bị khoá khỏi tất cả app không?

Có, đó là rủi ro cố hữu của centralized SSO. Mitigation: dùng VPS có snapshot 1-click + auto restart, đặt monitor uptime, và quan trọng nhất là giữ 1 đường backup. Mỗi app nên có 1 admin local account emergency (chỉ admin biết) để truy cập khi Authelia down. Đừng disable hoàn toàn local login.

Khác biệt giữa Authelia và Authentik là gì?

Authentik là SSO viết bằng Python/Django, có UI rất đẹp, hỗ trợ flow designer kéo thả, OIDC + SAML + LDAP đầy đủ. Nặng hơn Authelia (cần 1GB RAM tối thiểu) và phức tạp hơn. Authelia thì gọn, file config YAML, học nhanh, đủ cho 80% case startup. Chọn Authentik khi cần workflow phức tạp (lockout, approval, MFA conditional) hoặc multi-tenant.

Passkey trên iOS/Android có hoạt động với Authelia không?

Có. Authelia 4.38 hỗ trợ WebAuthn full spec, bao gồm passkey iCloud Keychain (iOS 17+) và Google Password Manager (Android 14+). User vào portal qua Safari/Chrome, bấm "Đăng ký Passkey", chạm Touch ID hoặc Face ID là xong. Lần sau login chỉ cần 1 chạm. Passkey sync giữa các thiết bị qua cloud của Apple/Google, không cần lưu thêm gì ở Authelia.