Linux Lesson 38 of 47

Enterprise Identity on Linux: LDAP, SSSD, Kerberos & Joining Active Directory (realmd/adcli)

There is a moment in every growing environment where local accounts stop working. It is not dramatic. You add the twelfth server, you useradd alice on it like you did on the other eleven, you copy her SSH key, and it works — and then three weeks later alice leaves the company, and you realise her account exists in twelve places, her sudo rights exist in twelve sudoers files, and the intern who is supposed to disable her can reach maybe nine of them. That gap between “I disabled the account” and “the account is actually disabled everywhere” is where breaches live.

This lesson is about closing that gap permanently. Centralized identity means an account is defined once, in a directory, and every host in the fleet asks that directory “who is this, and are they allowed?” at login time. Disable the account in one place and it is disabled everywhere, within a cache lifetime. Change someone’s group and their sudo rights change on every server at once. Onboard a new hire and they can log into all 300 machines they are entitled to without you touching a single /etc/passwd.

The Linux stack that delivers this is four cooperating pieces: NSS and PAM (the plug-in points already built into every host), LDAP (the directory that stores accounts), Kerberos (the ticket system that authenticates without shipping passwords around), and SSSD (the modern client daemon that ties them together and — crucially — caches everything so logins survive when the directory is unreachable). Active Directory is simply Microsoft’s bundle of LDAP + Kerberos + DNS, and a Linux host joins it with essentially one command. FreeIPA is the Linux-native equivalent. We will build the whole picture from first principles, with real config, a working lab you can run on one VM, and the troubleshooting table you will actually reach for at 2 a.m.

This lesson builds directly on two earlier ones. It assumes you understand the PAM stack — auth/account/password/session and control flags and the local users, groups and permissions model. We will recap the parts that matter, but if PAM control flags are a blur, read that lesson first.

Why this matters

Local accounts are perfect for exactly one machine. The instant you have two, you have a synchronisation problem, and every additional host makes it worse — linearly for the accounts, and combinatorially for the mistakes. Here is the shape of the pain, and what central identity replaces it with:

Problem with local /etc/passwd per host What breaks in practice Central identity fixes it by
One account per box UIDs drift — alice is 1004 here, 1009 there; NFS files show the wrong owner One uidNumber defined once, identical everywhere
Password set per box Users pick a different password per server, or you reuse one everywhere One credential verified at the KDC / directory
Deprovisioning is manual Leaver’s account lingers on the machines you forgot Disable once in the directory → denied fleet-wide
sudo rights per box 12 sudoers files drift out of sync One sudoRole / group grants rights everywhere
No central audit “Who can log into prod?” has no single answer Query the directory: one authoritative list
Onboarding is O(hosts) New hire needs an account created on every server Add once; every host resolves them on demand
Password policy per box pam_pwquality tuned differently everywhere Policy enforced centrally (AD GPO / IPA policy)

Notice that every row is a consistency problem, and consistency is exactly what a single source of truth provides. The directory becomes that source of truth; the hosts become stateless consumers of it. A freshly reimaged server, the moment it is joined, knows every user in the company without you copying a single file to it.

The cost of this power is a new failure mode: if the directory is the only place accounts live, what happens when a host cannot reach the directory? A laptop on a plane, a branch office with a dead WAN link, a maintenance window on the domain controllers — in the naïve design, nobody can log in, including you. Solving that cleanly, with an offline cache, is the single reason SSSD exists and replaced the older nss_ldap/pam_ldap libraries that simply hung or denied when the server went away. Keep that failure mode in mind; it drives most of the design decisions below.

The NSS + PAM foundation: where central identity plugs in

Before any directory enters the picture, understand the two questions a Linux host asks about a user, because they are answered by two completely different subsystems and central identity has to satisfy both.

Question Example Answered by Command that exercises it
Identity — “who is this, what are their attributes?” uid 10000 → name, home, shell, groups NSS (Name Service Switch) id alice, getent passwd alice
Authentication — “can they prove they are who they claim?” “is this the right password / ticket?” PAM (Pluggable Authentication Modules) login, sudo, su, sshd

This split trips up beginners constantly, so make it concrete: getent passwd alice can succeed (NSS resolves her) while su - alice fails (PAM refuses the password), and vice versa. When central identity is half-configured you land in exactly one of those broken states, and knowing which subsystem owns which question tells you which file to fix.

NSS and /etc/nsswitch.conf

The Name Service Switch decides where the C library looks to answer identity queries. Its config is /etc/nsswitch.conf, one line per database, listing sources in order:

# /etc/nsswitch.conf (excerpt) — sources are tried left to right
passwd:     files sss
group:      files sss
shadow:     files sss
hosts:      files dns
sudoers:    files sss
netgroup:   sss

Read passwd: files sss as: “to resolve a user, first consult the local files (/etc/passwd), then consult sss (the SSSD daemon).” Local users like root still come from files and stay fast; anyone not found locally falls through to SSSD, which asks the directory. This is the seam central identity plugs into — you are not replacing local accounts, you are adding a source after them.

nsswitch.conf database What it resolves Central-identity source you add
passwd user accounts (name↔uid, home, shell) sss
group groups and membership sss
shadow password hashes / aging metadata sss
sudoers sudo rules (when SSSD supplies them) sss
netgroup NIS-style host/user groupings sss
hosts hostnames → IPs (not identity) dns / files
automount autofs maps from the directory sss

The order is a policy decision with teeth: files sss means a local root or a break-glass admin in /etc/passwd always wins and always works, even if SSSD is down — which is exactly why you keep one. Never remove local root.

PAM and the auth handoff

PAM is the framework that programs like login, sshd, sudo and su call to authenticate a user — they never check a password themselves, they hand the job to libpam, which runs the stack in /etc/pam.d/<service>. Central identity plugs in here as a module, pam_sss.so, added to the auth, account, password and session groups:

# /etc/pam.d/system-auth (RHEL, excerpt) — pam_sss.so is the plug-in
auth        sufficient    pam_sss.so forward_pass
account     [default=bad success=ok user_unknown=ignore] pam_sss.so
password    sufficient    pam_sss.so use_authtok
session     optional      pam_sss.so
session     optional      pam_oddjob_mkhomedir.so   # create home on first login

You rarely hand-edit this. On RHEL/Fedora, authselect select sssd writes the correct stack; on Debian/Ubuntu, pam-auth-update does. The control flags (sufficient, [default=bad …]) behave exactly as covered in the PAM lesson — pam_sss.so in the auth group being sufficient means “if SSSD authenticates them, we are done; if not, fall through to the next module (usually local pam_unix).” The full path a login takes through this stack — auth → account → password → session, with SSSD as the broker behind pam_sss.so — is the same one the diagram in the next section traces end to end.

The one-line summary to carry forward: NSS answers who, PAM answers can they prove it, and central identity must satisfy both — one sss in nsswitch.conf and one pam_sss.so in the PAM stack. Miss either and you get a half-broken host.

LDAP: the directory model

LDAP — the Lightweight Directory Access Protocol — is both a protocol and a data model for a hierarchical database optimised for reads. It is where accounts physically live. You do not need to become an LDAP administrator to run SSSD, but you must be able to read the model, because every SSSD setting like ldap_search_base or ldap_user_object_class refers directly to it.

