Jump to content

Authentik

From Official NixOS Wiki

authentik is an identity provider supporting protocols such as OAuth 2.0, OpenID Connect, SAML, and LDAP. It provides a free open-source edition and a paid Enterprise edition with additional features and support. It is an alternative to Keycloak.

There are currently three ways to use authentik on NixOS:

  1. the official docker-compose
  2. unofficial flake
  3. your own NixOS module

As of NixOS 26.05 there is no module in nixpkgs which would start the service with something like services.authentik.enable = true;. Therefore, if you don't want to use docker containers or flakes, writing your own modules is the only way to run authentik, currently.

Your own NixOS module

Advantages of this option are not having to use flakes and having more integration and less indirection than with containers.

The example configuration below

  1. creates two systemd services: authentik server and authentik worker (this is analog to the to containers in the official docker-compose solution)
  2. reuses an existing PostgreSQL instance (the official docker-compose solution creates its own instance)
  3. uses Agenix for storing the authentik sercret key
  4. uses nginx as a reverse proxy
  5. does not store authentik's configuration such as users, groups, policies declaratively
  6. uses the unstable version of authentik. authentik version 2026 introduced several configuration option changes and therefore, for a new installation, it is advisable to use the unstable nixpkgs package which is already on 2026.x.y. This reduces future required configuration changes.

Preresiquites

  1. This example suggest to use https://auth.yourdomain.example as the URL of authentik. Add the DNS records (A and AAAA) for auth to the DNS configuration of your domain. If you change the domain option, you can use any other subdomain.
  2. Create an agenix secret file named authentik-secret-key.age and add a secret key using openssl rand -base64 60 | tr -d '\n'[1]

Configuration

You can import the authentik module below to your main configuration using imports. For example:

❄︎ /etc/nixos/configuration.nix
  ...
  imports = [
    ./hardware-configuration.nix
    ./authentik.nix
  ];
  ...
❄︎ /etc/nixos/authentik.nix
{ config, pkgs, lib, ... }:
let
  unstable-pkgs = import <nixpkgs-unstable> { };

  # TODO: Change this to your domain
  domain = "auth.yourdomain.example";

  authentikStateDirName = "authentik";
  authentikStateDir = "/var/lib/${authentikStateDirName}";
  authentikUser = "authentik";
  authentikGroup = "authentik";
  authentikPackage = unstable-pkgs.authentik;

  serverHttpPort = 9000;
  serverMetricsPort = 9300;
  workerHttpPort = 9001;
  workerMetricsPort = 9301;

  # Configuration that is common to authentik's server and worker processes
  commonEnvironment = {
    AUTHENTIK_POSTGRESQL__HOST = "/run/postgresql";
    AUTHENTIK_POSTGRESQL__NAME = "authentik";
    AUTHENTIK_POSTGRESQL__USER = "authentik";

    AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS = "127.0.0.0/8,::1/128";

    AUTHENTIK_STORAGE__FILE__PATH = authentikStateDir;

    AUTHENTIK_SECRET_KEY = "file://${config.age.secrets.authentik-secret-key.path}";

    AUTHENTIK_LOG_LEVEL = "info";
  };

  serverEnvironment = commonEnvironment // {
    AUTHENTIK_LISTEN__HTTP = "127.0.0.1:${toString serverHttpPort}";
    AUTHENTIK_LISTEN__METRICS = "127.0.0.1:${toString serverMetricsPort}";
  };

  workerEnvironment = commonEnvironment // {
    AUTHENTIK_LISTEN__HTTP = "127.0.0.1:${toString workerHttpPort}";
    AUTHENTIK_LISTEN__METRICS = "127.0.0.1:${toString workerMetricsPort}";
  };

  # Function to generate the systemd configuration for each role (server or worker).
  authentikSystemdService = role: environment: {
    description = "authentik ${role}";
    wantedBy = [ "multi-user.target" ];
    after = [ "postgresql.service" ];
    requires = [ "postgresql.service" ];


    # `environment` is both the argument name of this function and the NixOS option name to set a
    # systemd services environemnt. Thus, here we simply set the option's value to the argument's value.
    inherit environment;

    # postgresql.service may be started before the database is ready to accept connections. Therefore,
    # check the latter explicitly.
    preStart = ''
      ${config.services.postgresql.package}/bin/pg_isready -h /run/postgresql -d authentik -U authentik
    '';

    serviceConfig = {
      Type = "simple";
      User = authentikUser;
      Group = authentikGroup;
      WorkingDirectory = authentikStateDir;
      StateDirectory = authentikStateDirName;
      StateDirectoryMode = "0750";
      ExecStart = "${authentikPackage}/bin/ak ${role}";
      Restart = "on-failure";
      RestartSec = "10s";
    };
  };
in
{
  age.secrets.authentik-secret-key = {
    # TODO: adjust this to your path
    file = ../../secrets/authentik-secret-key.age;
    owner = authentikUser;
    group = authentikGroup;
    mode = "0400";
  };

  users.groups.${authentikGroup} = { };

  users.users.${authentikUser} = {
    isSystemUser = true;
    group = authentikGroup;
    home = authentikStateDir;
  };

  services.postgresql = {
    ensureDatabases = [ "authentik" ];
    ensureUsers = [
      {
        name = "authentik";
        ensureDBOwnership = true;
      }
    ];
    authentication = lib.mkAfter ''
      local authentik authentik peer
    '';
  };

  systemd.services = {
    authentik-server = authentikSystemdService "server" serverEnvironment;
    authentik-worker = authentikSystemdService "worker" workerEnvironment;
  };

  services.nginx.virtualHosts.${domain} = {
    forceSSL = true;
    enableACME = true;

    locations."/" = {
      proxyPass = "http://127.0.0.1:${toString serverHttpPort}";
      recommendedProxySettings = true;
      proxyWebsockets = true;
    };
  };
}

Search for all TODOs and set your system's values accordingly.