Quick answer
Your LDAP client is trying to bind to a domain controller without signing or encryption, and the DC is refusing it. Use LDAPS (port 636), StartTLS, or enable SASL signing — or temporarily relax LDAPServerIntegrity if you're diagnosing a legacy app.
Why you're seeing 0x2155
Microsoft has been tightening LDAP security since the March 2020 ADV190023 advisory, and then again in the January 2020 default changes. Domain controllers now treat unsigned, unencrypted LDAP binds as unacceptable unless you deliberately lower the bar. If your app talks to a DC on port 389 with a plain simple bind and no TLS, you'll get ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION — decimal 8533, hex 0x2155. The DC is basically saying: I need packet privacy or better before I'll let you in.
I've seen this hit hardest with older Java apps using JNDI, PHP ldap_bind() against an unencrypted URI, and homegrown PowerShell scripts using [System.DirectoryServices.DirectoryEntry] with an LDAP:// path and no AuthenticationTypes. The minute Windows Server 2019 or 2022 domain controllers enforce LDAP signing, every one of those breaks at once. It also shows up on hybrid setups where a Linux appliance (NAS, VoIP PBX, backup appliance) tries to do simple binds over 389.
Fix it step by step
- Confirm the error. Grab the full error string and the LDAP result code from your app. On Windows DCs, look for Event ID 2887 (unsigned binds summary) or 2889 (unsigned simple bind attempts) in the Directory Service log. Those events name the client IP and the offending bind type.
- Move to LDAPS on 636. The cleanest fix. Point your app at
ldaps://dc01.corp.local:636instead ofldap://dc01:389. Confirm port 636 is listening:
If it fails, install the DC's root CA cert on the client and confirm the DC has a valid server-auth certificate issued to its FQDN.Test-NetConnection dc01.corp.local -Port 636 - Or enable StartTLS on 389. Some apps can't switch to 636 but support the StartTLS extended operation. In PHP:
In .NET, useldap_start_tls($conn);AuthenticationTypes.SecureSocketsLayer. Java JNDI needscom.sun.jndi.ldap.connect.pool=trueplus a proper SSL socket factory — messy, which is why 636 is usually easier. - Or enable SASL signing (Negotiate or Kerberos). If you can't do TLS at all, at least sign. On Windows clients, use
AuthenticationTypes.Signing(and ideallySealing).
Kerberos-based binds are automatically signed, so switching from simple bind to Negotiate fixes it without touching TLS.$entry = New-Object System.DirectoryServices.DirectoryEntry( "LDAP://dc01.corp.local", "user@corp.local", "password", [System.DirectoryServices.AuthenticationTypes]::Secure -bor [System.DirectoryServices.AuthenticationTypes]::Signing ) - Verify the DC policy. On the DC, check what's actually enforced:
reg query "HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" /v LDAPServerIntegrity reg query "HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters" /v LdapEnforceChannelBindingLDAPServerIntegrity = 2means signing is required.1means none required (weak). Don't lower this without understanding the risk.
If the main fix doesn't work
Sometimes the app genuinely can't do signing or TLS — old Cisco Unified CM, some Xerox copiers, a 2011-era backup agent. In that case you have three realistic options:
- Stand up an LDAPS proxy. Put stunnel or haproxy in front of 636 and let the legacy app connect to localhost:389, which forwards over TLS. This is the fix I reach for most often because it keeps the DC hardened.
- Use a dedicated read-only service account on a specific DC and lower
LDAPServerIntegrityto 1 on that one DC only. Ugly, but a documented stopgap. Set a calendar reminder to retire whatever needs it. - Switch the app to a Kerberos bind. Even without TLS, Kerberos-encrypted binds satisfy the packet privacy requirement. This usually means passing a UPN and letting Windows negotiate instead of a plain DN/password simple bind.
One trap: if you fix the client but the app still fails, check for a load balancer or firewall between client and DC that's terminating TLS. Some F5 and Palo Alto configs silently strip LDAPS. Run openssl s_client -connect dc01:636 -showcerts from the client to see what's really happening on the wire.
Prevention
Audit every LDAP integration in your environment before Microsoft's next hardening wave forces the issue. On each DC, enable diagnostic logging for LDAP interface events and watch for Event ID 2889 for a week — it will list every unsigned simple bind with the client IP. That list is your migration queue. Then work through it: TLS first, Kerberos second, stunnel only for the apps that genuinely can't be fixed. And when you're writing new LDAP code, default to ldaps:// and port 636. The 0x2155 error is a feature, not a bug — it's the DC telling you something insecure is trying to talk to it.
I know this error is infuriating when it pops up on a Friday afternoon. The good news: 95% of cases are fixed by switching a URI fromldap://toldaps://and confirming the cert chain. Start there.