Manage OpenLDAP Users and Groups with LDIF

Add, search, modify, rename, reset passwords, manage group membership, and delete OpenLDAP POSIX users and groups using LDIF.

Published

Updated

Read time 21 min read

Reviewed byDeepak Prasad

Manage OpenLDAP POSIX users and groups using LDIF commands

This guide explains how to manage OpenLDAP users and groups using LDIF and the standard OpenLDAP command-line tools. If DN, objectClass, or memberUid terms are new, read LDAP and OpenLDAP basics first.

You will learn how to:

  • Add POSIX users and groups
  • Search for directory entries
  • Modify user attributes
  • Add and remove group members
  • Change a user's primary group
  • Rename users and groups
  • Move users between organizational units
  • Change or reset LDAP passwords
  • Verify changes through LDAP and SSSD
  • Apply bulk LDIF safely
  • Delete users and groups safely

The procedure applies to OpenLDAP 2.6 deployments across the RHEL family, including Red Hat Enterprise Linux, Rocky Linux, AlmaLinux, Oracle Linux, and CentOS Stream.

Tested on: Rocky Linux 10.2 with OpenLDAP 2.6 from EPEL.

The lab uses:

Setting Value
OpenLDAP server ldap-server.example.com
OpenLDAP client ldap-client.example.com
Base DN dc=example,dc=com
Users OU ou=people,dc=example,dc=com
Groups OU ou=groups,dc=example,dc=com
Administrator DN cn=admin,dc=example,dc=com
LDAP transport StartTLS on port 389
User schema inetOrgPerson and posixAccount
Group schema posixGroup with memberUid

Complete these lessons first:

IMPORTANT
All administrator binds in this guide use required StartTLS with -ZZ. Do not send administrator or user passwords over an unencrypted LDAP connection.

Understand OpenLDAP POSIX Users and Groups

Your directory suffix holds person and group entries under organizational units. After the install lesson you should already have ou=people and ou=groups beneath the base DN:

text
dc=example,dc=com
├── ou=people
└── ou=groups

The remaining sections add users and groups beneath these existing organizational units.

A Linux login account in OpenLDAP combines person and POSIX object classes:

Object class Purpose
top Base object class every entry needs
inetOrgPerson Name, surname, email, and other person attributes
posixAccount UID, GID, home directory, and login shell

Groups for NSS clients use posixGroup:

Object class Purpose
top Base object class
posixGroup Numeric GID and RFC2307 group members

These attributes appear on almost every account you manage:

Attribute Example Purpose
uid jdoe Login name and user RDN
uidNumber 10001 Numeric Linux UID
gidNumber 10001 Numeric primary group
cn John Doe Common name
sn Doe Required surname
homeDirectory /home/jdoe User home path
loginShell /bin/bash Login shell
mail [email protected] Email address

Group entries use:

Attribute Example Purpose
cn developers Group name and RDN
gidNumber 10001 Numeric Linux GID
memberUid jdoe RFC2307 supplementary member

Primary group versus supplementary membership

The user's gidNumber names the primary POSIX group—the one id prints first and the one new files inherit unless you change it:

text
User entry:
gidNumber: 10001

A posixGroup entry can list supplementary members with memberUid:

text
Group entry:
memberUid: jdoe

A matching gidNumber does not automatically create a memberUid value, and adding memberUid does not change the user's primary gidNumber. Plan both deliberately when you design SSSD client lookups.

RFC2307 and RFC2307bis

This guide stays on RFC2307 throughout the main examples:

text
ldap_schema = rfc2307
group membership attribute = memberUid
group member value = username

RFC2307bis is the common alternative:

text
ldap_schema = rfc2307bis
group membership attribute = member
group member value = complete user DN

SSSD documents memberUid as the RFC2307 membership model and DN-valued member as the major RFC2307bis difference. Pick one model for the directory and match it on every client—do not mix both in the same examples without a defined design. When you choose DN-valued member on groupOfNames, configure reverse memberOf in Configure OpenLDAP memberOf and referential integrity overlays.


Prepare the Directory and Allocate UID/GID Numbers

Before you add an account, confirm the organizational units from the install lesson are present. Directory lookups in this chapter use the ldapsearch command; filter and base-DN flags must match what you configure in Apache or SSSD later.

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(|(ou=people)(ou=groups))" dn ou

Sample output:

output
dn: ou=people,dc=example,dc=com
ou: people

