FreeBSD.software
Home/Guides/How to Set Up LDAP Authentication on FreeBSD
tutorial·2026-04-09·10 min read

How to Set Up LDAP Authentication on FreeBSD

Configure centralized LDAP authentication on FreeBSD with OpenLDAP: server setup, user/group schemas, PAM integration, SSH key lookup, and sudo authorization.

How to Set Up LDAP Authentication on FreeBSD

Managing user accounts across multiple FreeBSD servers with local passwd files does not scale. Every new hire means updating every server. Every password change means logging into every machine. LDAP solves this by storing user accounts, groups, SSH public keys, and sudo rules in a central directory that all servers query at authentication time.

This guide covers a complete LDAP authentication deployment on FreeBSD 14.x: installing and configuring an OpenLDAP server, defining user and group schemas, configuring FreeBSD clients to authenticate against LDAP via PAM and NSS, storing SSH public keys in LDAP for key-based login, and centralizing sudo authorization rules. Every command and configuration has been tested on FreeBSD 14.

Architecture Overview

The setup involves two roles:

  • LDAP server: runs slapd (the OpenLDAP daemon), stores the directory database, handles authentication queries. Typically one primary server with optional replication.
  • LDAP clients: FreeBSD servers that query the LDAP server during login. They run nslcd (name service LDAP client daemon) and use PAM modules to authenticate users against the directory.

A user logs into any client server via SSH. PAM checks the LDAP directory for the user's credentials. NSS resolves the user's UID, GID, home directory, and shell from LDAP. If the user exists in LDAP and the password matches, login succeeds. The user's SSH public key can also be stored in LDAP and fetched by sshd at connection time.

Installing the OpenLDAP Server

Install OpenLDAP server and client tools:

sh
pkg install openldap26-server openldap26-client

Enable the service:

sh
sysrc slapd_enable="YES" sysrc slapd_flags='-h "ldapi:/// ldap:/// ldaps:///"'

The flags tell slapd to listen on Unix socket (ldapi), plaintext (ldap, port 389), and TLS-encrypted (ldaps, port 636) connections.

Configuring the LDAP Server

OpenLDAP uses slapd.conf or the newer cn=config (OLC) backend. This guide uses slapd.conf for clarity and auditability.

Generate a password hash for the LDAP admin:

sh
slappasswd

Copy the hash output. Edit the server configuration:

sh
vi /usr/local/etc/openldap/slapd.conf
shell
include /usr/local/etc/openldap/schema/core.schema include /usr/local/etc/openldap/schema/cosine.schema include /usr/local/etc/openldap/schema/inetorgperson.schema include /usr/local/etc/openldap/schema/nis.schema include /usr/local/etc/openldap/schema/openssh-lpk.schema pidfile /var/run/openldap/slapd.pid argsfile /var/run/openldap/slapd.args modulepath /usr/local/libexec/openldap moduleload back_mdb # TLS configuration TLSCACertificateFile /usr/local/etc/openldap/certs/ca.crt TLSCertificateFile /usr/local/etc/openldap/certs/server.crt TLSCertificateKeyFile /usr/local/etc/openldap/certs/server.key # Access control access to attrs=userPassword by self write by anonymous auth by dn="cn=admin,dc=example,dc=com" write by * none access to * by dn="cn=admin,dc=example,dc=com" write by self read by users read by anonymous auth # Database definition database mdb maxsize 1073741824 suffix "dc=example,dc=com" rootdn "cn=admin,dc=example,dc=com" rootpw {SSHA}YOUR_HASHED_PASSWORD_HERE directory /var/db/openldap-data index objectClass eq index uid eq index cn eq index gidNumber eq index uidNumber eq index memberUid eq

Replace dc=example,dc=com with your actual domain and paste the hash from slappasswd into the rootpw line.

Create the data directory:

sh
mkdir -p /var/db/openldap-data chown ldap:ldap /var/db/openldap-data

SSH Public Key Schema

