Skip to content

nss

Serves POSIX accounts and groups from the directory to the ssoossh-nss daemon on each host, authenticated by fleet API keys. Disabled by default.

Every account and group comes from the directory, people and shared or service accounts alike, so NSS requires ldap.enabled; there is no OIDC claim source for POSIX attributes. The directory connection, base DN and ldap.id_attribute are the ldap block’s.

Key Type Default
nss.enabled bool false
nss.user_filter string (objectClass=posixAccount)
nss.group_filter string (objectClass=posixGroup)
nss.disabled_filter string empty
nss.sync_interval duration 15m
nss.full_sync_interval duration 24h
nss.attributes.name string uid
nss.attributes.uid string uidNumber
nss.attributes.gid string gidNumber
nss.attributes.home string homeDirectory
nss.attributes.shell string loginShell
nss.attributes.group_name string cn
nss.attributes.group_gid string gidNumber
nss.attributes.group_member string memberUid
nss.attributes.gecos_attributes list displayName, cn
nss.gecos_template string {{.DisplayName}}
nss.auto_private_groups string hybrid
nss.tombstone_after duration 168h
nss.tombstone_retention duration 2160h
nss.default_home string /home/%u
nss.default_shell string empty
nss.disabled_shell string /usr/sbin/nologin
nss.max_group_members int 2000
nss.rate_limit number 20
nss.rate_burst int 200
nss.miss_alert_rate number 5
nss.usage_retention duration 2160h
nss.key_max_lifetime duration 8760h
nss.key_rotation_grace duration 720h
nss.source_allow_list list empty
nss.privileged_name_alert list root, admin, daemon, bin, sys
nss.key_source_alert int 1000

bool, default false

Turns the NSS directory on: the lookup endpoints, the fleet key administration and the directory sync of POSIX entries. Requires ldap.enabled; a configuration with this on and LDAP off fails at startup.

nss:
enabled: false

string, default (objectClass=posixAccount)

The LDAP filter that selects POSIX accounts under ldap.base_dn. A plain RFC 4515 filter in parentheses, not a template: the sync enumerates it with paged searches, so ldap.limits does not cap it, and an on-demand lookup ANDs it with an equality on the name or uid attribute. Required when nss.enabled is on; its syntax is checked when the server starts. A preset from ldap.schema may set this.

nss:
user_filter: "(objectClass=posixAccount)"

string, default (objectClass=posixGroup)

The LDAP filter that selects POSIX groups under ldap.base_dn, used the same way as user_filter. Required when nss.enabled is on. A preset from ldap.schema may set this.

nss:
group_filter: "(objectClass=posixGroup)"

string, default empty

An LDAP filter an account’s entry is tested against on every read: a match marks the account disabled by the directory itself, which hosts then lock. For example (nsAccountLock=TRUE) on 389-ds and FreeIPA, or (userAccountControl:1.2.840.113556.1.4.803:=2) on Active Directory. Empty means the directory never disables an account; a person’s ssoossh disable still does. A preset from ldap.schema may set this.

nss:
disabled_filter: "(nsAccountLock=TRUE)"

duration, default 15m

How often the incremental sync runs: it reads only the accounts and groups whose modifyTimestamp is no older than five minutes before the last successful run on this instance began, so changes reach hosts within one interval. The first run after a start, and any run the directory refuses the modifyTimestamp filter for, is a full sync instead. Every instance runs its own. Zero disables the incremental sync.

nss:
sync_interval: 15m

duration, default 24h

How often the full sync runs: it reads every account and group the filters select, which is the only run that notices an entry has gone (missing_since, then a tombstone after tombstone_after) and the only one that clears collisions whose other entry has gone. POST /api/admin/ldap/sync runs one too. Zero disables the scheduled full sync.

nss:
full_sync_interval: 24h

string, default {{.DisplayName}}

Renders the passwd gecos field, with the same text/template engine as the key ID templates. It sees .Name (the mapped name), .DisplayName (the first non-empty attribute of attributes.gecos_attributes, else .Name) and .Attr.<name> for an attribute listed in attributes.gecos_attributes; nothing else is reachable, and an unlisted attribute renders empty. A multi-valued attribute renders with join, as in {{join .Attr.mail “, “}}.

The result is cleaned before any host sees it: invalid UTF-8 becomes .Name, control characters and “:” become spaces, whitespace is collapsed and trimmed, the value is capped at 256 bytes, and an empty result becomes .Name. A template that does not parse, or that names a field which does not exist, fails startup.

nss:
gecos_template: "{{.DisplayName}}"

string, default hybrid

Chooses each account’s primary group, with the names and meanings of sssd’s option (decided 2026-10-18 by the owner: match sssd):

“hybrid”: the directory’s gid. An account whose uid equals its gid, when no directory group holds that gid, gets a private group named after it; a directory group that does hold it is the primary group. An entry without the gid attribute is skipped with a warning.