dn: ou=groups,dc=example,dc=com
ou: groups

Both OUs are in place, so new users and groups have a valid parent entry.

Search for a conflicting username before ldapadd. Search the full suffix so a match is not missed when accounts live outside ou=people:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(&(objectClass=posixAccount)(uid=jdoe))" dn uid uidNumber

An empty result means the name is free. A hit means you must pick another uid or modify the existing entry instead.

Check whether the UID number is already assigned anywhere under the suffix:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(&(objectClass=posixAccount)(uidNumber=10001))" dn uid uidNumber

Sample output when UID 10001 is taken:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
uid: jdoe
uidNumber: 10001

Repeat the same idea for groups—search by cn and by gidNumber before you create a new posixGroup.

Document an allocation policy so operators do not collide with system accounts or bulk-migrated users:

Range Suggested use
Below 1000 System and service accounts; avoid for normal LDAP users
10000–19999 Normal directory users
20000–29999 Application or service identities
Separate documented range Shared or role-based groups

The exact ranges are an organizational decision. The hard requirement is uniqueness across local /etc/passwd, LDAP, containers, and any other identity source visible to the same clients.

To see the highest UID currently stored for any POSIX account under the suffix:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(&(objectClass=posixAccount)(uidNumber=*))" uidNumber | awk '/^uidNumber:/ {print $2}' | sort -n | tail -1

Use the result as a hint, not an automatic allocator—your ID-management policy may reserve gaps or block ranges outright.


Add an OpenLDAP Group and User

Create the primary group before the user when gidNumber on the account should reference that group.

Add a POSIX group

Create add-developers-group.ldif:

ldif
dn: cn=developers,ou=groups,dc=example,dc=com
objectClass: top
objectClass: posixGroup
cn: developers
gidNumber: 10001
description: Application development team

Import the group with ldapadd:

bash
ldapadd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f add-developers-group.ldif

Sample output:

output
adding new entry "cn=developers,ou=groups,dc=example,dc=com"

Confirm the group entry:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(cn=developers)" dn cn gidNumber memberUid

Sample output:

output
dn: cn=developers,ou=groups,dc=example,dc=com
cn: developers
gidNumber: 10001

Add a POSIX user

Create add-jdoe-user.ldif without a password attribute:

ldif
dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: top
objectClass: inetOrgPerson
objectClass: posixAccount
cn: John Doe
sn: Doe
givenName: John
uid: jdoe
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/jdoe
loginShell: /bin/bash
mail: [email protected]
description: Application developer

Add the entry:

bash
ldapadd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f add-jdoe-user.ldif

Sample output:

output
adding new entry "uid=jdoe,ou=people,dc=example,dc=com"

Set the initial password in a separate step so the secret never lands in the LDIF file:

bash
ldappasswd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -S "uid=jdoe,ou=people,dc=example,dc=com"

The -S flag prompts for the new password interactively, which keeps it out of shell history and process arguments.

Verify the user attributes:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=people,dc=example,dc=com" -LLL "(uid=jdoe)" dn objectClass cn sn uid uidNumber gidNumber homeDirectory loginShell mail

Sample output:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
objectClass: top
objectClass: inetOrgPerson
objectClass: posixAccount
cn: John Doe
sn: Doe
uid: jdoe
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/jdoe
loginShell: /bin/bash
mail: [email protected]

Confirm the user can bind:

bash
ldapwhoami -x -ZZ -H ldap://ldap-server.example.com -D "uid=jdoe,ou=people,dc=example,dc=com" -W

Sample output:

output
dn:uid=jdoe,ou=people,dc=example,dc=com

The bind succeeded—the directory recognizes the account and password.


Search OpenLDAP Users and Groups

Every ldapsearch call combines a search base, optional scope, a filter, and the attributes you want back.

Component Example
Search base ou=people,dc=example,dc=com
Scope Base, one level, or subtree (default)
Filter (uid=jdoe)
Requested attributes uid uidNumber gidNumber

List all users

Search the complete suffix when you need every POSIX account, including entries moved to another OU such as ou=contractors:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "dc=example,dc=com" -LLL "(objectClass=posixAccount)" dn uid cn uidNumber gidNumber

Sample output:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
cn: John Doe
uid: jdoe
uidNumber: 10001
gidNumber: 10001

Search by username

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=people,dc=example,dc=com" -LLL "(uid=jdoe)" dn cn mail homeDirectory loginShell