To store SSH public keys in LDAP, you need the openssh-lpk schema. If it is not included with your OpenLDAP package, create it:

sh
vi /usr/local/etc/openldap/schema/openssh-lpk.schema
shell
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey' DESC 'OpenSSH public key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 ) objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' DESC 'OpenSSH public key objectclass' SUP top AUXILIARY MAY ( sshPublicKey $ uid ) )

TLS Certificate Setup

Production LDAP must use TLS. For testing, generate a self-signed certificate. For production, use certificates from Let's Encrypt or your internal CA.

sh
mkdir -p /usr/local/etc/openldap/certs cd /usr/local/etc/openldap/certs openssl req -new -x509 -nodes -days 3650 \ -keyout server.key -out server.crt \ -subj "/CN=ldap.example.com" cp server.crt ca.crt chown ldap:ldap server.key server.crt ca.crt chmod 400 server.key

Test the configuration and start the server:

sh
slaptest -f /usr/local/etc/openldap/slapd.conf service slapd start

Verify the server is listening:

sh
sockstat -l | grep slapd

Populating the Directory

Create the base directory structure. Write an LDIF file:

sh
vi /tmp/base.ldif
ldif
dn: dc=example,dc=com objectClass: top objectClass: dcObject objectClass: organization o: Example Organization dc: example dn: ou=People,dc=example,dc=com objectClass: organizationalUnit ou: People dn: ou=Groups,dc=example,dc=com objectClass: organizationalUnit ou: Groups

Import it:

sh
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/base.ldif

Adding Users

Create a user entry with SSH public key:

sh
vi /tmp/user-jdoe.ldif
ldif
dn: uid=jdoe,ou=People,dc=example,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount objectClass: ldapPublicKey cn: John Doe sn: Doe uid: jdoe uidNumber: 10001 gidNumber: 10001 homeDirectory: /home/jdoe loginShell: /bin/sh userPassword: {SSHA}HASHED_PASSWORD sshPublicKey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... jdoe@workstation

Generate the password hash and replace:

sh
slappasswd -s "temporarypassword"

Import the user:

sh
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/user-jdoe.ldif

Adding Groups

Create a POSIX group:

sh
vi /tmp/group-sysadmin.ldif
ldif
dn: cn=sysadmin,ou=Groups,dc=example,dc=com objectClass: posixGroup cn: sysadmin gidNumber: 10001 memberUid: jdoe
sh
ldapadd -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/group-sysadmin.ldif

Verify the entries:

sh
ldapsearch -x -b "dc=example,dc=com" "(uid=jdoe)"

Configuring FreeBSD LDAP Clients

On each FreeBSD server that should authenticate against LDAP, install the required packages:

sh
pkg install nss-pam-ldapd openldap26-client

nss-pam-ldapd provides nslcd, the daemon that resolves NSS queries (user, group, host lookups) against LDAP, and the PAM module for authentication.

Configure nslcd

Edit the nslcd configuration:

sh
vi /usr/local/etc/nslcd.conf
shell
uid nslcd gid nslcd uri ldaps://ldap.example.com base dc=example,dc=com binddn cn=admin,dc=example,dc=com bindpw your_admin_password ssl on tls_cacertfile /usr/local/etc/openldap/certs/ca.crt scope sub filter passwd (objectClass=posixAccount) filter group (objectClass=posixGroup) filter shadow (objectClass=shadowAccount) map passwd homeDirectory homeDirectory map passwd loginShell loginShell

For production, create a read-only bind account instead of using the admin DN. Set restrictive permissions:

sh
chmod 600 /usr/local/etc/nslcd.conf chown nslcd:nslcd /usr/local/etc/nslcd.conf

Copy the CA certificate from the LDAP server to the client:

sh
scp ldap-server:/usr/local/etc/openldap/certs/ca.crt /usr/local/etc/openldap/certs/

Configure NSS

Edit /etc/nsswitch.conf to add LDAP as a source for passwd and group:

sh
vi /etc/nsswitch.conf

Change the relevant lines:

shell
passwd: files ldap group: files ldap shadow: files ldap

Configure PAM

Edit the PAM configuration for SSH:

sh
vi /etc/pam.d/sshd

Add the LDAP PAM module. The file should look like:

shell
auth sufficient /usr/local/lib/pam_ldap.so auth required pam_unix.so no_warn try_first_pass auth required pam_login_access.so account sufficient /usr/local/lib/pam_ldap.so account required pam_unix.so session required pam_permit.so session optional /usr/local/lib/pam_ldap.so session optional pam_mkhomedir.so skel=/etc/skel umask=0077 password sufficient /usr/local/lib/pam_ldap.so password required pam_unix.so no_warn try_first_pass

The pam_mkhomedir.so module automatically creates the user's home directory on first login.

Also update /etc/pam.d/system for console and su authentication:

sh
vi /etc/pam.d/system
shell
auth sufficient /usr/local/lib/pam_ldap.so auth required pam_unix.so no_warn try_first_pass account sufficient /usr/local/lib/pam_ldap.so account required pam_unix.so session required pam_permit.so session optional /usr/local/lib/pam_ldap.so session optional pam_mkhomedir.so password sufficient /usr/local/lib/pam_ldap.so password required pam_unix.so no_warn try_first_pass

Enable and Start Services

sh
sysrc nslcd_enable="YES" service nslcd start

Test user resolution:

sh
id jdoe getent passwd jdoe getent group sysadmin

If id jdoe returns the UID, GID, and group memberships from LDAP, NSS integration is working.

SSH Public Key Lookup from LDAP

Configure sshd to fetch authorized keys from LDAP instead of (or in addition to) ~/.ssh/authorized_keys.

Create a script that queries LDAP for the user's SSH key:

sh
vi /usr/local/bin/ldap-ssh-keys.sh
sh
#!/bin/sh ldapsearch -x -H ldaps://ldap.example.com \ -b "ou=People,dc=example,dc=com" \ -D "cn=admin,dc=example,dc=com" \ -w "your_bind_password" \ "(uid=$1)" sshPublicKey | \ sed -n 's/^sshPublicKey: //p'
sh
chmod 755 /usr/local/bin/ldap-ssh-keys.sh

Test it:

sh
/usr/local/bin/ldap-ssh-keys.sh jdoe

It should output the SSH public key stored in LDAP.

Configure sshd to use this script:

sh
vi /etc/ssh/sshd_config

Add:

shell
AuthorizedKeysCommand /usr/local/bin/ldap-ssh-keys.sh AuthorizedKeysCommandUser nobody

Restart SSH:

sh
service sshd restart

Now users can log in with the SSH key stored in their LDAP entry, without needing a local authorized_keys file.

Sudo Authorization via LDAP

Centralize sudo rules so that group membership in LDAP controls who can run privileged commands.

The simplest approach uses local sudoers with LDAP group references. On each client:

sh
pkg install sudo visudo

Add:

shell
%sysadmin ALL=(ALL) ALL

Since sysadmin is a POSIX group in LDAP, and NSS resolves group membership from LDAP, any user whose memberUid appears in the sysadmin group gets sudo access. No per-server configuration needed -- just manage group membership in LDAP.

To add a user to the sysadmin group:

sh
vi /tmp/add-to-group.ldif
ldif
dn: cn=sysadmin,ou=Groups,dc=example,dc=com changetype: modify add: memberUid memberUid: newuser
sh
ldapmodify -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/add-to-group.ldif

The change takes effect on the next sudo invocation -- no restart needed.

Managing Users

Change a user's password:

sh
ldappasswd -x -D "cn=admin,dc=example,dc=com" -W -S "uid=jdoe,ou=People,dc=example,dc=com"

Disable a user (lock the account):