The tree, entries, and the DN

An LDAP directory is a tree. Every node is an entry, and every entry has a globally unique name called its Distinguished Name (DN) — the path from the entry up to the root, read right to left, like a reversed filesystem path. The company’s DNS domain is conventionally mapped to the top of the tree using dc (domain component):

dc=corp,dc=example                         ← the base / root of the tree
├── ou=People                              ← an organizationalUnit (a folder)
│   ├── uid=alice,ou=People,dc=corp,dc=example      ← a user entry (its DN)
│   └── uid=bob,ou=People,dc=corp,dc=example
└── ou=Groups
    └── cn=engineering,ou=Groups,dc=corp,dc=example  ← a group entry

Every entry is a bag of attributes (name: value pairs) and declares one or more objectClasses that dictate which attributes it MUST and MAY have. The DN is built from RDN (relative DN) components:

DN component Stands for Role in the tree Example
dc domain component Builds the base from the DNS name dc=corp,dc=example
o organization Alternative top level (X.500 style) o=Acme,c=US
ou organizational unit A container / “folder” ou=People, ou=Groups
cn common name Naming attribute for groups, people cn=engineering
uid user id Naming attribute for POSIX users uid=alice
c country Top-level in X.500 schemes c=US

The objectClasses that make an entry a POSIX account

For a Linux host to treat an LDAP entry as a usable account, the entry must carry the POSIX schema — the same fields that live in /etc/passwd and /etc/group. These come from the posixAccount and posixGroup objectClasses (RFC 2307). This is the single most important table in the LDAP section, because these attribute names appear verbatim in SSSD’s ldap_user_* mapping options:

objectClass Purpose MUST attributes Key MAY attributes Maps to
posixAccount A Unix login account cn, uid, uidNumber, gidNumber, homeDirectory loginShell, gecos, userPassword, description /etc/passwd fields
posixGroup A Unix group cn, gidNumber memberUid, userPassword, description /etc/group
shadowAccount Password-aging metadata uid shadowLastChange, shadowMax, shadowExpire /etc/shadow
inetOrgPerson A rich “person” entry (structural) cn, sn mail, givenName, displayName, telephoneNumber directory / GAL
organizationalUnit A container ou description a “folder”
groupOfNames Group by DN membership cn, member description DN-based groups (AD-style)

The individual POSIX attributes you will see over and over:

Attribute Meaning /etc/passwd or /etc/group equivalent
uid login name field 1 (alice)
uidNumber numeric user ID field 3 (10000)
gidNumber primary numeric group ID field 4
homeDirectory home path field 6 (/home/alice)
loginShell login shell field 7 (/bin/bash)
gecos full name / comment field 5
cn common name display name
memberUid a member’s uid (on posixGroup) /etc/group member list
userPassword password (hashed; never read by SSSD for auth) /etc/shadow hash

Two things bite people here. First, userPassword is not how SSSD authenticates — SSSD does a real LDAP bind as the user (or uses Kerberos), it does not read and compare the hash itself, so you do not need read access to userPassword. Second, the group-membership model has two competing schemas: RFC 2307 uses memberUid: alice (the bare login name) on the group, while groupOfNames/AD use member: uid=alice,ou=People,… (the full DN). SSSD handles both, but you must tell it which via ldap_schema — mismatch it and groups resolve empty.

OpenLDAP vs 389 Directory Server vs AD vs FreeIPA

You will meet several directory servers speaking the same LDAP protocol. As an SSSD client you mostly do not care which — but you must recognise them:

Server Origin / ecosystem Where you see it Notes for the Linux client
OpenLDAP (slapd) Community, C, mdb backend Classic Unix shops, appliances Bare directory; you bolt on Kerberos/TLS yourself
389 Directory Server Red Hat, the engine inside FreeIPA RHEL identity stacks Feature-rich, replication, the DS under IPA
Active Directory Microsoft The overwhelming majority of enterprises LDAP + Kerberos + DNS bundled; join with realmd
FreeIPA (IdM) Red Hat / upstream of RHEL IdM Linux-native “AD equivalent” 389-DS + MIT Kerberos + DNS + CA + HBAC + sudo

For the client, the practical differences are the auth method (plain LDAP bind vs Kerberos), the schema (RFC 2307 vs AD’s own), and how membership is stored. SSSD abstracts all of this behind the id_provider you choose (ldap, ad, or ipa).

Browsing with ldapsearch

ldapsearch is your read-only window into the directory and the first tool you reach for when “SSSD can’t find the user.” It is worth memorising its core flags:

# Anonymous-ish simple bind over StartTLS, clean output, find one user
ldapsearch -x -ZZ -H ldap://dc1.corp.example \
  -D "cn=readonly,ou=Service,dc=corp,dc=example" -W \
  -b "dc=corp,dc=example" -LLL \
  "(uid=alice)" uid uidNumber gidNumber homeDirectory loginShell