Search by UID number

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=people,dc=example,dc=com" -LLL "(uidNumber=10001)" dn uid cn

Search by partial name

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=people,dc=example,dc=com" -LLL "(cn=*Doe*)" dn uid cn mail

List all POSIX groups

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(objectClass=posixGroup)" dn cn gidNumber memberUid

Find groups containing a user

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(memberUid=jdoe)" dn cn gidNumber

At this point the result is empty. jdoe uses developers as the primary group through the user's gidNumber, but the group does not yet contain a memberUid: jdoe value. After you add supplementary membership in a later step, this filter returns only groups that list the username in memberUid.

Find users with a specific primary GID

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "dc=example,dc=com" -LLL "(&(objectClass=posixAccount)(gidNumber=10001))" dn uid cn

Sample output:

output
dn: uid=jdoe,ou=people,dc=example,dc=com
cn: John Doe
uid: jdoe

(memberUid=jdoe) answers which groups list the username. (gidNumber=10001) answers which users use 10001 as their primary group. Those are different questions.


Modify Users and Group Membership

LDIF modifications use changetype: modify with one or more operations:

Operation Purpose
add Add a new attribute value
replace Replace every current value of an attribute
delete Remove an attribute or one exact value

Separate operations on the same entry with a hyphen on its own line:

ldif
dn: TARGET_DN
changetype: modify
replace: attribute
attribute: value
-
add: anotherAttribute
anotherAttribute: value

Modify user attributes

Before assigning a login shell, confirm that the path exists on every client where the LDAP account can log in. Use /bin/bash instead when Zsh is not installed.

On each relevant client:

bash
test -x /bin/zsh && grep -Fx '/bin/zsh' /etc/shells

When either check fails, do not assign /bin/zsh until the shell is installed and approved on that client. On a default Rocky Linux 10 host without the zsh package, the command produces no output and exits with a non-zero status—the path is missing and /etc/shells does not list it.

OpenLDAP still accepts loginShell: /bin/zsh when you run ldapmodify; the directory stores the value even if the shell is absent on LDAP clients. Interactive login can fail later when PAM or SSH tries to start a missing shell.

Create modify-jdoe.ldif:

ldif
dn: uid=jdoe,ou=people,dc=example,dc=com
changetype: modify
replace: loginShell
loginShell: /bin/zsh
-
replace: mail
mail: [email protected]
-
add: telephoneNumber
telephoneNumber: +1 555 0100

Apply the change with ldapmodify:

bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f modify-jdoe.ldif

Sample output:

output
modifying entry "uid=jdoe,ou=people,dc=example,dc=com"

Read back only the attributes you changed:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=people,dc=example,dc=com" -LLL "(uid=jdoe)" loginShell mail telephoneNumber

Sample output:

output
loginShell: /bin/zsh
mail: [email protected]
telephoneNumber: +1 555 0100

Delete one user attribute

Supply the exact value when the attribute is multi-valued:

bash
cat > delete-jdoe-telephone.ldif <<'EOF'
dn: uid=jdoe,ou=people,dc=example,dc=com
changetype: modify
delete: telephoneNumber
telephoneNumber: +1 555 0100
EOF
bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f delete-jdoe-telephone.ldif

Sample output:

output
modifying entry "uid=jdoe,ou=people,dc=example,dc=com"

Confirm the attribute is gone:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "uid=jdoe,ou=people,dc=example,dc=com" -s base -LLL telephoneNumber dn

Sample output:

output
dn: uid=jdoe,ou=people,dc=example,dc=com

The deletion succeeded when the entry is returned without a telephoneNumber: line.

Add a user to a supplementary group

Create the supplementary group first when it does not exist:

bash
cat > add-operations-group.ldif <<'EOF'
dn: cn=operations,ou=groups,dc=example,dc=com
objectClass: top
objectClass: posixGroup
cn: operations
gidNumber: 10002
description: Operations team
EOF
bash
ldapadd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f add-operations-group.ldif

Sample output:

output
adding new entry "cn=operations,ou=groups,dc=example,dc=com"

Create the membership change file:

bash
cat > add-jdoe-to-operations.ldif <<'EOF'
dn: cn=operations,ou=groups,dc=example,dc=com
changetype: modify
add: memberUid
memberUid: jdoe
EOF

Apply the membership change:

bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f add-jdoe-to-operations.ldif

Verify the group now lists the username:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(memberUid=jdoe)" dn cn memberUid