sh
vi /tmp/lock-user.ldif
ldif
dn: uid=jdoe,ou=People,dc=example,dc=com changetype: modify replace: loginShell loginShell: /usr/sbin/nologin
sh
ldapmodify -x -D "cn=admin,dc=example,dc=com" -W -f /tmp/lock-user.ldif

Delete a user:

sh
ldapdelete -x -D "cn=admin,dc=example,dc=com" -W "uid=jdoe,ou=People,dc=example,dc=com"

List all users:

sh
ldapsearch -x -b "ou=People,dc=example,dc=com" "(objectClass=posixAccount)" uid cn

Security Hardening

Use a read-only bind account for clients. Do not use the admin DN in nslcd.conf. Create a dedicated bind user:

ldif
dn: cn=readonly,dc=example,dc=com objectClass: organizationalRole objectClass: simpleSecurityObject cn: readonly userPassword: {SSHA}HASHED_PASSWORD

Update access controls in slapd.conf:

shell
access to attrs=userPassword by self write by anonymous auth by dn="cn=admin,dc=example,dc=com" write by dn="cn=readonly,dc=example,dc=com" read by * none

Require TLS for all connections. Add to slapd.conf:

shell
security tls=1

Restrict network access with PF:

shell
pass in on egress proto tcp from 10.0.0.0/24 to any port { 389, 636 } block in on egress proto tcp to any port { 389, 636 }

Enable audit logging. Add to slapd.conf:

shell
loglevel stats

Configure syslog to capture LDAP logs:

sh
vi /etc/syslog.conf

Add:

shell
local4.* /var/log/ldap.log
sh
service syslogd restart

Troubleshooting

nslcd cannot connect to the LDAP server:

Check connectivity:

sh
openssl s_client -connect ldap.example.com:636 -CAfile /usr/local/etc/openldap/certs/ca.crt

Check nslcd logs:

sh
tail -50 /var/log/messages | grep nslcd

User exists in LDAP but id does not resolve:

Verify nslcd is running:

sh
service nslcd status

Verify NSS configuration:

sh
getent passwd | grep jdoe

Test LDAP query directly:

sh
ldapsearch -x -H ldaps://ldap.example.com -b "ou=People,dc=example,dc=com" "(uid=jdoe)"

PAM authentication fails:

Check PAM module load:

sh
ls -la /usr/local/lib/pam_ldap.so

Test authentication with pamtester if installed, or check auth logs:

sh
tail -50 /var/log/auth.log

FAQ

Can I use LDAP alongside local accounts?

Yes. The nsswitch.conf line passwd: files ldap checks local files first, then LDAP. Local accounts (root, service accounts) always work even if LDAP is unreachable. LDAP users supplement local accounts.

What happens if the LDAP server goes down?

Users already logged in stay logged in. New SSH connections for LDAP-only users will fail because PAM cannot verify credentials. Local accounts are unaffected. For high availability, set up LDAP replication with a second server and list both URIs in nslcd.conf.

Should I use OpenLDAP or 389 Directory Server?

OpenLDAP is the standard on FreeBSD and available as a binary package. 389 Directory Server (formerly Fedora DS) has a richer admin interface but is not as well supported on FreeBSD. For most deployments of under 10,000 users, OpenLDAP is the pragmatic choice.

How do I replicate LDAP for high availability?

OpenLDAP supports syncrepl for replication. Add a syncrepl directive in the consumer's slapd.conf pointing to the provider. The consumer maintains a synchronized copy of the directory. Configure nslcd.conf on clients with multiple uri directives for failover.

Can LDAP users have different shells or home directories per server?

The shell and home directory are stored in the LDAP entry and apply everywhere. If you need per-server overrides, use nslcd.conf map directives to rewrite attributes, or use local /etc/passwd entries to override specific users.

How do I migrate existing local users to LDAP?

Extract user entries from /etc/passwd and /etc/master.passwd, convert them to LDIF format, and import with ldapadd. A script that reads pw usershow -a and generates LDIF entries is the fastest approach. After migration, test thoroughly before removing local accounts.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.