Administration
LDAP and Active Directory Setup
LDAP and Active Directory Setup
This page goes from an empty form to a working directory sign-in, and from a failed sign-in to the reason it failed. It assumes you know your own directory and nothing at all about docu.nexus.
There are two worked examples below, one for Active Directory and one for OpenLDAP. Follow the one that matches your directory. Their defaults differ in ways that decide whether anyone can sign in at all.
What a connection is, and what it is not
A directory connection belongs to the instance. It is not attached to any nexus, and it puts nobody into one. It does three things:
- It signs people in, while its Use for sign-in switch is on.
- It creates their accounts and keeps them in step with the directory: names and email addresses, and switching off the accounts it owns once the directory stops listing them.
- One connection, chosen separately, may let its groups decide who is an instance administrator. See Directory groups and instance administrators.
What somebody can reach once they are signed in is decided exactly as it is for everybody else, by an admin placing them. See Placing directory people.
Only an instance administrator sees or changes connections. The first account made on the setup screen is one; Admin, then Instance Administrators says who else is. Somebody who administers a nexus but not the instance sees the Sign-in methods tab of Authentication read only, the Single sign-on tab, and no LDAP tab at all.
How a directory sign-in actually works
Every sign-in runs the same four steps against a connection.
- Connect to the host, encrypted the way the connection’s Transport says.
- Bind as the service account. This happens only when both a Bind DN and a bind password are stored. With no Bind DN the search runs anonymously. A Bind DN with no password also searches anonymously, which is almost never what anybody meant, so Test connection reports it as Incomplete and never as green.
- Search for the person, using your search base, scope and filter, with
{{username}}replaced by whatever was typed in the sign-in box. - Bind a second time as that person, using the DN the search returned in step 3 and the password they typed.
If step 4 succeeds they are authenticated. docu.nexus then looks for the account this connection already owns, first by the directory username and then by email address, and creates one if Create an account on first successful sign-in is on.
With more than one connection used for sign-in, the four steps run against each connection in the order the connections were added, until one of them accepts the password. A connection that cannot be used is skipped and the next one is tried: it does not answer, TLS fails, or steps 2 or 3 are refused. The reason goes to the server log, not to the sign-in page. Only when no connection could be used does the sign-in page say the directory could not be reached. That also means a person’s password is offered to each directory in turn until one accepts it, so switch on only directories you trust with your people’s passwords.
When step 4 is refused, the sign-in page says only Authentication failed, whatever the directory said. Active Directory reports a locked, disabled or expired account only when the password was right, so naming the reason would tell whoever is at the sign-in page that they had the password. The directory’s words go to the server log.
Two consequences are worth holding on to, because most broken configurations come from missing them.
- Step 3 decides who step 4 tries. The search takes the first entry that matched. A filter that never mentions the username is not a restriction, it is a query that ignores who is signing in.
- The typed password is only ever used for the bind in step 4. It is not stored, and directory accounts get no local password.
Before you start
Collect these from whoever runs the directory.
| You need | Example | Used for |
|---|---|---|
| Host and port, and whether it is encrypted | dc01.corp.example, 636, LDAPS | Transport, Host and Port |
| A service account that can read user entries | svc-docunexus@corp.example | Bind DN and Bind password |
| The container that holds people | OU=Staff,DC=corp,DC=example | Search base DN |
| The attribute people type as their username | sAMAccountName or uid | Username attribute |
| For LDAPS or StartTLS against an internal CA, that CA’s certificate in PEM form | the file your CA publishes | TLS CA certificate |
| Optionally, a group whose members should be instance administrators | CN=Docs Admins,OU=Groups,DC=corp,DC=example | Admin, then Instance Administrators |
The service account only ever reads. Nothing in docu.nexus writes to your directory.
Where the settings live
Open Admin, then Authentication, which sits under People & Access in the panel’s navigation, and pick the LDAP tab.
The tab is one list, Directory connections, with one row per connection in the order sign-in tries them. Each row shows:
- the connection’s name, and its host and port;
- how it is encrypted: LDAPS, StartTLS, or No encryption;
- its status, from the same check Test connection runs (see Test before you save);
- Grants instance administrator, on the one connection whose groups may make somebody an instance administrator;
- how many accounts the connection owns;
- and at the end, its own Use for sign-in switch.
Click anywhere on a row to open that connection’s form below the list. Add connection opens a blank one, and the arrow at the top of a form goes back to the list.
The status checks itself when the tab opens only while there is a single connection. With several, each row waits, reading Not checked (or Off for a connection not used for sign-in), until you press Check all, so opening the tab does not bind to every directory at once: each check is a bind as the service account, and in Active Directory a failed one counts towards that account’s lock-out.
A sentence appears above the list only when no connection signs anybody in, and it says which of the two switches below is the reason.
The same connections also appear under Admin, then Connections, one row each, with the scope Instance.
Two switches decide whether anyone signs in
- LDAP / Active Directory on the Sign-in methods tab turns directory sign-in on or off for the whole instance, without touching any connection. While it is off, Sync users now is refused as well.
- Use for sign-in on each connection, on its row and on its form. A new connection is saved with it on.
The sign-in page grows a second tab beside the email and password form once both are on for at least one connection. The tab is labelled with that connection’s Name while exactly one connection is used for sign-in, and LDAP / Active Directory when there are several, because one form serves them all.
Worked example: Active Directory
Every value below is a placeholder. Substitute your own.
Connection, at the top of the form
| Field | Value |
|---|---|
| Name | Corp sign in |
| Transport | LDAPS (636) |
| Host | dc01.corp.example |
| Port | leave it empty, which means 636 |
| Bind DN | svc-docunexus@corp.example |
| Bind password | the service account password |
| Search base DN | OU=Staff,DC=corp,DC=example |
Under the port the form shows the address it will connect to, here ldaps://dc01.corp.example. If you paste a whole URL such as ldaps://dc01.corp.example:636 into Host, the form reads it and fills in Transport, Host and Port for you.
Active Directory accepts either the user principal name shown above or a full DN such as CN=svc-docunexus,OU=Services,DC=corp,DC=example as the Bind DN. Whatever you type is handed to the directory unchanged.
Advanced
| Field | Value |
|---|---|
| TLS CA certificate | paste your issuing CA in PEM form if the domain controller’s certificate comes from an internal CA |
| Verify the directory’s certificate | on |
| Search filter | leave it alone |
| Search scope | Subtree (default) |
| Active Directory bind domain | leave it empty |
| Username attribute | sAMAccountName |
| Email attribute | mail |
| Display name attribute | displayName |
| Group membership attribute | memberOf |
| First name attribute | givenName |
| Last name attribute | sn |
| Create an account on first successful sign-in | on |
| Refresh name, email and groups from the directory on every sign-in | on |
| Adopt local accounts | off |
Then:
- Press Test connection. It should come back green, saying it bound with the service account and how many people the sign-in filter matches. Read Test before you save before you trust that result.
- Press Save connection. The save binds with the new password before it stores it; see Saving a bind password.
- On the Sign-in methods tab, switch LDAP / Active Directory on if it is not on already.
- Sign out, or open a private window, and sign in on the new tab as a real person from the directory.
- Place that person where they will work: see Placing directory people.
Because you left the filter alone, it follows your attribute mapping and matches either the short username or the email address. The uid in the default is replaced with sAMAccountName for you.
Worked example: OpenLDAP
Connection, at the top of the form
| Field | Value |
|---|---|
| Name | Directory |
| Transport | StartTLS (389) |
| Host | ldap01.corp.example |
| Port | leave it empty, which means 389 |
| Bind DN | cn=svc-docunexus,ou=Services,dc=corp,dc=example |
| Bind password | the service account password |
| Search base DN | ou=People,dc=corp,dc=example |
If your server offers LDAPS instead, choose LDAPS (636). The transport is one choice, so there is no way to ask for both.
Advanced
| Field | Value |
|---|---|
| TLS CA certificate | paste your CA in PEM form if it is not in the system trust store |
| Verify the directory’s certificate | on |
| Search filter | leave it alone |
| Search scope | Subtree (default) |
| Active Directory bind domain | leave it empty, it is an Active Directory feature |
| Attribute mapping | the stock defaults already suit OpenLDAP: uid, mail, cn, memberOf, givenName, sn. Change nothing unless your schema differs |
| Provisioning | as for Active Directory above |
Then test, save, switch on, sign in as a real person, and place them, exactly as above.
The search filter, and the one mistake that matters
The filter is the single field most likely to lock everyone out or let everyone in. It sits under Advanced, in Directory search, and it behaves in two distinct modes.
Left at a built-in default, it is rebuilt from your attribute mapping every time it is used, so it matches either the username or the email address.
Two strings count as a default, and they behave identically. A new connection shows this one in the box, unless the deployment prefills another:
(|(uid={{username}})(mail={{username}}))
A connection saved by an older release may show the shorter one, which is recognised just the same:
(uid={{username}})
Leave either alone and the filter that actually runs is built from your mapping. Map the username attribute to sAMAccountName and it becomes this, with no editing on your part:
(|(sAMAccountName={{username}})(mail={{username}}))
So a box reading (uid={{username}}) does not mean sign-in matches the username only. That is an untouched default, and it accepts the email address too.
Changed to anything else, it is used exactly as you typed it. Nothing is added to it. This is deliberate: quietly appending an alternative to your filter would widen a restriction you meant to impose, such as a group clause.
That leads to the mistake. Some products take a username attribute and a separate user filter and combine the two for you. This one does not. If you paste in a bare group filter:
(memberOf=CN=Docs Users,OU=Groups,DC=corp,DC=example)
then the search never mentions the person signing in. It returns the group’s members, the first one is taken, and the password is checked against that entry. The group clause has become the whole query rather than an extra condition. Test connection cannot catch this for you: it runs the filter with any username in place of {{username}}, and a filter that ignores the username finds the group’s members and reads green.
A custom filter must locate the user itself. Combine your conditions with & and keep the username clause inside:
(&(objectClass=user)(sAMAccountName={{username}})(memberOf=CN=Docs Users,OU=Groups,DC=corp,DC=example))
The placeholder is {{username}}, spelled exactly like that, in lower case, with two braces on each side. Any other spelling is sent to the directory as literal text and matches nothing.
Special characters in what someone types are escaped before substitution, so a username containing *, (, ) or \ cannot turn into a wildcard.
Nested groups in Active Directory
memberOf in Active Directory carries direct membership only. Someone in a group that is itself a member of your target group will not match a plain memberOf clause, and Test connection says Finds nobody when that leaves the filter matching no one. Ask the directory to walk the chain with the matching rule OID:
(&(sAMAccountName={{username}})(memberOf:1.2.840.113556.1.4.1941:=CN=Docs Users,OU=Groups,DC=corp,DC=example))
This works for the filter, because the directory evaluates it. It does not help a group listed for instance administrators, which is compared against the group values the directory returned on the entry. Those are direct memberships, so add people to that group directly.
The filter is also the sync filter
Sync users now reuses the same filter with every {{username}} replaced by *. The default becomes a query for every entry that has a username or an email, and a group filter like the one above becomes a query for that group’s members. A filter with no placeholder at all is used unchanged, so it imports exactly what it says.
Attribute defaults are OpenLDAP defaults
The stock mapping suits OpenLDAP. Active Directory needs two of the six changed. The mapping sits under Advanced, in Attribute mapping.
| Field | Default | OpenLDAP | Active Directory |
|---|---|---|---|
| Username attribute | uid | uid | sAMAccountName |
| Email attribute | mail | mail | mail |
| Display name attribute | cn | cn | displayName |
| Group membership attribute | memberOf | memberOf | memberOf |
| First name attribute | givenName | givenName | givenName |
| Last name attribute | sn | sn | sn |
Only the attributes named in this table are requested from the directory, so nothing else on the entry is read. If you map a name the entry does not carry, that field arrives empty.
An account’s name is the entry’s display name when it has one; otherwise its first and last names, joined by a space; otherwise the username.
An entry with no email value gets a placeholder address of the form username@ldap.local so the account can be created at all. If you see those in the People list, your email attribute is mapped to something the entries do not carry.
Choosing the transport
Transport is one choice of three, and the address under the port is composed from it, the host and the port.
| What you want | Transport | Usual port |
|---|---|---|
| Encrypted from the first byte, the usual choice | LDAPS | 636 |
| A plain connection upgraded to TLS before anything is sent | StartTLS | 389 |
| No encryption at all, only ever on a trusted segment or a test rig | None | 389 |
None sends everything in plain text, the bind password and every person’s password included.
An empty port means the transport’s usual one, which the field shows as its placeholder. Some sites really do run LDAPS on 3269 or StartTLS on 3268, so any port is accepted. A port that looks like the other transport’s default, 389 with LDAPS or 636 with the other two, earns a warning with an (i) under the field, never a refusal.
LDAPS and StartTLS together have never connected: StartTLS cannot run inside a connection that LDAPS has already encrypted. The form offers no way to ask for both, and a save that asks for both is refused. A connection saved that way by an older release has never connected and still does not: its row reads LDAPS and StartTLS in red, its form opens with no transport chosen and says so, and Save stays disabled until you pick one. Nothing picks for you, because the choice changes who can sign in.
The certificate
Under Advanced, Certificate verification applies to LDAPS and StartTLS only. With the transport at None the card says there is no certificate to verify.
- Verify the directory’s certificate is on by default. Turning it off accepts any certificate at all, and a warning stays on the form for as long as it is off. Treat it as a temporary diagnostic.
- TLS CA certificate is where you paste the issuing CA, in PEM form from
-----BEGIN CERTIFICATE-----to-----END CERTIFICATE-----, when the directory’s certificate comes from a CA the container does not trust. Leave it empty and the system trust store is used. A paste that does not read as a certificate is refused when you save.
Leave verification on. If the certificate comes from an internal CA, paste that CA rather than turning verification off.
Once a CA is saved it is not shown again as text. The form shows Stored, and under it one line per certificate: whose it is, who issued it, and when it expires. One that has expired, or that expires within 30 days, is flagged there. Its SHA-256 fingerprint is behind the (i), for comparing against what your CA publishes.
- Replace opens the field to paste a new one.
- Remove marks it Removed on save, and the next save goes back to the system trust store. Undo takes that back before you save.
A CA stored on a connection whose transport is now None is still shown, so that Remove stays within reach.
The bind password
Until a password is stored, Bind password is an ordinary field. Once one is stored, the form shows Stored in its place, with Replace and Remove, and no field at all. That is deliberate: a browser that fills a saved password into anything that looks like a sign-in form has nothing to fill.
- Replace opens an empty field for the new password, with Cancel to keep the stored one.
- Remove marks it Removed on save. Undo takes that back. Saving it leaves a Bind DN with no password, which searches anonymously, and the status then reads No bind password.
Saving a bind password
Three checks run before a new password is stored.
It goes with a Bind DN. The password is only ever sent in a bind beside the Bind DN, so a new password saved with the Bind DN empty is refused. For an anonymous bind, clear the Bind DN and Remove the password.
It is not your own password. A new bind password that is the password you sign in to docu.nexus with is refused: Not saved: that is the password you sign in to docu.nexus with, not the directory service account password, and a browser may have filled it in. This check needs no directory, so it holds even when the directory cannot be reached.
The directory is asked. A save that changes where or how the service account binds (the Bind DN, the password, the transport, host or port, certificate verification or the CA), and has a password to bind with, binds first. A save that changes only the name, the filter or the mapping does not bind at all, because every bind counts in Active Directory.
| What the directory says | What the save does |
|---|---|
The credentials are wrong: invalid credentials (result 49), on Active Directory data 52e (wrong password) or data 525 (no such account), or a Bind DN that does not exist (result 32) | Refused. Nothing is saved, and the sentence at the top of the app names the field to fix. |
The account is right but cannot bind now. On Active Directory: 530 or 531 (logon hours or workstation restriction), 532 (password expired), 533 (account disabled), 701 (account expired), 773 (must change password), 775 (locked out) | Saved, with a warning at the top of the app. Active Directory reports these only for the correct password, so refusing would refuse exactly the password you need to store while the account is fixed. |
| Nothing about the password: the directory could not be reached, or TLS failed | Saved, with a warning. |
| It binds | Saved, with a green check beside the button. |
There is no override, and none is needed: everything that is not refused is stored.
A saved password is only ever sent to the connection it was saved for: the address shown under the port is the one it was saved with, StartTLS and certificate verification have not been switched off, and the CA is the same. A save that moves the connection away from any of those without a new password is refused, and nothing is saved: the password would otherwise go wherever the connection now points, at every sign-in. Press Replace and type the password, which is then checked against the new values before it is stored, or Remove it.
Test before you save
Test connection sits under the search base. It runs the values on the form, including anything you have not saved, so you can try a change before committing it and prove a fix without saving a broken configuration over a working one. The result stays on screen until you change a field.
It runs one check, the same one behind the status on each row and the one the server runs at every start:
- Connect with the transport you chose.
- Bind as the service account, or anonymously when there is no Bind DN.
- Run the sign-in search under the search base, with every
{{username}}replaced by*, and count who it finds, up to 50.
| Result | Colour | Meaning |
|---|---|---|
| Connected | green | It bound, and the sign-in filter matches somebody: “The sign-in filter matches 12 people under OU=Staff,DC=corp,DC=example.” Only this is green. |
| Finds nobody, or a reason such as Search base not found | amber | It bound, but the search found no one or was refused. Every sign-in would be refused, so this is the filter or the search base to fix, not the network. |
| Incomplete, No bind password, No search base | amber | Something the check needs is missing. |
| Not tested | amber | See the stored password rule below. |
| A short reason such as CA not trusted, Wrong bind DN or password, Host not found | red | It failed. The sentence beside it says which field to change, and the directory’s own words sit behind the (i). |
On a saved connection, a password or CA you have not replaced is used as stored, so a connection that pins an internal CA tests exactly the way it signs people in. The stored password is sent only while the form still describes the connection it was saved for, by the rule above. Change the address under the port or the CA, or switch StartTLS or verification off, and Test answers Not tested without dialling anything. Replace the password to test the new values.
On a new connection that has never been saved, nothing is stored, so Test uses only what is on the form.
What a green result does not tell you is that the filter picks out the right person. A filter that ignores the username matches people and reads green; see The search filter. The only end-to-end test is a real sign-in by a real person.
Placing directory people
A directory places nobody into a nexus. The first time somebody signs in through a connection, or a sync reaches them, they get an account and the private nexus every account gets, and nothing else: no shared nexus and no role. That is the same starting point as an account made any other way, and they are placed the same way.
- Open Admin, then People & Access, and press + Person.
- Choose Existing account. In Who, type their exact username or their exact email address. Somebody who shares none of your nexuses, which is every new directory person, is found only by that exact username or email, never by part of a name.
- Pick the Nexus and the Role, and add them.
An instance administrator can also search by part of a name, and sees every account on the instance in the People list, including directory accounts that belong to no nexus yet. The Nexus list in + Person offers only nexuses you administer, so an instance administrator joins a nexus before placing people in it.
To have people placed before their first sign-in, run Sync users now first. It creates an account for everybody the filter finds, and those accounts can be placed straight away. A connection can be synced while its Use for sign-in is off, so this works before anybody can sign in through it.
Nothing the directory says changes a nexus role afterwards. Joining or leaving a directory group promotes, demotes and removes nobody.
Two things the directory does still decide about the account itself:
- An account the directory stops listing is switched off at the next sync, if this connection owns it.
- An account the directory lists again is switched back on at the next sync. That includes an account you deactivated by hand, because nothing records why an account was switched off. To keep a directory person out for good, remove them from the directory, or from what the filter matches, as well.
Directory groups and instance administrators
A directory’s groups decide one thing: who is an instance administrator, and only through the one connection chosen for it. They never decide a nexus role.
Set it up under Admin, then Instance Administrators, in Automatic grants.
- Under Where the group comes from, choose Directory group, and add each group’s full DN. Press Enter after each one, or paste several at once separated by new lines or semicolons. A comma is part of a DN, never a separator between DNs, so paste each DN whole, from its first
CN=to its lastDC=. - Under Directory evidence comes from, choose the one connection whose group memberships may grant it. Memberships reported through every other connection are ignored, and with no connection chosen no directory grants anybody.
The chosen connection carries Grants instance administrator in the LDAP list. Choosing it does not let it sign anybody in, which is its Use for sign-in switch, and switching on Use for sign-in does not let a connection grant anything. Deleting the chosen connection means no directory grants instance administrator until you choose another.
A group is compared with the values the connection’s Group membership attribute returned on the person’s entry: the whole DN, ignoring case and any spaces around the commas and equals signs. Those are direct memberships only. The grant is checked at every directory sign-in and sync, and withdrawn when the membership goes away.
The deployment can pin both answers instead, with INSTANCE_ADMIN_GROUPS and INSTANCE_ADMIN_LDAP_CONNECTION. While either is set it outranks the panel. See the Configuration Reference.
Field reference
Connection
| Field | What it is | Leave it blank and |
|---|---|---|
| Name | What this connection is called in the list, and the label of the sign-in page’s tab while it is the only connection used for sign-in. Required: it is how people tell connections apart. | you cannot save. |
| Transport | LDAPS, StartTLS or None. See Choosing the transport. | not applicable, one is always chosen. |
| Host | The directory’s host name. Paste a whole ldap:// or ldaps:// URL here and it is read into Transport, Host and Port. | you cannot save or test. |
| Port | The port to connect to. | the transport’s usual port: 636 for LDAPS, 389 otherwise. |
| Bind DN | The service account that performs the user search. A DN, or a user principal name on Active Directory. | the search runs anonymously, which most directories restrict or refuse. |
| Bind password | The service account’s password. Shown as Stored once saved, with Replace and Remove, and held in the database in plaintext, as noted under Before you start. | with a Bind DN set, the search runs anonymously and the status reads No bind password. |
| Search base DN | Where to look for people. | you cannot save. |
Use for sign-in
| Field | What it is | Effect when off |
|---|---|---|
| Sign people in through this connection | The connection’s one sign-in switch, the same one as on its row. On for a new connection. | nobody signs in through it. It stays saved, and it can still be synced: it creates, refreshes and switches off the accounts it owns, but it takes over no account it does not already own. |
To turn directory sign-in off for the whole instance, use Sign-in methods instead.
Advanced: certificate verification
| Field | What it is | Leave it blank and |
|---|---|---|
| TLS CA certificate | A PEM certificate to trust, for an internal CA. Shown as Stored once saved, with a summary, Replace and Remove. | the system trust store is used. |
| Verify the directory’s certificate | Checks the directory’s certificate. On by default. | not applicable, it is a switch. Off accepts any certificate. |
Advanced: directory search
| Field | What it is | Leave it blank and |
|---|---|---|
| Search filter | How to find one person from what they typed. {{username}} is the placeholder. | the built-in default applies, matching the username attribute or the email attribute. |
| Search scope | Subtree searches the base and everything under it. One level searches only its direct children. Base examines the base entry itself. | Subtree, the default and the right answer for nearly everyone. |
| Active Directory bind domain | Active Directory only. When set, the second bind uses username@domain instead of the DN found by the search. | the DN from the search is used, which works on Active Directory too and accepts both username and email sign-ins. Recommended. |
Advanced: attribute mapping
Covered in the table above. Each field names one directory attribute to read from the entry.
Do not clear one. An empty name is saved as an empty name, and the default search filter built from it comes out malformed. The LDAP client refuses to send a filter it cannot parse, so the sign-in fails before the directory is ever asked anything.
Advanced: provisioning
| Field | What it is | Effect when off |
|---|---|---|
| Create an account on first successful sign-in | Creates an account the first time someone signs in successfully, with their private nexus and nothing else. | a person with no account is refused with “User not found and auto-provisioning is disabled”, even though their password was correct. Accounts must then come from Sync users now or be created by hand. |
| Refresh name, email and groups from the directory on every sign-in | Refreshes name, address and directory identifiers at each sign-in. | local edits stick, and a directory rename only arrives when you run Sync users now. |
| Adopt local accounts | Lets this directory take over an existing account that somebody signs into with a password, when the directory lists the same email address. The password on that account then stops being a way in. It asks before it turns on, and a warning stays on the form while it is on. An account holding instance administrator is never taken over, whatever this says. | a directory person whose address belongs to a password account is not signed in to it, and Sync users now lists them under Skipped with the reason. |
Turn Adopt local accounts on for a migration from password sign-in to the directory, and off again when the migration is done.
Actions
Save connection on a new connection, Save changes on a saved one. A success earns a green check beside the button. A refusal, and a warning from checking the bind password, is a sentence at the top of the app.
Sync users now creates or refreshes an account for everybody the filter matches, takes over the accounts it is allowed to, switches off the accounts it owns that the directory no longer lists, and switches back on the ones it lists again. It places nobody into a nexus. When it finishes, a sentence at the top of the app counts what it did, and anything it skipped is listed with its reason under Skipped in the last sync. It is refused while directory sign-in is off on Sign-in methods, and disabled when no licensed seats remain, because every account it would create would be refused.
Delete connection asks first, and the question names what the delete takes away: that nobody signs in through it any more, how many accounts it owns (they become owned by no connection), and, for the connection that grants instance administrator, that no directory will grant it any more. It cannot be undone. To pause a connection instead, switch off Use for sign-in.
You cannot take away the last way in. If email and password sign-in is off and this connection is what everyone signs in with, switching off its Use for sign-in and deleting it are both refused with That would leave no way to sign in to this instance, and nothing changes. Switch another sign-in method on first.
Every change to a connection, every flip of its sign-in switch, every deletion, sync and account takeover is recorded in the instance’s audit log. The two secrets are recorded only as kept, cleared or replaced, never their values.
Environment variables
The LDAP_* variables in .env fill in the form for a new connection, and nothing else. The form says so when they did. Once a connection is saved, what is saved is what sign-in uses, and editing a variable afterwards changes no saved connection. They do not switch anything on either: directory sign-in needs a saved connection with Use for sign-in on, and the LDAP / Active Directory switch on Sign-in methods.
Three variables used to do more and now do nothing. The server says so in its log at every start while one is set:
| Variable | What happened to it |
|---|---|
LDAP_BIND_CREDENTIALS | It filled in the bind password of the next new connection, whatever host that connection named. Type the password into the form instead, where the save checks it. |
LDAP_ADMIN_GROUPS | It made directory group members admins of a nexus. A directory no longer makes anybody a nexus admin; for instance administrators, see Directory groups and instance administrators. |
LDAP_DEFAULT_ROLE | It was the role a directory placed people at. A directory places nobody now; an admin of each nexus picks the role when they place a person. |
LDAP_SIGNIN_CONNECTIONS is on its way out. While it is set it can only narrow sign-in: a connection it does not name signs nobody in, whatever its switch says, and its row reads Not in LDAP_SIGNIN_CONNECTIONS. It never switches a connection on. Keep it exactly as it is until the release that removes it. The version before this one read it before anything stored in the database, and rolling back restores the database but not .env, so a changed value would change who signs in after a rollback; see Rolling back. Decide who signs in with Use for sign-in instead.
Two names people bring from other products’ documentation do not exist here and are read by nothing:
| Wrong, and silently ignored | Instead |
|---|---|
LDAP_BIND_PASSWORD | Type the bind password into the form. |
LDAP_USER_FILTER | LDAP_SEARCH_FILTER |
The complete list of names that are read is in the Configuration Reference. There is no environment variable for the CA certificate or the connection name; set those in the form.
Troubleshooting by symptom
Where to look first
The status on each row, and Test connection on the form, say in words what is wrong and which field to change. The server also checks every connection used for sign-in when it starts, and writes one line for each that is not well. From your install directory:
docker compose logs app | grep -i ldap
A sign-in whose search matched nobody is logged as LDAP login: no entry matched under <base> for connection <id>, without the username. A connection a sign-in could not use is logged as LDAP login: connection <id> could not be used: followed by the reason in words.
The status is green but nobody can sign in
Green means the service account bound and the filter matches somebody. Work down this list.
- No tab on the sign-in page. Directory sign-in is off on Sign-in methods, or no connection has Use for sign-in on. The sentence above the list says which.
- The row reads Not in LDAP_SIGNIN_CONNECTIONS. Add the connection’s id to that variable in
.envand apply the change withdocker compose up -d. Keep a copy of the value it had before: rolling back to the previous version needs that value, not this one. - The filter ignores the username. See the next symptom.
- The Active Directory bind domain is set, and people sign in with an email address. See the warning under the field reference.
- They type something the filter does not match, such as an email address against a custom filter that names only the username attribute.
Then try a real sign-in and read the error. “User not found” means the search matched nothing for what was typed. Anything mentioning invalid credentials means the search found somebody and the second bind was refused.
Everyone can sign in, whatever group they are in
Almost always a custom filter with no {{username}} in it, most often a bare memberOf clause pasted in to restrict access. Custom filters are used verbatim, so that filter returns the group’s members and the first one is taken. The password is then checked against that entry rather than against the person signing in, and the session is attached to that entry’s identity. If the Active Directory bind domain is also set, the bind is whatever-they-typed@domain and the found entry is not even consulted.
Put the username clause back inside the filter and re-test with two real sign-ins, one that should be allowed and one that should not.
Test says Finds nobody
The service account bound, and the sign-in filter matches no one under the search base. That is the filter or the base, not the network. In Active Directory, check first whether a memberOf clause needs the nested form described in Nested groups in Active Directory. Search base not found means the base DN does not exist: check it against the directory, character for character.
Test says Not tested
The form no longer describes the connection the password was saved for: the address under the port changed, StartTLS or verification was switched off, or the CA was replaced or removed. The stored password is only sent where it was saved for. Press Replace, type the password, and test again. Saving those values without doing so is refused for the same reason.
Only some people can sign in
Work down this list.
- The search base is too deep. People in a sibling OU are invisible. Move the base up.
- The search scope is One level while entries live in child containers. Use Subtree.
- Their entry lacks the attribute you mapped as the username attribute, so the filter cannot match them.
- A custom filter carries a clause they do not satisfy, such as an objectClass or a group.
- The filter matches more than one entry, for example two accounts sharing a mail value. The first entry the directory returns wins, and it may not be theirs.
- Account creation is off and they have no account yet.
- Their address belongs to an account somebody signs into with a password, and Adopt local accounts is off. Sync users now names them under Skipped.
- Their account was switched off, by a sync after the directory stopped listing them, or by hand. A deactivated account is refused however good the password.
- The seat limit is reached. Existing accounts keep working; only new ones are refused, with a message naming the licence tier and the seat count.
- They have two-factor authentication on their account. The code prompt after the directory accepts them is expected, not a failure.
Someone signs in but sees nothing
That is how a directory person starts. Signing in creates the account and its private nexus, and nothing else; a directory places nobody into a shared nexus. Place them as described in Placing directory people.
It worked and then it stopped
- The service account password was rotated or expired. Every sign-in now fails at the search step, and the row’s status says so. Press Replace on the bind password, type the new one, and save; the save checks it.
- The service account was locked out. The status reads Account locked. Unlock it in Active Directory before testing again, because every failed bind counts towards the lock-out.
- The certificate expired or was reissued under a different CA. Affects LDAPS and StartTLS only, and only with verification on, which is where it should stay. The CA summary on the form flags a pinned CA within 30 days of expiry. Replace the CA rather than turning verification off.
- The filter was edited. Any change away from either built-in default switches it to verbatim mode, including a change that looks cosmetic.
- The connection was switched off or deleted. With a single connection used for sign-in, the tab disappears from the sign-in page, which is the giveaway. With more than one it stays, and only the people who relied on that connection are turned away.
- The seat limit was reached. Only new people are affected.
Sign-in works with the username but not the email address, or the other way round
The built-in default matches both. A custom filter matches only what it says, so (sAMAccountName={{username}}) accepts the short name only.
If email sign-in specifically is broken while usernames work, check the Active Directory bind domain. When it is set, an email address becomes user@corp.example@corp.example at the bind and is refused.
The email tab says “This account uses external authentication”
The right message on the wrong tab. Directory accounts have no local password, so they cannot use the email and password form. Sign in on the directory tab instead.
What the status and Test say, and what to do
The sentence beside the result says what to change. The short labels, and the codes behind them:
| Label | Behind it | What to do |
|---|---|---|
| Wrong bind DN or password | result 49; Active Directory data 52e | Check the Bind DN, then Replace the password. |
| Bind DN not found | Active Directory data 525, or result 32 at the bind | The Bind DN names nobody. Copy it out of the directory. |
| Password expired, Account disabled, Account expired, Password must be changed, Account locked, Account restricted | Active Directory data 532, 533, 701, 773, 775, 530 or 531 | Fix the service account in Active Directory. The password on the form is correct. |
| Bind refused | any other Active Directory data code, or a bind the directory does not allow | Read the directory’s words behind the (i). data 80090346 means the domain controller enforces LDAP channel binding, which is not supported. |
| Encryption required | result 8 (strong authentication required) | The directory refuses binds in plain text. Choose LDAPS or StartTLS. |
| StartTLS failed | the directory refused or could not start TLS | Often a missing or expired certificate on the directory. If the port speaks LDAPS, choose LDAPS. |
| TLS handshake failed, Connection closed | the socket closed or the handshake failed | The port may speak plain LDAP (choose StartTLS or None), or LDAPS (choose LDAPS). |
| CA not trusted | the certificate chain does not lead to a trusted CA | Paste the issuing CA under TLS CA certificate. |
| Certificate expired | a certificate in the chain is out of date | Renew it on the directory, or pin the CA that issued its replacement. |
| Certificate name mismatch | the certificate is for another name | Connect by a host name the certificate lists. |
| CA unreadable | the stored CA is not a certificate | Replace it with the PEM text of the CA. |
| Host not found, Connection refused, Unreachable, Timed out | name resolution, nothing listening, no route, or no answer | Wrong host, wrong port, or a firewall between this server and the directory. |
| Search base not found, Malformed DN, No read access, Filter does not parse | results 32, 34 and 50 during the search, or a filter the client cannot parse | Fix the search base, the service account’s rights, or the filter’s parentheses. |
| Directory unavailable | results 51 or 52 | The directory is busy. Try again shortly. |