Sample output:

output
dn: cn=operations,ou=groups,dc=example,dc=com
cn: operations
memberUid: jdoe

The next two subsections demonstrate independent administrative operations. The continuing lab used later in the rename and SSSD verification sections keeps jdoe in the operations supplementary group and retains primary GID 10001 for developers. Apply these examples only when you intend to change that state.

Remove a supplementary membership

bash
cat > remove-jdoe-from-operations.ldif <<'EOF'
dn: cn=operations,ou=groups,dc=example,dc=com
changetype: modify
delete: memberUid
memberUid: jdoe
EOF
bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f remove-jdoe-from-operations.ldif

Verify the membership is gone:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(memberUid=jdoe)" dn cn memberUid

An empty result confirms jdoe no longer appears in any group's memberUid values.

Change the user's primary group

Confirm the target GID exists:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(gidNumber=10002)" dn cn gidNumber
bash
cat > change-jdoe-primary-group.ldif <<'EOF'
dn: uid=jdoe,ou=people,dc=example,dc=com
changetype: modify
replace: gidNumber
gidNumber: 10002
EOF
bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f change-jdoe-primary-group.ldif

Changing gidNumber does not add or remove memberUid values anywhere—you update those separately when supplementary membership should change too. If you applied this example during the continuous walkthrough, restore gidNumber: 10001 before the rename section.


Rename or Move an OpenLDAP User or Group

The rename, move, password, client-verification, bulk-operation, and deletion steps below continue one lab account through the guide. Substitute each command's DN with the entry's current location whenever you adapt the examples to your own directory.

Use ldapmodrdn for RDN or superior changes. Ordinary ldapmodify cannot rename an entry's DN.

The -r flag removes the old RDN attribute value after the rename. Without -r, the previous value remains on the entry as an additional attribute.

Rename a user

Rename uid=jdoe to uid=john.doe:

bash
ldapmodrdn -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -r "uid=jdoe,ou=people,dc=example,dc=com" "uid=john.doe"

Confirm only the new RDN remains:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=people,dc=example,dc=com" -LLL "(|(uid=jdoe)(uid=john.doe))" dn uid cn

Sample output:

output
dn: uid=john.doe,ou=people,dc=example,dc=com
cn: John Doe
uid: john.doe

Update RFC2307 group memberships after renaming

Renaming a user does not rewrite memberUid strings in group entries.

Find stale references:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(memberUid=jdoe)" dn cn memberUid

Update each affected group:

bash
cat > rename-jdoe-group-membership.ldif <<'EOF'
dn: cn=operations,ou=groups,dc=example,dc=com
changetype: modify
delete: memberUid
memberUid: jdoe
-
add: memberUid
memberUid: john.doe
EOF
bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f rename-jdoe-group-membership.ldif

Verify no stale username remains:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=groups,dc=example,dc=com" -LLL "(|(memberUid=jdoe)(memberUid=john.doe))" dn cn memberUid

Sample output before the group rename:

output
dn: cn=operations,ou=groups,dc=example,dc=com
cn: operations
memberUid: john.doe

Only john.doe should appear—jdoe must not remain after the membership update.

Renaming uid changes the LDAP login attribute and DN, but it does not automatically change homeDirectory, rename an existing home directory on clients, or update file paths. Modify homeDirectory and handle filesystem changes separately when your naming policy requires them.

Also review sudo rules, SSH keys, automount maps, application role mappings, and automation that still mention the old uid or DN.

Move a user to another OU

Create the target OU when it does not exist—ldapmodrdn -s fails with No such object (32) when the superior entry is missing:

bash
cat > add-contractors-ou.ldif <<'EOF'
dn: ou=contractors,dc=example,dc=com
objectClass: top
objectClass: organizationalUnit
ou: contractors
EOF
bash
ldapadd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f add-contractors-ou.ldif

Before moving an account outside ou=people, confirm that the SSSD user search base includes the target OU. A client configured with ldap_user_search_base = ou=people,dc=example,dc=com will no longer find a user moved to ou=contractors.

SSSD searches only the configured ldap_search_base or ldap_user_search_base. A full-suffix subtree base includes both OUs, while an ou=people base does not.

Check the client setting:

bash
sudo grep -E '^[[:space:]]*(ldap_search_base|ldap_user_search_base)[[:space:]]*=' /etc/sssd/sssd.conf

A full-suffix configuration can discover both locations:

text
ldap_search_base = dc=example,dc=com

After changing an SSSD search base:

bash
sudo systemctl restart sssd
bash
sudo sss_cache -E

Keep detailed SSSD configuration in the OpenLDAP client with SSSD lesson.

Move the entry under the new superior:

bash
ldapmodrdn -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -s "ou=contractors,dc=example,dc=com" "uid=john.doe,ou=people,dc=example,dc=com" "uid=john.doe"

DN-valued references—RFC2307bis member attributes, ACL targets, or application settings—may still need manual updates after a move.

ldapmodrdn -r removes the old RDN value, while -s moves the entry beneath a new superior; neither operation updates unrelated attributes or application references automatically. After this step the user DN is uid=john.doe,ou=contractors,dc=example,dc=com.

Rename a group

bash
ldapmodrdn -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -r "cn=operations,ou=groups,dc=example,dc=com" "cn=platform-operations"

After a group rename, review SSSD access filters, sudo policies, file ownership expectations, and scripts that still use the old cn value.


Change or Reset an OpenLDAP User Password

ldappasswd sends the password through the LDAP Password Modify extended operation (RFC 3062), allowing slapd to apply its configured password hashing. When the password-policy overlay and an applicable policy are enabled, the server can also enforce configured password requirements. ACLs and the authorization identity still determine whether the change is allowed.

Administrator resets another user's password

bash
ldappasswd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -S "uid=john.doe,ou=contractors,dc=example,dc=com"

The tool prompts for the administrator password, then twice for the new user password.

Verify the user can bind with the new secret:

bash
ldapwhoami -x -ZZ -H ldap://ldap-server.example.com -D "uid=john.doe,ou=contractors,dc=example,dc=com" -W

User changes their own password

bash
ldappasswd -x -ZZ -H ldap://ldap-server.example.com -D "uid=john.doe,ou=contractors,dc=example,dc=com" -W -S "uid=john.doe,ou=contractors,dc=example,dc=com"

That requires an ACL allowing self-service password changes. Configure userPassword and self-service rules in OpenLDAP ACL Configuration with Practical Examples. Server-side expiration, lockout, and pwdReset behavior belong in the password-policy lesson for this series—not in this operational guide.

Avoid passwords in command history

Do not publish commands such as:

text
ldappasswd -s PlainTextPassword
ldapmodify ... userPassword: PlainTextPassword

For automation, read secrets from a protected file or vault and avoid logging the command input.


Verify Changes from an SSSD Client

ldapsearch proves the directory holds the new data. NSS and SSSD on a client prove Linux utilities see the same identity.

When you need an immediate read after a change on a host that already runs SSSD against this directory, clear the cache:

bash
sudo sss_cache -E

After the rename and group rename in this walkthrough, look up the passwd entry by the new login name:

bash
getent passwd john.doe

Sample output:

output
john.doe:*:10001:10001:John Doe:/home/jdoe:/bin/zsh

The home path still reflects the original homeDirectory value until you change it separately. The shell comes from the earlier loginShell change to /bin/zsh.

Check UID, primary GID, and supplementary groups:

bash
id john.doe

Sample output:

output
uid=10001(john.doe) gid=10001(developers) groups=10001(developers),10002(platform-operations)

Resolve a group by name:

bash
getent group developers

Sample output:

output
developers:*:10001:

The user still has developers as the primary group through gidNumber, even though memberUid is empty on the group entry. With RFC2307, supplementary group membership is stored in the multi-valued memberUid attribute. SSSD distinguishes this from the primary group selected through the account's gidNumber.

Confirm the renamed supplementary group:

bash
getent group platform-operations

Sample output:

output
platform-operations:*:10002:john.doe

ldapsearch validates the directory. getent and id validate NSS and SSSD. An SSH login exercises the full authentication path. This page does not repeat SSSD installation or sssd.conf tuning—that stays in the client lesson.


Perform Bulk Operations

Add multiple entries

Place complete entries in one LDIF file separated by blank lines:

ldif
dn: cn=group1,ou=groups,dc=example,dc=com
objectClass: top
objectClass: posixGroup
cn: group1
gidNumber: 11001

dn: cn=group2,ou=groups,dc=example,dc=com
objectClass: top
objectClass: posixGroup
cn: group2
gidNumber: 11002

Dry-run the file without applying changes:

bash
ldapadd -n -v -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f bulk-groups.ldif