dn: uid=alice,ou=People,dc=corp,dc=example
uid: alice
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/alice
loginShell: /bin/bash
ldapsearch flag What it does Why you use it
-x Simple bind (username+password), not SASL Simplest to reason about; pair with TLS
-H <uri> Server URI (ldap://, ldaps://, ldapi://) Modern replacement for -h/-p
-D <binddn> Bind as this DN The account you authenticate as
-W Prompt for the bind password Keeps the secret off the command line/history
-w <pw> / -y <file> Password inline / from file Scripting (prefer -y, never -w in history)
-b <basedn> Search base — where to start in the tree Scopes the query; = SSSD’s ldap_search_base
-s <scope> base/one/sub How deep to search (sub = whole subtree)
-Z / -ZZ StartTLS (-ZZ = fail if TLS can’t start) Encrypt on port 389; always use -ZZ
-LLL Strip comments/version from output Readable, parseable results
(uid=alice) The LDAP filter The query itself

TLS/StartTLS — never bind in plaintext

A simple bind sends the DN and password to the server. Over an unencrypted connection, that password crosses the network in cleartext — a tcpdump on any hop reads it. This is not a theoretical risk; it is the classic LDAP finding on every pen-test. There are two ways to encrypt:

Mode Port How it works SSSD setting
LDAPS 636 TLS from the first byte (like HTTPS) ldap_uri = ldaps://dc1.corp.example
StartTLS 389 Connects plaintext, then upgrades to TLS ldap_id_use_start_tls = true
ldapi:// socket Unix socket, local only ldap_uri = ldapi:/// (local admin)
Plaintext bind 389 ⚠️ password in cleartext never — no legitimate use

⚠️ Never configure a plaintext simple bind against a remote server. Either use ldaps:// (port 636), or ldap:// with ldap_id_use_start_tls = true, and always validate the server certificate (ldap_tls_reqcert = demand with ldap_tls_cacert pointing at the CA). Setting ldap_tls_reqcert = never “to make it work” disables the very protection you turned TLS on for — it is the second-most-common insecure identity misconfiguration after plaintext binds.

Kerberos: tickets, not passwords

LDAP answers “who is this.” Kerberos answers “prove it” — and it does so without your password ever crossing the network to the services you use, and without those services ever seeing it. This is the mechanism behind single sign-on: authenticate once, then reach many services without retyping anything. Active Directory is a Kerberos realm (plus LDAP plus DNS), so understanding Kerberos is understanding how AD auth actually works.

The ticket model

Kerberos is built on a trusted third party, the KDC (Key Distribution Center), which every principal shares a secret with. The flow, simplified:

  1. You run kinit alice (or log in). Your machine asks the KDC’s Authentication Service for a Ticket-Granting Ticket (TGT), proving knowledge of alice’s key. The password is used locally to decrypt the reply; it is not sent to the KDC.
  2. You now hold a TGT (klist shows it), valid for a lifetime (typically 10 hours). This is your “SSO session.”
  3. To reach a service — say host/web1.corp.example — your machine presents the TGT to the KDC’s Ticket-Granting Service and receives a service ticket for that specific service.
  4. You present the service ticket to the service. It validates it using its own key (from its keytab) — again, no password involved, and no call back to the KDC.

The vocabulary you must know cold:

Term What it is
Realm A Kerberos administrative domain, written UPPERCASE by convention: CORP.EXAMPLE
Principal An identity in the realm: alice@CORP.EXAMPLE (user) or host/web1.corp.example@CORP.EXAMPLE (service)
KDC Key Distribution Center — issues tickets; AD’s DC is one
TGT Ticket-Granting Ticket — your session token, used to get service tickets
TGS Ticket-Granting Service — the KDC function that issues service tickets
Service ticket A ticket for one specific service (HTTP/…, host/…, cifs/…)
SPN Service Principal Name — the name a service is known by (HTTP/web1.corp.example)
Keytab A file of a principal’s long-term keys, so a service/host authenticates without a human password (/etc/krb5.keytab)
KVNO Key Version Number — bumps when a key/password changes; stale keytabs mismatch

The Kerberos command table

These are the client commands you will actually type. Know them:

Command What it does Example
kinit Get a TGT (authenticate) kinit alice@CORP.EXAMPLE
klist List cached tickets klist
klist -e Show encryption types of tickets diagnosing enctype mismatches
klist -k -t /etc/krb5.keytab List principals/timestamps in a keytab verify a host’s keytab
kdestroy Discard cached tickets (log out of SSO) kdestroy
kpasswd Change your Kerberos password kpasswd alice
kvno <spn> Fetch a service ticket / show its KVNO kvno host/web1.corp.example
ktutil Interactively edit keytabs add/remove keys
kinit -k -t <keytab> <princ> Get a TGT using a keytab (no password) service/cron auth
kswitch Switch between ticket caches multiple identities

A minimal end-to-end check:

kinit alice@CORP.EXAMPLE            # prompts for password, gets a TGT
# Password for alice@CORP.EXAMPLE:

klist                               # confirm you hold a ticket
# Ticket cache: KEYRING:persistent:10000:10000
# Default principal: alice@CORP.EXAMPLE
#
# Valid starting     Expires            Service principal
# 07/09/26 09:14:02  07/09/26 19:14:02  krbtgt/CORP.EXAMPLE@CORP.EXAMPLE

kdestroy                            # throw the ticket away

/etc/krb5.conf

The client’s Kerberos config. SSSD and realm join write this for you, but you must be able to read it:

[libdefaults]
    default_realm = CORP.EXAMPLE
    dns_lookup_realm = true
    dns_lookup_kdc = true          # find KDCs via DNS SRV records
    rdns = false
    ticket_lifetime = 24h
    renew_lifetime = 7d
    clockskew = 300                # 5 minutes — the hard limit (see below)

[realms]
    CORP.EXAMPLE = {
        kdc = dc1.corp.example
        kdc = dc2.corp.example
        admin_server = dc1.corp.example
    }

[domain_realm]
    .corp.example = CORP.EXAMPLE    # map DNS domain → Kerberos realm
    corp.example  = CORP.EXAMPLE
krb5.conf section Purpose Key settings
[libdefaults] Global client defaults default_realm, dns_lookup_kdc, ticket_lifetime, clockskew
[realms] Per-realm KDC locations kdc, admin_server, kpasswd_server
[domain_realm] Map DNS names → realms .corp.example = CORP.EXAMPLE
[capaths] Cross-realm trust paths multi-forest trusts
[logging] Where krb5 libs log default = FILE:/var/log/krb5libs.log

Clock skew — the silent killer

⚠️ Every Kerberos ticket is timestamped, and the KDC rejects any request whose clock differs from its own by more than clockskew (default 300 seconds / 5 minutes). This is a deliberate anti-replay measure, and it is the number-one “it worked yesterday” identity outage. The symptom is a login that fails with Clock skew too great while plain LDAP lookups still work — because LDAP has no such requirement. The fix is not clever: run chrony (or ntpd) on every client and every KDC/DC, pointed at the same time source.

chronyc tracking          # is this host's clock disciplined?
# Reference ID    : C0A80001 (ntp.corp.example)
# System time     : 0.000021 seconds fast of NTP time   ← good, well under 5 min
timedatectl                # confirm NTP is active and synchronised
# System clock synchronized: yes
#               NTP service: active

SSO via GSSAPI

Once you hold a TGT, GSSAPI is the API that lets applications use it for single sign-on. The everyday example is SSH: enable GSSAPIAuthentication yes on client and server (covered in the OpenSSH keys, config and hardening lesson), and after one kinit you can ssh web1.corp.example with no password and no key — SSH obtains a host/… service ticket transparently. The same mechanism authenticates you to Kerberized NFS, HTTP (SPNEGO), and databases. This is what people mean when they say “AD single sign-on on Linux”: it is Kerberos + GSSAPI, brokered by SSSD.

SSSD: the daemon that ties it together

Everything so far — NSS, PAM, LDAP, Kerberos — is glued together on the client by one daemon: SSSD, the System Security Services Daemon. SSSD is the broker. Applications talk to NSS and PAM; NSS and PAM talk to SSSD; SSSD talks to the directory and the KDC, and caches the results. Nothing else on the host needs to know how to reach LDAP or Kerberos.

Here is the whole flow end to end. A user logs into any host; the host’s NSS and PAM hand off to SSSD; SSSD resolves the account from LDAP/AD, gets a Kerberos ticket from the KDC, caches both in an encrypted local store, and returns allow — with the two things that silently break it (clock skew and the realm join plumbing) called out.

Centralized login flow across a Linux fleet: a login on any host passes through the host's NSS (nsswitch.conf) and PAM (pam_sss.so), which hand off to the single SSSD daemon; SSSD resolves the account from LDAP or the AD domain controller and obtains a Kerberos TGT from the KDC, writes both into an encrypted offline cache under /var/lib/sss/db, and returns an allow verdict; badges call out that NSS and PAM are the two plug-in points, SSSD is the sole broker, Kerberos issues the tickets, the cache is what enables offline login, realm join wires the host plumbing, and clock skew over five minutes breaks Kerberos

/etc/sssd/sssd.conf and the provider model

SSSD’s config is /etc/sssd/sssd.conf. It has a global [sssd] section and one [domain/NAME] section per identity source. The single most important idea is the provider model: for each function, you name where SSSD gets it.

# /etc/sssd/sssd.conf  — MUST be chmod 0600, root:root, or sssd refuses to start
[sssd]
config_file_version = 2
services = nss, pam, ssh, sudo
domains = corp.example

[domain/corp.example]
id_provider = ldap
auth_provider = krb5
chpass_provider = krb5
access_provider = ldap

ldap_uri = ldaps://dc1.corp.example
ldap_search_base = dc=corp,dc=example
ldap_id_use_start_tls = false          # already TLS via ldaps://
ldap_tls_cacert = /etc/pki/tls/certs/corp-ca.pem
ldap_tls_reqcert = demand

krb5_realm = CORP.EXAMPLE
krb5_server = dc1.corp.example, dc2.corp.example

cache_credentials = true               # ← the offline-login killer feature
enumerate = false
override_homedir = /home/%u
default_shell = /bin/bash
Provider (key) Answers Legal values
id_provider Where identities come from (NSS) ldap, ad, ipa, proxy, files
auth_provider How to authenticate (PAM auth) krb5, ldap, ad, ipa, proxy, defaults to id_provider
access_provider Who is allowed to log in ldap, ad, ipa, simple, permit, deny
chpass_provider How to change passwords krb5, ldap, ad, ipa, none
sudo_provider Where sudo rules come from ldap, ad, ipa, none
selinux_provider SELinux user maps ipa, none
autofs_provider automount maps ldap, ad, ipa, none

The elegance is that these are independent. A common pattern is id_provider = ldap (accounts from OpenLDAP) with auth_provider = krb5 (passwords verified by a separate MIT KDC). When you join AD, you set them all to ad and SSSD’s AD provider fills in sane defaults for the rest. The other domain options you will touch most:

Option What it controls Typical value
cache_credentials Cache creds for offline login true
enumerate Let getent passwd list all users false (⚠️ heavy on big directories)
ldap_id_mapping Algorithmically map AD SIDs→uid true for AD (see id-mapping section)
use_fully_qualified_names Require user@domain form true (multi-domain safe)
override_homedir Force a home path template /home/%u or /home/%d/%u
default_shell Shell when directory has none /bin/bash
fallback_homedir Home when directory lacks one /home/%u
ldap_user_object_class Which objectClass is a user posixAccount / user
dyndns_update Register host DNS in AD true on AD joins

The offline cache — the killer feature

With cache_credentials = true, the first successful online login writes the account plus a salted hash of the credential into an encrypted database under /var/lib/sss/db/. When the directory is later unreachable, SSSD serves logins from that cache instead of failing shut. This is the entire reason SSSD displaced nss_ldap/pam_ldap. It is what lets a laptop authenticate its owner on a plane, and a branch office keep working through a WAN outage. The cache respects an expiry (offline_credentials_expiration, in days; 0 = never) so a disabled account cannot log in offline forever.

The responder services

The services = line enables SSSD’s front-end responders — the sub-daemons that answer each subsystem:

Responder Serves Backing config
nss getent, id (identity) nsswitch.conf sss source
pam login/sudo/sshd (auth) pam_sss.so in PAM stack
sudo sudo rules from the directory nsswitch.conf sudoers: sss
ssh Serves user/host SSH keys from dir sss_ssh_authorizedkeys
ifp InfoPipe D-Bus API sssctl, monitoring
pac AD PAC (group SIDs) handling trusts
autofs automount maps automount: sss

Enumeration, cache control, and troubleshooting

Enumeration (enumerate = true) makes getent passwd return every user in the directory. It sounds convenient and it is a trap: against an AD forest with 50,000 users it hammers the DCs and bloats the cache. Leave it false; look users up by name (getent passwd alice@corp.example), which always works regardless.

When the directory changes and the host still shows stale data, you invalidate the cache:

Command What it does
sss_cache -E Expire all cached entries (next lookup refetches)
sss_cache -u alice Expire one user
sss_cache -g engineering Expire one group
sssctl cache-expire -E Newer equivalent of sss_cache -E
sssctl user-checks alice@corp.example Show NSS+PAM+cache view of a user (best single debug)
sssctl domain-list / domain-status <d> Domains and whether they are online
sssctl config-check Validate sssd.conf for errors before restart

⚠️ Expiring the cache is not the same as clearing it. If accounts genuinely changed identity (uid remap) and you need a hard reset, stop SSSD and delete the cache files:

sudo systemctl stop sssd
sudo rm -f /var/lib/sss/db/*        # ⚠️ wipes the offline cache; users can't log in offline until they re-auth online
sudo systemctl start sssd

For diagnosis, raise the log level and read the per-service logs:

# Temporarily raise debug level without editing the file (SSSD ≥ 1.13)
sudo sssctl debug-level 8
# or set  debug_level = 8  under [sssd], [nss], [pam], and the [domain/…] section
sudo systemctl restart sssd
sudo ls /var/log/sssd/
# sssd.log  sssd_nss.log  sssd_pam.log  sssd_corp.example.log
sudo journalctl -u sssd -f          # watch the daemon live

Joining Active Directory: realmd + adcli

Now the payoff. The overwhelming majority of enterprises run Active Directory, and AD is exactly LDAP + Kerberos + DNS in one product. Joining a Linux host to it — so AD users can log in — is essentially one command, because realmd orchestrates everything. There are two paths; this is the mainstream one.

Prerequisites (the boring 20% that causes 80% of failures)

Before realm join will work, three things must be true. Skip them and you will burn an hour:

  1. DNS must resolve the domain and its SRV records. The host must use the AD DNS servers (or ones that forward to them), because Kerberos and realmd find the DCs via _ldap._tcp and _kerberos._tcp SRV records. Test with realm discover corp.example and dig -t SRV _ldap._tcp.corp.example.
  2. Clocks must be within 5 minutes of the DC — the clock-skew rule from the Kerberos section. Run chrony.
  3. Install the packages. They differ by family:
Purpose Debian/Ubuntu (apt) RHEL/Fedora/Rocky (dnf)
Join + discovery realmd adcli realmd adcli
SSSD + tools sssd sssd-tools sssd sssd-tools
NSS/PAM modules libnss-sss libpam-sss (in sssd)
Samba helpers samba-common-bin samba-common-tools
Kerberos client krb5-user krb5-workstation
Home-dir on login oddjob oddjob-mkhomedir oddjob oddjob-mkhomedir
PAM/NSS profile tool libpam-runtime (pam-auth-update) authselect
# Debian/Ubuntu
sudo apt install -y realmd adcli sssd sssd-tools libnss-sss libpam-sss \
     samba-common-bin krb5-user oddjob oddjob-mkhomedir packagekit
# RHEL/Fedora/Rocky
sudo dnf install -y realmd adcli sssd sssd-tools samba-common-tools \
     krb5-workstation oddjob oddjob-mkhomedir authselect

The realmd command table

realmd is a thin, high-level orchestrator over adcli, sssd, and Kerberos. Its whole vocabulary:

Command What it does
realm discover corp.example Probe the domain via DNS/LDAP; print what a join would configure
realm join -U Administrator corp.example Create the machine account and configure everything (interactive password)
realm join --computer-ou='OU=Linux,…' … Place the computer object in a specific OU
realm list Show joined realms and current login policy
realm permit alice@corp.example Allow a specific user to log in
realm permit -g 'Linux Admins' Allow a whole group
realm permit --all Allow every domain user (⚠️ broad)
realm deny --all Deny everyone (then permit selectively)
realm leave corp.example Unjoin: remove the machine account and config

The join, step by step

# 1. Discover — no changes made; shows required packages and what will be set
realm discover corp.example
# corp.example
#   type: kerberos
#   realm-name: CORP.EXAMPLE
#   configured: no
#   server-software: active-directory
#   client-software: sssd
#   required-package: sssd
#   required-package: adcli
#   ...

# 2. Join — creates the host's machine account in AD, writes all local config
sudo realm join -U Administrator corp.example
# Password for Administrator:
#  * Successfully enrolled machine in realm

# 3. Verify
realm list
# corp.example
#   type: kerberos
#   realm-name: CORP.EXAMPLE
#   ...
#   login-formats: %U@corp.example
#   login-policy: allow-realm-logins

That single realm join is doing a great deal under the hood. Knowing exactly which files it touched is how you debug the host afterwards:

realm join configures What it writes / does Why
AD machine account Creates HOST$ computer object in AD (via adcli) The host’s own Kerberos identity
/etc/krb5.keytab The host’s keys Host authenticates without a human password
/etc/sssd/sssd.conf id_provider = ad, realm, domain, mapping The working SSSD config
/etc/krb5.conf default_realm, KDC discovery Kerberos client config
/etc/nsswitch.conf Adds sss to passwd/group/… NSS plug-in
PAM stack Adds pam_sss.so (via authselect/pam-auth-update) Auth plug-in
oddjobd Enables it (with --automatic-id-mapping) Create home dirs on first login
DNS Registers the host’s A record (dyndns_update) Host is reachable by name

The other path: FreeIPA / ipa-client-install

If your directory is FreeIPA (or RHEL IdM) rather than AD, the equivalent join is ipa-client-install. It does the same category of work — machine account, keytab, sssd.conf with id_provider = ipa, krb5.conf, NSS/PAM — but it also enrolls the host into IPA’s integrated services (certificate auto-enrollment via certmonger, HBAC, sudo):

sudo ipa-client-install --domain=ipa.example --server=ipa1.ipa.example \
     --principal=admin --mkhomedir --enable-dns-updates
# ... discovers IPA, obtains a keytab, writes sssd.conf with id_provider = ipa ...

Both paths converge on the same runtime picture: NSS + PAM → SSSD → directory + KDC. Only the id_provider (ad vs ipa) and the back-end services differ.

Managing AD-joined behavior

A successful join is the start, not the end. Out of the box, realm join typically permits everyone or no one, creates no home directories the way you want, and maps IDs algorithmically. Tuning this is where real deployments live.

Who may log in: realm permit / deny and access control

By default a fresh join may allow all domain users (login-policy: allow-realm-logins) — rarely what you want on a specific server. Lock it down:

sudo realm deny --all                              # deny everyone first
sudo realm permit -g 'Linux Admins@corp.example'   # then allow one group
sudo realm permit alice@corp.example bob@corp.example
realm list                                         # confirm the policy

Behind the scenes this drives SSSD’s access_provider. You can also express access directly in sssd.conf:

access_provider How access is decided Config
simple Static allow/deny lists simple_allow_groups = linux-admins
ad Honour AD GPO logon rights ad_gpo_access_control = enforcing
ldap An LDAP filter must match ldap_access_filter = memberOf=cn=…
permit Everyone allowed (testing only)
deny No one allowed (lockdown)

AD GPO access control

When access_provider = ad, SSSD can enforce the same “Allow log on locally / through Remote Desktop” GPO rights that Windows uses. This is powerful and a notorious foot-gun:

ad_gpo_access_control Behaviour
enforcing Honour GPO logon rights; deny if not granted (default on modern SSSD)
permissive Evaluate and log what it would deny, but allow — perfect for testing
disabled Ignore GPO logon rights entirely

⚠️ The classic outage: you join a host, access_provider = ad defaults to enforcing, and because no GPO grants “Allow log on locally” to your Linux admins, everyone is locked out — including the account you were about to test with. Set ad_gpo_access_control = permissive while you validate, watch /var/log/sssd/ for the would-be denials, grant the right GPO, then switch to enforcing.

Home directories on first login

AD/LDAP stores a homeDirectory path, but the directory does not exist on a freshly joined host until you create it. Two moving parts create it on first login:

Mechanism What it is How to enable
pam_mkhomedir.so PAM session module that makes the home dir Debian: sudo pam-auth-update → enable “mkhomedir”
pam_oddjob_mkhomedir.so Same, but via the privileged oddjobd D-Bus helper RHEL: authselect select sssd with-mkhomedir + systemctl enable --now oddjobd
override_homedir Force the path regardless of the directory value sssd.conf: override_homedir = /home/%u
# RHEL/Fedora — the modern, correct incantation
sudo authselect select sssd with-mkhomedir --force
sudo systemctl enable --now oddjobd
# Debian/Ubuntu
sudo pam-auth-update --enable mkhomedir

⚠️ Symptom of forgetting this: the user authenticates fine but lands in / with Could not chdir to home directory /home/alice@corp.example: No such file or directory. That is the missing-mkhomedir signature, not an auth failure.

ID mapping: ldap_id_mapping and POSIX attributes

Windows identifies users by SID (a long string), not by a POSIX uidNumber. Linux needs an integer uid. There are two ways to bridge this, and choosing wrong causes files owned by “nobody” across NFS:

Mode ldap_id_mapping How uid/gid is derived Use when
Algorithmic true (realmd default) SSSD hashes the SID→uid deterministically Pure AD, no POSIX attrs; must be identical config fleet-wide
POSIX attributes false Read uidNumber/gidNumber from AD (IDMU / RFC 2307) You manage explicit uids in AD, or share NFS with legacy Unix

⚠️ The trap: algorithmic mapping is deterministic only if every host uses the same range and the same ldap_idmap_* settings. Mix ldap_id_mapping = true on host A with false (POSIX attrs) on host B and the same AD user gets different uids on the two hosts — so files alice creates over NFS on A show as some random uid on B. Pick one strategy and apply it uniformly. When AD carries real uidNumber attributes (Identity Management for Unix), set ldap_id_mapping = false and let them win.

sudo rules from AD/LDAP

Central identity should include central authorization. SSSD can pull sudo rules from the directory so a group’s privileges live in one place. Two steps:

# 1. Tell NSS that sudoers can come from sss
grep sudoers /etc/nsswitch.conf || echo 'sudoers: files sss' | sudo tee -a /etc/nsswitch.conf
# 2. Enable the sudo provider in sssd.conf
[sssd]
services = nss, pam, sudo
[domain/corp.example]
sudo_provider = ad          # or ldap / ipa

Rules are stored as sudoRole entries (the sudo LDAP schema), typically under ou=SUDOers:

sudoRole attribute Meaning Example
cn Rule name cn=admins
sudoUser Who it applies to %linux-admins (a group)
sudoHost Which hosts ALL or web1
sudoCommand What they may run ALL or /usr/bin/systemctl
sudoOption Options !authenticate, runas=root

FreeIPA models this natively (ipa sudorule-add), which is far more pleasant than hand-crafting sudoRole LDIF in raw LDAP.

Testing an AD-joined host

After a join, prove every layer works, bottom to top:

Test Command Confirms
Identity (NSS) id alice@corp.example uid/gid/groups resolve
Identity (getent) getent passwd alice@corp.example NSS sss source works
Kerberos kinit alice@CORP.EXAMPLE then klist KDC issues a TGT
Groups groups alice@corp.example group membership resolves
Local switch su - alice@corp.example PAM auth + home dir + shell
Remote login ssh alice@corp.example@web1 end-to-end, incl. mkhomedir
SSSD view sssctl user-checks alice@corp.example the full NSS+PAM+cache picture
id alice@corp.example
# uid=1631400500(alice@corp.example) gid=1631400500(...) groups=...,1631400513(linux-admins@corp.example)

If id resolves but su - fails → PAM/Kerberos problem (clock, keytab, access). If id itself fails → NSS/LDAP problem (search base, TLS, sss not in nsswitch.conf). That single split points you at the right half of the stack every time.

FreeIPA: the Linux-native Active Directory

Active Directory is superb — for Windows. On a majority-Linux estate, FreeIPA (upstream of Red Hat IdM) gives you the same benefits built the Linux way, out of open components, with first-class POSIX support and no reliance on Windows servers. It is worth knowing even if you run AD, because IPA can trust AD and manage the Linux-specific policy (HBAC, sudo, SELinux maps) that AD models awkwardly.

FreeIPA is an integrated bundle — you install a server, not seven services:

FreeIPA component Role AD equivalent
389 Directory Server The LDAP directory (accounts, groups) AD’s LDAP
MIT Kerberos KDC Ticket authority AD’s KDC
BIND (integrated DNS) DNS + SRV records for discovery AD-integrated DNS
Dogtag / certmonger Certificate Authority, auto-enrollment AD Certificate Services
HBAC Host-Based Access Control (who logs in where) GPO logon rights
sudo rules Native central sudo sudoRole in LDAP
SSSD ipa provider The client integration SSSD ad provider

The workflow mirrors AD’s, in Linux idiom:

# On the server (one-time): stand up the whole stack
sudo ipa-server-install --domain=ipa.example --realm=IPA.EXAMPLE --setup-dns
# On each client: enroll
sudo ipa-client-install --mkhomedir
# Administer with the `ipa` CLI (talks LDAP+Kerberos for you)
ipa user-add alice --first=Alice --last=Ng --shell=/bin/bash
ipa group-add-member linux-admins --users=alice
ipa sudorule-add admins --hostcat=all
ipa hbacrule-add allow-admins        # who may log into which hosts

Where each fits:

Standalone LDAP + Kerberos FreeIPA / IdM Active Directory
Setup effort High — wire each piece yourself One ipa-server-install Windows-native
POSIX accounts Native (RFC 2307) Native, first-class Bolt-on (IDMU) or algorithmic
Integrated DNS/CA No Yes Yes
Central sudo/HBAC Manual LDIF Built-in, CLI-managed GPO + sudoRole
Linux client provider id_provider = ldap+krb5 id_provider = ipa id_provider = ad
Best when Appliances, minimal footprint Majority-Linux estate Windows-centric enterprise
Windows clients No Via AD trust Native

The pragmatic modern architecture in a mixed shop is an IPA–AD trust: AD remains the source of truth for people, IPA manages Linux hosts and Linux-specific policy, and the two trust each other so an AD user logs into a Linux host governed by IPA HBAC rules. That is beyond this lesson, but the components above are exactly the pieces that make it possible.

Hands-on lab

This lab is fully self-contained: you will run a real LDAP directory in a container, point SSSD at it, log in as a directory user, and then — the payoff — pull the network out from under it and watch the offline cache keep the login working. You need one Linux VM (or WSL2) with sudo and either podman or docker. It uses plain LDAP over localhost for reproducibility; the ⚠️ notes flag exactly where production would differ.

⚠️ Do this on a throwaway VM or container host, not a machine whose logins you care about — you are editing nsswitch.conf, PAM, and sssd.conf.

Step 1 — Run a directory server

# Start OpenLDAP with a seeded base (dc=example,dc=org), admin password "admin"
podman run -d --name lab-ldap -p 389:389 -p 636:636 \
  -e LDAP_ORGANISATION="Lab" -e LDAP_DOMAIN="example.org" \
  -e LDAP_ADMIN_PASSWORD="admin" docker.io/osixia/openldap:1.5.0
podman ps

What just happened: you now have a real LDAP server on localhost:389 with base dc=example,dc=org and a directory manager cn=admin,dc=example,dc=org. (Use docker in place of podman identically.)

Step 2 — Seed a POSIX user and group

sudo apt install -y ldap-utils || sudo dnf install -y openldap-clients
cat > /tmp/lab.ldif <<'EOF'
dn: ou=People,dc=example,dc=org
objectClass: organizationalUnit
ou: People

dn: ou=Groups,dc=example,dc=org
objectClass: organizationalUnit
ou: Groups

dn: cn=labusers,ou=Groups,dc=example,dc=org
objectClass: posixGroup
cn: labusers
gidNumber: 20001

dn: uid=labuser,ou=People,dc=example,dc=org
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Lab User
sn: User
uid: labuser
uidNumber: 20001
gidNumber: 20001
homeDirectory: /home/labuser
loginShell: /bin/bash
EOF

ldapadd -x -H ldap://127.0.0.1 -D "cn=admin,dc=example,dc=org" -w admin -f /tmp/lab.ldif
# adding new entry "ou=People,dc=example,dc=org"  ... (4 entries)

# Set labuser's password (a real bind credential)
ldappasswd -x -H ldap://127.0.0.1 -D "cn=admin,dc=example,dc=org" -w admin \
  -s "LabPass123" "uid=labuser,ou=People,dc=example,dc=org"

What just happened: you created a posixAccount (uid 20001) and a posixGroup, exactly the objectClasses from the LDAP table, then set the user’s password with ldappasswd.

Step 3 — Confirm with ldapsearch

ldapsearch -x -H ldap://127.0.0.1 -b "dc=example,dc=org" -LLL "(uid=labuser)" \
  uid uidNumber gidNumber homeDirectory loginShell
# dn: uid=labuser,ou=People,dc=example,dc=org
# uid: labuser
# uidNumber: 20001
# gidNumber: 20001
# homeDirectory: /home/labuser
# loginShell: /bin/bash

What just happened: you queried the directory as a client. This is precisely what SSSD will do — same base, same filter.

Step 4 — Install and configure SSSD

sudo apt install -y sssd sssd-tools libnss-sss libpam-sss oddjob oddjob-mkhomedir \
  || sudo dnf install -y sssd sssd-tools oddjob oddjob-mkhomedir authselect

sudo tee /etc/sssd/sssd.conf >/dev/null <<'EOF'
[sssd]
config_file_version = 2
services = nss, pam
domains = LAB

[domain/LAB]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldap://127.0.0.1
ldap_search_base = dc=example,dc=org
ldap_default_bind_dn = cn=admin,dc=example,dc=org
ldap_default_authtok = admin
# ⚠️ LAB ONLY: no TLS, and admin creds in the file. In production use
#   ldaps://, ldap_tls_reqcert=demand, a read-only bind DN, and chmod-600 secrets.
cache_credentials = true
enumerate = true
override_homedir = /home/%u
default_shell = /bin/bash
EOF

sudo chmod 600 /etc/sssd/sssd.conf     # SSSD refuses to start otherwise

What just happened: SSSD now knows one domain, id_provider/auth_provider = ldap, with cache_credentials = true (the feature we will test) and enumerate = true (fine for a tiny lab directory).

Step 5 — Wire NSS + PAM to SSSD

# RHEL/Fedora: authselect writes both nsswitch and PAM correctly
sudo authselect select sssd with-mkhomedir --force 2>/dev/null

# Debian/Ubuntu: enable the sss NSS source and mkhomedir PAM module
sudo pam-auth-update --enable mkhomedir 2>/dev/null
grep -q 'passwd:.*sss' /etc/nsswitch.conf || \
  sudo sed -ri 's/^(passwd:.*)/\1 sss/; s/^(group:.*)/\1 sss/; s/^(shadow:.*)/\1 sss/' /etc/nsswitch.conf

sudo systemctl enable --now oddjobd
sudo systemctl restart sssd

What just happened: nsswitch.conf now lists sss after files, pam_sss.so is in the PAM stack, and oddjobd is ready to make home directories.

Step 6 — Resolve the directory user

getent passwd labuser
# labuser:*:20001:20001:Lab User:/home/labuser:/bin/bash
id labuser
# uid=20001(labuser) gid=20001(labuser) groups=20001(labuser),20001(labusers)

What just happened: NSS fell through files and asked SSSD, which asked LDAP. The account resolves on this host without existing in /etc/passwd. That is central identity.

Step 7 — Log in as the directory user

su - labuser        # enter LabPass123 when prompted
# Creating home directory for labuser.
# labuser@host:~$ whoami
# labuser
# labuser@host:~$ exit

What just happened: PAM’s auth group ran pam_sss.so, SSSD did an LDAP bind as uid=labuser,… with the password, it succeeded, and pam_oddjob_mkhomedir created /home/labuser. Because cache_credentials = true, SSSD just cached a hash of that credential.

Step 8 — The payoff: offline login

podman stop lab-ldap        # the directory is now UNREACHABLE
sssctl domain-status LAB    # SSSD notices it is offline
# Online status: Offline

su - labuser                # enter LabPass123 again
# labuser@host:~$ whoami
# labuser

What just happened: the directory is down, yet labuser still logged in — SSSD served the identity and verified the credential from its offline cache. This is the single feature that makes centralized identity survivable on laptops and branch offices. Restart the container (podman start lab-ldap) and SSSD transparently goes back online.

Step 9 — Tear down

sudo systemctl stop sssd
sudo rm -f /var/lib/sss/db/* /var/lib/sss/mc/*   # wipe the cache
sudo rm -f /etc/sssd/sssd.conf
podman rm -f lab-ldap
# On RHEL: sudo authselect select minimal  (or restore your prior profile)

What just happened: you removed the SSSD config and cache and stopped the container. The host is back to local-only accounts.

Common mistakes and troubleshooting

Identity failures are intimidating because the error rarely names the real cause — “permission denied” could be DNS, a clock, a keytab, a search base, or a missing home dir. This table is the map from symptom to cause to fix; the prose after it covers the three that waste the most time.

Symptom Likely cause Fix
Clock skew too great on login; LDAP still works Host clock >5 min off the KDC/DC chronyc makestep; run chrony on all hosts + DCs against one source
User resolves (id works) but password rejected auth_provider/keytab/Kerberos wrong; or wrong password kinit user@REALM to isolate; check /etc/krb5.keytab with klist -k
id user@domain returns “no such user” sss missing from nsswitch.conf, or wrong ldap_search_base Add sss to passwd/group; verify base with ldapsearch
Login OK but lands in /, “Could not chdir to home” mkhomedir not enabled / oddjobd off authselect … with-mkhomedir or pam-auth-update; systemctl enable --now oddjobd
Same AD user has different uids on two hosts Inconsistent ldap_id_mapping (true vs false) Standardise mapping strategy + ranges fleet-wide; clear cache
Everyone locked out right after AD join ad_gpo_access_control = enforcing, no GPO grants logon Set permissive, grant the “Allow log on locally” GPO, then enforcing
realm join fails at discovery DNS can’t resolve SRV records; wrong resolver Point host at AD DNS; dig -t SRV _ldap._tcp.corp.example
Stale account data after a directory change SSSD cache not expired sss_cache -E (or sssctl cache-expire -E)
SSSD couldn't load the configuration on start sssd.conf not 0600 root:root, or syntax error chmod 600; sssctl config-check
Groups resolve empty though membership exists Wrong ldap_schema (rfc2307 vs rfc2307bis) Match schema to the directory’s membership model
TLS bind fails: “Can’t contact LDAP server” Cert not trusted / reqcert mismatch / wrong port Set ldap_tls_cacert; use ldaps:// (636) or StartTLS on 389
getent passwd lists nothing but named lookups work enumerate = false (this is normal!) Look up by name; do not enable enumerate on big directories

Gotcha 1 — DNS is the hidden dependency

More AD-join failures trace back to DNS than to anything else. Kerberos and realmd locate domain controllers through SRV records (_ldap._tcp.corp.example, _kerberos._tcp.corp.example), and if the host’s resolver cannot see them, realm discover returns nothing and kinit fails with Cannot find KDC. Before touching SSSD, prove DNS: dig -t SRV _ldap._tcp.corp.example must return your DCs. Point the host at the AD DNS servers (or a resolver that forwards the AD zones) — the networking fundamentals lesson covers setting resolvers with nmcli and testing DNS. This one prerequisite, quietly unmet, produces a dozen different downstream error messages.

Gotcha 2 — the clock, again and forever

It bears repeating because it is that common: any Kerberos or AD login fails hard if the host clock drifts more than five minutes from the KDC. The tell is that plain LDAP identity lookups (getent, id) keep working while every authentication fails, because LDAP has no timestamp requirement and Kerberos does. If auth breaks fleet-wide “for no reason,” check chronyc tracking before anything else. NTP is not optional infrastructure for Kerberos; it is a hard dependency.

Gotcha 3 — id-mapping drift corrupts shared files

The subtlest failure is silent. When one host uses algorithmic mapping (ldap_id_mapping = true) and another reads POSIX attributes (false), the same AD user is uid 1631400500 on the first host and uid 10000 on the second. Everything looks fine until they share storage: files alice creates over NFS on host A show up owned by some stranger’s uid — or nobody — on host B. The fix is discipline, not cleverness: decide one strategy for the whole fleet (algorithmic or POSIX attributes), pin the same ldap_idmap_range_* settings everywhere, and if you migrate, clear the SSSD cache (sss_cache -E) so old mappings do not linger.

Cheat-sheet

Bookmark this. LDAP and Kerberos first:

Command What it does
ldapsearch -x -ZZ -H ldap://dc -b <base> -LLL "(uid=x)" Search over StartTLS, clean output
ldapsearch -x -H ldaps://dc -b <base> "(objectClass=posixAccount)" List POSIX accounts (LDAPS)
ldapadd -x -D <binddn> -W -f file.ldif Add entries from an LDIF
ldappasswd -x -D <binddn> -W -s NEW <userdn> Set a user’s password
kinit user@REALM Get a TGT (authenticate)
klist / klist -e List tickets / with enctypes
klist -k -t /etc/krb5.keytab Inspect a keytab
kdestroy Discard tickets
kvno host/fqdn Fetch/check a service ticket version
chronyc tracking Verify clock sync (Kerberos prereq)

SSSD, realmd, and testing:

Command What it does
realm discover DOMAIN Probe a domain (no changes)
realm join -U admin DOMAIN Join AD; configures everything
realm list Show joined realms + login policy
realm permit -g 'Group' / realm deny --all Manage who may log in
realm leave DOMAIN Unjoin
ipa-client-install --mkhomedir Enroll into FreeIPA
sssctl user-checks user@dom Full NSS+PAM+cache view of a user
sssctl domain-status DOM Is the domain online?
sssctl config-check Validate sssd.conf
sss_cache -E / sssctl cache-expire -E Expire the cache
systemctl restart sssd Reload after config changes
id user@dom / getent passwd user@dom Test identity resolution
su - user@dom Test auth + home dir + shell
journalctl -u sssd -f Watch SSSD live

Key files:

File Role
/etc/nsswitch.conf NSS sources (passwd: files sss)
/etc/pam.d/* PAM stacks (pam_sss.so)
/etc/sssd/sssd.conf SSSD config (must be 0600 root:root)
/etc/krb5.conf Kerberos client config
/etc/krb5.keytab The host’s Kerberos keys
/var/lib/sss/db/ The offline cache
/var/log/sssd/ Per-service SSSD logs

Interview and exam questions

Q: What is the difference between NSS and PAM, and why does central identity need both? A: NSS (/etc/nsswitch.conf) answers identity questions — who is uid 10000, what is alice’s home and shell — behind getent/id. PAM answers authentication — is this the right password/ticket — behind login/sudo/sshd. Central identity must add sss to nsswitch.conf (so the directory’s users resolve) and pam_sss.so to the PAM stack (so their passwords verify). Fix only one and you get a half-broken host: the user resolves but can’t authenticate, or authenticates into a broken shell.

Q: Your users can getent passwd alice@corp but su - alice@corp is rejected. Where do you look first? A: NSS works, so it is an authentication problem, not identity. Isolate with kinit alice@CORP — if that fails too it is Kerberos (clock skew, KDC reachability, keytab); if kinit works but su doesn’t, look at the PAM stack / access_provider. Check chronyc tracking for clock skew and /var/log/sssd/sssd_pam.log.

Q: What is a TGT, and why is it better than sending a password to each service? A: A Ticket-Granting Ticket is a session token from the KDC obtained at login (kinit). You trade it for per-service tickets without re-entering your password, and services validate those tickets with their own keytab keys — so your password is verified once, at the KDC, and never travels to or is seen by the services. That is the basis of Kerberos single sign-on via GSSAPI.

Q: Why must clocks be synchronised for Kerberos, and what is the default tolerance? A: Tickets are timestamped to prevent replay; the KDC rejects requests whose clock differs from its own by more than clockskew (default 300 s / 5 minutes). A drifted clock fails every Kerberos/AD login with “Clock skew too great” even though LDAP identity lookups still work. Fix: run chrony/NTP on all clients and KDCs against a common source.

Q: What does cache_credentials = true buy you, and what is the risk? A: It lets SSSD cache a hash of a successfully verified credential so the user can log in offline when the directory is unreachable — essential for laptops and branch offices. The risk is that a credential remains usable offline until the cache expires (offline_credentials_expiration), so a disabled account could still log in offline within that window; set a sane expiry.

Q: Explain the two ldap_id_mapping strategies for AD and when to use each. A: true (algorithmic) deterministically hashes each AD SID into a uid/gid — no POSIX attributes needed, but every host must use identical ranges/config or the same user gets different uids. false reads explicit uidNumber/gidNumber from AD (Identity Management for Unix / RFC 2307) — use it when AD carries real POSIX attributes or you share NFS with legacy Unix. The cardinal rule is consistency across the fleet.

Q: A user logs in but lands in / with “Could not chdir to home directory.” What is wrong? A: The home directory does not exist and nothing is creating it on first login. Enable mkhomedir: on RHEL authselect select sssd with-mkhomedir plus systemctl enable --now oddjobd; on Debian pam-auth-update --enable mkhomedir. This is a session-stage problem, not an auth failure.

Q: What does realm join actually configure on the host? (name at least four) A: It creates the host’s machine account in AD; writes /etc/krb5.keytab; writes /etc/sssd/sssd.conf with id_provider = ad; updates /etc/krb5.conf; adds sss to /etc/nsswitch.conf; adds pam_sss.so to the PAM stack (via authselect/pam-auth-update); and enables oddjobd for home creation. Understanding this list is how you debug an AD-joined host.

Q: Why should you never do a plaintext LDAP simple bind, and what are the two ways to encrypt? A: A simple bind sends the DN and password; over an unencrypted socket the password is readable by anyone sniffing the wire. Encrypt with LDAPS (TLS from the first byte, port 636) or StartTLS (upgrade an ldap:// connection on 389 via ldap_id_use_start_tls = true), and always validate the server cert (ldap_tls_reqcert = demand) — never never.

Q: (RHCSA-style task) Join this host to corp.example and permit only the sysadmins group to log in. List the commands. A: Ensure DNS+time are correct, then sudo dnf install -y realmd sssd adcli krb5-workstation oddjob-mkhomedir authselect; sudo realm join -U Administrator corp.example; sudo realm deny --all; sudo realm permit -g 'sysadmins@corp.example'; enable home dirs with sudo authselect select sssd with-mkhomedir && sudo systemctl enable --now oddjobd; verify with id someuser@corp.example and realm list.

Q: (LFCS-style task) SSSD shows stale group membership after you changed a group in LDAP. How do you force a refresh without a reboot? A: Expire the cache: sudo sss_cache -g groupname (or sudo sss_cache -E for everything, or sssctl cache-expire -E). The next lookup refetches from the directory. A systemctl restart sssd also works but is heavier and drops the online/offline state.

Q: What is FreeIPA and how does it compare to Active Directory for a Linux estate? A: FreeIPA (RHEL IdM) is an integrated Linux identity server bundling 389-DS (LDAP), MIT Kerberos, DNS, a Dogtag CA, HBAC, and central sudo, with first-class POSIX support. It is the Linux-native equivalent of AD: ipa-client-install enrolls hosts (id_provider = ipa). In mixed shops the common design is an IPA–AD trust — AD owns people, IPA owns Linux hosts and Linux-specific policy (HBAC/sudo/SELinux maps).

Key takeaways

linuxsssdldapkerberosactive-directoryrealmdadclifreeipansswitchpamauthenticationidentityrhcsa389-ds
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments