Syncing from LDAP or Active Directory
Keep mailboxes in step with the directory you already have, on a schedule — who arrives, who changes, who leaves — without typing anyone in twice.
If your people already exist in Active Directory or an LDAP server, you should not have to keep them here as well. A sync reads that directory on a schedule and makes this one agree: people who appeared get mailboxes, people whose names changed get the new ones, people who left stop being able to sign in.
It works with Active Directory, OpenLDAP, and anything else that speaks LDAP.
Before you start
You need a read-only account in your directory — Nixt Server never writes to it — and the distinguished name of the part of the tree your people are in.
Turn it on
sudo -u versealx versealx-server admin put tenants/1/directory-sync \
"url=ldaps://dc1.example.com" \
"bindDn=cn=versealx,ou=service,dc=example,dc=com" \
"bindPassword=the-password" \
"baseDn=ou=people,dc=example,dc=com"
Then see what it would do, without doing any of it:
sudo -u versealx versealx-server sync 1 --dry-run
tenant 1 — ldaps://dc1.example.com
+ ada@example.com (Ada Lovelace)
+ bob@example.com (Bob Stone)
nothing was changed (--dry-run)
If that looks right, run it:
sudo -u versealx versealx-server sync 1
After that it runs on its own, every everyMinutes.
| Field | Meaning |
|---|---|
url | ldaps://host or ldap://host. Use ldaps:// unless the directory is on the same machine. |
bindDn | The account that reads the directory. |
bindPassword | Its password. Never shown again; leave it out of later calls to keep it. |
baseDn | Where under the tree to look. |
userFilter | Which entries are people. (&(objectClass=person)(mail=*)) if you leave it out. |
groupFilter | Which entries are groups. Leave it out and no groups are synced. |
mapping | Which attribute becomes which field — see below. |
everyMinutes | How often. 60 if you leave it out; 0 runs only when you say so. |
enabled | false keeps the settings and stops the schedule. |
mostAtOnce, atLeast | The safety limits — see below. |
passThrough | true and the people this sync creates sign in with their directory password — see below. false if you leave it out. |
Check what is configured, what it manages and how the last run went:
sudo -u versealx versealx-server admin get tenants/1/directory-sync
The attributes in that answer are exactly what a search will ask your directory for. Compare it with what your directory publishes if a sync finds fewer people than you expected.
Which attribute becomes which
The defaults are what Active Directory publishes. On OpenLDAP you will want to change two:
sudo -u versealx versealx-server admin put tenants/1/directory-sync \
"url=ldaps://ldap.example.com" \
"bindDn=cn=versealx,dc=example,dc=com" \
"baseDn=ou=people,dc=example,dc=com" \
'mapping={"id":"entryUUID","disabledFlag":""}'
| Field | Default | What it is |
|---|---|---|
id | objectGUID | The identifier that survives a rename. entryUUID on OpenLDAP. |
address | mail | The mailbox address. |
displayName | displayName | What to call them. Falls back to cn, then to the address. |
givenName, familyName | givenName, sn | Kept alongside the account. |
disabledFlag | userAccountControl | Whether the account is in use. Set it to "" on a directory that has no such attribute. |
groupName | cn | A group’s name. |
groupAddress | mail | A group’s address, where it has one. |
groupMembers | member | A group’s members, as distinguished names. |
id is the one that matters. People are matched on it, never on their address, so somebody who marries and has their name — and therefore their whole distinguished name — changed in the directory keeps the same mailbox. Matching on the address would give them a new empty one.
Mail-enabled groups on OpenLDAP
The standard groupOfNames class does not permit a mail attribute, so a group that is also a distribution address needs an auxiliary class:
dn: cn=Engineering,ou=groups,dc=example,dc=com
objectClass: groupOfNames
objectClass: extensibleObject
cn: Engineering
mail: eng@example.com
member: cn=Ada Lovelace,ou=people,dc=example,dc=com
Active Directory groups carry mail already. A group with no address still syncs — it is a set of people, and not everything a directory calls a group is something mail is sent to.
What a sync will and will not do
It never deletes anyone. Someone the directory no longer holds is offboarded with the organisation’s default choices: they stop being able to sign in, their mail goes on arriving and is handed on, and their mailbox stays where it is (see When somebody leaves). With offboarding.on_deprovision set to disable, they are only disabled. A sync is a reading of your directory, and a reading is not a reason to destroy mail. Remove the mailbox yourself when you mean to.
It only touches what it created. Accounts you made by hand, and accounts SCIM provisioned, are never changed or disabled by a sync — so you can run both without them fighting.
A run happens whole or not at all. If one entry cannot be taken — most often an address in a domain the organisation does not own — the run changes nothing and says which entry and why. Nobody is added halfway through a run that failed.
Every change is in the audit log. Each person added, changed, suspended or let back in, each group added, changed or removed, and one line for the run with its counts, written by the sync itself (role directory-sync). See Roles and the audit log.
It stops itself if something looks wrong. A run is refused whole, changing nothing, when:
- the directory returns nothing at all while the sync manages somebody, or
- it would disable more than a fifth of what it manages and at least five accounts.
A bind that fails open, a filter with a typo and a base DN pointing at an empty subtree all read as “everybody has left”, and all three have emptied a real mail system. The refusal names the numbers:
this run would disable 30 of the 100 accounts it manages, which is more
than the 20% one run may; check the filter and the base first, and
confirm the run when you mean it
Both numbers are yours to change (mostAtOnce, atLeast). After a genuine reorganisation, say you mean it:
sudo -u versealx versealx-server sync 1 --confirm
A scheduled run is never confirmed automatically. The valve exists for exactly the run nobody is watching.
Signing in with the directory password
If you have a directory but no identity provider for single sign-on, your people can sign in to their mail with the password they already use for everything else:
sudo -u versealx versealx-server admin put tenants/1/directory-sync \
"url=ldaps://dc1.example.com" \
"bindDn=cn=versealx,ou=service,dc=example,dc=com" \
"baseDn=ou=people,dc=example,dc=com" \
"passThrough=true"
A PUT sets everything at once, so send the other fields you rely on — mapping, the filters, the schedule — in the same call.
From then on, when one of the people this sync created signs in — to IMAP, POP3, sending mail, calendars, or a sign-in page — Nixt Server finds them in the directory and asks the directory whether the password is right. There is nothing to set up per person, and a password changed in the directory works at once.
- Only the people the sync created. Accounts you made here, and accounts SCIM provisioned, keep the passwords set here.
- App passwords still work. They belong to this server, one per device, and are checked here.
- A password set here stops working for those people. The directory decides.
- The directory must use
ldaps://, orldap://to the same machine. People’s passwords are sent to it on every sign-in, so a directory reached over plainldap://across a network is refused. - Choose
PLAINorLOGINin mail apps that let you pick, or let them sign in with a password over TLS as most do by default.SCRAMneeds a password stored here, and these people do not have one.
If an address in the directory is given to somebody else, the old mailbox does not open to the new person’s password: the entry has to still be the person the mailbox was made for.
If the directory cannot be reached, those people cannot sign in until it can — and it does not count against them. A mail client will ask for the password again; the account is not locked, and it works again as soon as the directory answers. It shows in monitoring as vsx_authentications_total{outcome="unavailable"}.
Wrong passwords do count, the same as for anyone else, and lockout applies before the directory is ever asked.
A directory with its own certificate authority
Most on-premises directories present a certificate signed by an internal CA rather than a public one:
[federation]
trust = "/etc/versealx-server/internal-ca.pem"
This adds a root; the certificate is still checked. It is the same setting single sign-on uses.
When a sync does not work
sudo -u versealx versealx-server admin get tenants/1/directory-sync
The lastRun in that answer holds the problem from the last attempt, so you can see a sync that has been failing since Tuesday without running one.
| What it says | What to do |
|---|---|
the directory refused the bind for … | The bind DN or password is wrong, or the account is locked. |
the directory refused the search … | The base DN does not exist, or the bind account cannot read it. |
the directory answered 4 for … | A size or time limit. Your directory is sending part of an answer — narrow the filter, or raise the limit for the bind account. A partial answer is refused rather than treated as the whole directory. |
cannot reach … | The address, the port, or the firewall. |
returned nothing at all | The filter matches nobody. Try it with ldapsearch first. |
has no stable identifier | The id attribute is not being returned — check the mapping against what your directory publishes. |
that domain is not this tenant's | The address attribute is giving addresses in a domain this tenant does not own. |
pass-through sends people's passwords to the directory, so it needs ldaps://… | Point url at ldaps://, or turn passThrough off. |
pass-through signs people in against the directory a sync reads, and this tenant has no sync configured | Configure the sync first; passThrough goes in the same call. |
A sync that finds fewer people than you expected is usually the filter. Entries with no address and no identifier are passed over rather than stopping the run — a service account with no mail is not a mailbox.
Something unclear or out of date on this page? Tell us.