-n -v checks the command construction and LDIF parsing and displays the proposed operations. It does not submit them to the server, so it cannot detect server-side schema, ACL, uniqueness, or existing-entry failures. Validate risky batches against a test directory or snapshot before production.

Apply the batch:

bash
ldapadd -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f bulk-groups.ldif

Use -c only when partial success is acceptable—it continues after errors and can leave only part of the batch applied. Always review the output and search for every DN you expected.

Apply multiple modifications

Put one changetype: modify record per target DN, separated by blank lines. Validate with:

bash
ldapmodify -n -v -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f bulk-modifications.ldif

As with ldapadd -n, this pass only exercises client-side parsing—it does not prove the server will accept the modifications.

For each production change:

text
1. Search for the current entry
2. Check UID/GID and group references
3. Save the original LDIF
4. Review the change file
5. Test with -n and -v for LDIF syntax (not server acceptance)
6. Apply one logical change
7. Search the entry again
8. Verify from an SSSD client
9. Preserve the change record

Delete OpenLDAP Users and Groups Safely

Deletion is the final lifecycle step in this walkthrough. Run the client checks in the previous section before you remove the lab account.

Start every deletion with a reference check so you do not orphan memberships or leave applications pointing at a missing DN.

Check user references

Search RFC2307 membership:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "ou=groups,dc=example,dc=com" -LLL "(memberUid=john.doe)" dn cn memberUid

Search DN-valued references when your deployment uses them:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(member=uid=john.doe,ou=contractors,dc=example,dc=com)" dn member

Remove supplementary memberUid values before you delete the user entry. In this lab the group has already been renamed to platform-operations:

bash
cat > remove-john-doe-from-platform-operations.ldif <<'EOF'
dn: cn=platform-operations,ou=groups,dc=example,dc=com
changetype: modify
delete: memberUid
memberUid: john.doe
EOF
bash
ldapmodify -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -f remove-john-doe-from-platform-operations.ldif

Confirm no RFC2307 group still references the user:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "ou=groups,dc=example,dc=com" -LLL "(memberUid=john.doe)" dn cn memberUid

Proceed with ldapdelete only when that search is empty.

Delete a user

bash
ldapdelete -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W "uid=john.doe,ou=contractors,dc=example,dc=com"

Confirm the entry is gone:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -b "ou=contractors,dc=example,dc=com" -LLL "(uid=john.doe)" dn

An empty result means the delete succeeded.

Check before deleting a group

Find users whose primary GID matches the group anywhere under the suffix:

bash
ldapsearch -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W -b "dc=example,dc=com" -LLL "(&(objectClass=posixAccount)(gidNumber=10002))" dn uid gidNumber

Reassign every affected user's gidNumber before you remove a primary group.

Delete a group

bash
ldapdelete -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W "cn=platform-operations,ou=groups,dc=example,dc=com"
WARNING
Do not use ldapdelete -r for routine user or group removal. Recursive delete removes the selected entry and every descendant without individual confirmation and can erase a large subtree.

Troubleshoot OpenLDAP User and Group Management

Error or symptom Likely cause Recommended check
Already exists (68) The entry DN already exists Search the exact DN before adding
No such object (32) Parent OU or target DN does not exist Verify the base and parent entries
Invalid credentials (49) Wrong bind DN or password Test with ldapwhoami
Object class violation (65) Required attribute is absent or attribute is not allowed Check object-class requirements
Invalid syntax (21) Attribute value does not match its schema syntax Check numeric and DN attributes
Type or value exists (20) The exact attribute value is already present Query current values before add
Constraint violation (19) Unique or schema constraint was violated Search UID, GID, mail, or other unique values
Naming violation (64) New RDN is incompatible with the entry Ensure the RDN attribute exists and is valid
Not allowed on non-leaf (66) Entry still has child entries Search one level below the target
Password reset fails ACL, TLS, or password-policy restriction Use ldappasswd -ZZ and inspect server logs
User rename succeeds but group access is lost Old memberUid or DN references remain Search and update every group reference
id shows no supplementary groups SSSD schema does not match group membership model Compare RFC2307 versus RFC2307bis settings
LDAP shows new data but client shows old data SSSD cache is stale Run sss_cache -E and repeat the lookup
User exists but login fails Identity lookup and password authentication are separate Test ldapwhoami, getent, id, PAM, and SSH in order
Deleted group still appears SSSD or application cache is stale Clear the applicable cache and retest