“true”: every account gets a private group with gid equal to its uid, and the gid attribute is optional. A gid attribute other than the uid is served as a supplementary group when a directory group holds it, and follows the directory without waiting for an admin. An account whose uid is a directory group’s gid, or whose name is a directory group’s name, is not served (sssd refuses whichever of the two it caches second; here the directory group always wins).

“false”: the directory’s gid, no private groups. A gid no directory group holds is still served; it just does not resolve to a name. An entry without the gid attribute is skipped with a warning.

Applied by the server when an account is read, so every host sees the same gid; there is no host-side setting. An account stored without a gid under “true” is not served after a switch to the other modes, which have no primary gid to give it.

nss:
auto_private_groups: "hybrid"

duration, default 168h

How long an entry may stay missing from the directory before its account or group becomes deleted. Measured from the first successful sync that could not find it by id, DN or filter; a directory outage is never a miss. A deleted group still resolves by name and gid, so ls -l keeps showing a name, but is served with no members, so it grants nothing. Zero tombstones an entry at the first sync that misses it.

nss:
tombstone_after: 168h

duration, default 2160h

How long a deleted account or group stays resolvable (an account served as disabled, a group with no members) before it is purged and answers not found. A tombstone holds its number and name the whole time, so neither can be handed to a new entry, and the files it owned on hosts cannot pass to someone else. Zero purges a tombstone at the next sweep and frees its number and name at once.

nss:
tombstone_retention: 2160h

string, default /home/%u

The home directory served for an account whose entry has no home attribute; %u is replaced by the account’s name. Must be an absolute path when nss.enabled is on.

nss:
default_home: "/home/%u"

string, default empty

The shell served for an account whose entry has no shell attribute. Empty serves no shell, and the host applies its own default.

nss:
default_shell: "/bin/bash"

string, default /usr/sbin/nologin

Replaces the shell of every disabled or deleted account, so a locked account cannot run a login shell even where a host ignores the lock. Must be an absolute path when nss.enabled is on.

nss:
disabled_shell: "/usr/sbin/nologin"

int, default 2000

Caps the members returned for one group. A larger group is truncated and flagged as truncated in the response, and the daemon logs it. The sync stores every member it can resolve to an account; this caps only what one lookup returns. Must be greater than zero.

nss:
max_group_members: 2000

number, default 20

The lookups per second allowed for one fleet key from one source address. The address is the one the server observes, honouring forwarded headers only from http.trusted_proxies, the same address the audit log records as source_ip; behind a load balancer listed there each host keeps its own bucket rather than all of them sharing the balancer’s. An overbroad http.trusted_proxies lets a key holder spoof that address with X-Forwarded-For and get a fresh bucket per address; nss.key_source_alert still fires but never blocks. Each server instance keeps its own buckets, so N instances behind a load balancer allow N times this. Must be greater than zero.

nss:
rate_limit: 20

int, default 200

How many lookups one key and source address (as for rate_limit, with the same http.trusted_proxies caveat) may make back to back before rate_limit starts holding them back: a host starting up resolves many names at once. Must be greater than zero, since a zero burst refuses every lookup.

nss:
rate_burst: 200

number, default 5

The not-found lookups per second for one key above which the server raises an alert (a log line and an audit event). It never blocks anything: a host enumerating names it does not know is worth hearing about, not refusing. Zero disables the alert.

nss:
miss_alert_rate: 5

duration, default 2160h

How long the per-key, per-address, per-day usage counts are kept for the usage and rotation reports. Zero disables the prune and keeps every row.

nss:
usage_retention: 2160h

duration, default 8760h

The longest a new fleet key may live, and how long it lives when its creator gives no expiry. A later expiry is trimmed to it, and each rotation renews a key by its own lifetime up to it. Changing this does not touch existing keys until their next rotation. 0 sets no limit: a key created without an expiry then never expires.

nss:
key_max_lifetime: 8760h

duration, default 720h

How long the previous secret stays accepted after a key is rotated, so every host can pick up the new one before the old one stops working. Zero ends the previous secret at the rotation itself.

nss:
key_rotation_grace: 720h

list, default empty

Limits the lookup endpoints to requests from these CIDR networks, checked before the key is: a request from anywhere else is refused with an audit event that carries its address and the key id it presented. The address is the one the server observes, honouring forwarded headers only from http.trusted_proxies, the same address the audit log records as source_ip; a forwarded header from any other peer is ignored. An overbroad http.trusted_proxies lets a key holder pass this list, and forge the audited address, with X-Forwarded-For. Empty allows every address.

nss:
source_allow_list: []

list, default root, admin, daemon, bin, sys

Lists account names whose lookup raises an audit alert, because a directory entry that answers for one of them could shadow a host’s own system account. Empty disables the alert.

nss:
privileged_name_alert: [root, admin, daemon, bin, sys]

int, default 1000

The number of distinct source addresses one key may be seen from in a day before the server raises an audit alert: far more than a fleet’s size suggests the key has leaked. Zero disables the alert.

nss:
key_source_alert: 1000