Confirm your bind identity when ACL errors are suspected:

bash
ldapwhoami -x -ZZ -H ldap://ldap-server.example.com -D "cn=admin,dc=example,dc=com" -W

Shows the DN slapd accepted for the bind. Use this when Error 49 or 50 appears on add or modify.

Read recent server messages:

bash
sudo journalctl -u slapd -n 100 --no-pager

Look for object-class, constraint, and ACL messages around the failed ldapadd or ldapmodify.

On a client host, inspect SSSD:

bash
sudo journalctl -u sssd -n 100 --no-pager

Check when LDAP data changed but getent or id still shows old values.

bash
sudo sssctl user-checks -a acct -s sshd john.doe

Separates directory identity from SSH account authorization when the user exists in LDAP but login fails.


References


Summary

You can now:

  • Plan unique UID and GID values before every add
  • Create POSIX groups and users with ldapadd, then set passwords with ldappasswd -S
  • Search users and groups with practical LDAP filters
  • Modify attributes with add, replace, and delete
  • Manage primary gidNumber and supplementary memberUid membership separately
  • Rename or move entries with ldapmodrdn and fix RFC2307 references afterward
  • Confirm directory and client views with ldapsearch, getent, and id
  • Dry-run bulk LDIF with -n -v for parsing, then apply on a test directory before production
  • Delete users and groups only after reference checks

Frequently Asked Questions

1. Which object classes are required for an OpenLDAP Linux user?

Use inetOrgPerson for person attributes and posixAccount for UID, primary GID, home directory, and login shell. Every entry also needs the top object class.

2. What is the difference between uidNumber and gidNumber?

uidNumber is the numeric Linux user ID on the account entry. gidNumber is the numeric ID of the user primary POSIX group. They are often equal when the primary group is a private group with the same number.

3. What is the difference between a primary group and memberUid membership?

gidNumber on the user entry defines the primary group shown by id and file ownership. memberUid on a posixGroup entry records RFC2307 supplementary membership and does not change the user primary gidNumber.

4. How do I add an OpenLDAP user to a group?

For RFC2307 posixGroup entries, ldapmodify add memberUid with the username on the group entry. Changing gidNumber on the user switches the primary group instead.

5. What is the difference between RFC2307 and RFC2307bis groups?

RFC2307 stores usernames in memberUid. RFC2307bis stores full user DNs in member. SSSD and your directory must use the same model—do not mix both on one deployment without a deliberate design.

6. Should I store userPassword in the user-creation LDIF?

No for routine administration. Create the entry first, then set the password with ldappasswd -S so the secret never appears in the LDIF file, shell history, or process listings.

7. How do I reset an OpenLDAP user password?

Bind as an administrator and run ldappasswd -ZZ -S against the user's current DN. slapd hashes the password through the LDAP Password Modify extended operation; policy checks apply only when the password-policy overlay and an applicable policy are configured.

8. What is the difference between ldapmodify and ldapmodrdn?

ldapmodify changes attribute values on an existing DN. ldapmodrdn renames the entry or moves it under a new superior OU without recreating the whole record.

9. Does renaming an LDAP user update group membership automatically?

No. RFC2307 memberUid values are plain usernames, so you must search every group and update stale memberUid values after a uid rename.

10. How do I move an LDAP user to another OU?

Use ldapmodrdn with -s and the target OU DN as the new superior. DN-valued references in ACLs, RFC2307bis member attributes, or applications may still need manual updates.

11. How do I check whether a UID or GID is already assigned?

Search the full suffix for (uidNumber=N) or (uid=name) on posixAccount entries and ou=groups for (gidNumber=N) or (cn=name) before ldapadd. Treat collisions across LDAP, local files, and other identity sources as hard errors.

12. Why does ldapsearch show a group membership but id does not?

ldapsearch reads the directory directly. id uses NSS and SSSD, which cache results and must match your RFC2307 versus RFC2307bis schema choice. Clear the cache with sss_cache -E and confirm the client ldap_schema setting.

13. How do I safely delete an OpenLDAP user?

Search for memberUid and DN-valued member references, remove supplementary memberships, reassign or delete dependent data, then ldapdelete the user DN after a final search confirms nothing still points at it.

14. Why should I avoid ldapdelete -r?

Recursive delete removes the target entry and every descendant in one step without per-entry confirmation. That is appropriate for tearing down a whole subtree, not for routine user or group removal.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …