<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Directory Services Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/directory-services/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/directory-services/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:41:28 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Directory Services Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/directory-services/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>LDAP High Availability: Load Balancing and Production Architecture</title>
		<link>https://linuxcent.com/ldap-high-availability/</link>
					<comments>https://linuxcent.com/ldap-high-availability/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Wed, 06 May 2026 05:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Directory Services]]></category>
		<category><![CDATA[HAProxy]]></category>
		<category><![CDATA[High Availability]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[OpenLDAP]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1790</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>LDAP high availability architecture: HAProxy load balancing, read/write split, connection pooling, cn=monitor metrics, and what to watch for at 10M+ entries.</p>
<p>The post <a href="https://linuxcent.com/ldap-high-availability/">LDAP High Availability: Load Balancing and Production Architecture</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 7</em><br />
<a href="/openldap-setup-replication/">EP06: OpenLDAP</a> → <strong>EP07</strong> → <a href="/freeipa-linux-identity-management/">EP08: FreeIPA</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>LDAP HA means multiple directory servers behind a load balancer — clients connect to a VIP, not to individual servers</li>
<li>Read/write split: all writes go to the provider, reads are distributed across consumers — the load balancer enforces this by routing on port or backend check</li>
<li>SSSD handles multi-server failover natively (<code class="" data-line="">ldap_uri</code> accepts a comma-separated list) — for apps without built-in failover, HAProxy with health checks does the work</li>
<li>Connection pooling is critical at scale — <code class="" data-line="">nss_ldap</code> and <code class="" data-line="">pam_ldap</code> opened a new connection per login; SSSD maintains a pool; apps that use libldap directly must implement their own</li>
<li><code class="" data-line="">cn=monitor</code> is the built-in monitoring endpoint — exposes connection counts, operation rates, and backend stats readable via <code class="" data-line="">ldapsearch</code></li>
<li>389-DS (Red Hat Directory Server) is the production choice for &gt;1M entries — purpose-built for large directories with a dedicated replication engine</li>
</ul>
<hr />
<h2 id="the-big-picture-production-ldap-topology">The Big Picture: Production LDAP Topology</h2>
<pre><code class="" data-line="">         Clients (SSSD, apps, VPN concentrators)
                      │
              ┌───────▼───────┐
              │   HAProxy VIP  │   ← single endpoint, port 389/636
              │  10.0.0.10     │
              └───────┬───────┘
                      │
          ┌───────────┼───────────┐
          ▼           ▼           ▼
   ldap1.corp.com  ldap2.corp.com  ldap3.corp.com
   (Provider)      (Consumer)      (Consumer)
   Reads + Writes  Reads only      Reads only
          │           ▲               ▲
          └───────────┴───────────────┘
               SyncRepl replication
</code></pre>
<p>EP06 built a two-node replicated directory. This episode covers what happens when the directory becomes infrastructure — when it needs to survive a node failure, handle thousands of connections, and be monitored like any other critical service.</p>
<hr />
<h2 id="haproxy-for-ldap">HAProxy for LDAP</h2>
<p>HAProxy is the standard choice for LDAP load balancing. Unlike HTTP, LDAP is a stateful protocol — once a client binds, subsequent operations on that connection share the authenticated session. The load balancer must use connection persistence, not per-request routing.</p>
<pre><code class="" data-line=""># /etc/haproxy/haproxy.cfg

global
    log /dev/log local0
    maxconn 50000

defaults
    mode tcp                  # LDAP is TCP, not HTTP
    timeout connect 5s
    timeout client  30s
    timeout server  30s
    option tcplog

# ── LDAP read/write split ─────────────────────────────────────────────

# Writes → provider only
frontend ldap-write
    bind *:389
    default_backend ldap-provider

backend ldap-provider
    balance first                   # always use first available (provider)
    option tcp-check
    tcp-check connect
    server ldap1 ldap1.corp.com:389 check inter 5s rise 2 fall 3
    server ldap2 ldap2.corp.com:389 check inter 5s rise 2 fall 3 backup

# Reads → all nodes round-robin
frontend ldap-read
    bind *:3389                     # internal read port
    default_backend ldap-consumers

backend ldap-consumers
    balance roundrobin
    option tcp-check
    tcp-check connect
    server ldap1 ldap1.corp.com:389 check inter 5s
    server ldap2 ldap2.corp.com:389 check inter 5s
    server ldap3 ldap3.corp.com:389 check inter 5s

# LDAPS (TLS)
frontend ldaps
    bind *:636
    default_backend ldap-consumers-tls

backend ldap-consumers-tls
    balance roundrobin
    server ldap1 ldap1.corp.com:636 check inter 5s ssl verify required ca-file /etc/ssl/certs/ca.pem
    server ldap2 ldap2.corp.com:636 check inter 5s ssl verify required ca-file /etc/ssl/certs/ca.pem
</code></pre>
<p>The health check (<code class="" data-line="">tcp-check connect</code>) just verifies TCP connectivity. For a more precise check — verifying that <code class="" data-line="">slapd</code> is actually responding to LDAP requests — use a custom script that runs <code class="" data-line="">ldapsearch</code> and checks the result code.</p>
<hr />
<h2 id="sssd-multi-server-failover">SSSD Multi-Server Failover</h2>
<p>SSSD has native failover — no load balancer required for SSSD-based clients:</p>
<pre><code class="" data-line=""># /etc/sssd/sssd.conf
[domain/corp.com]
ldap_uri = ldap://ldap1.corp.com, ldap://ldap2.corp.com, ldap://ldap3.corp.com
# SSSD tries them in order; switches to next on failure
# Switches back to primary after ldap_recovery_interval (default: 30s)

# For AD, discovery via DNS SRV records is even better:
ad_server = _srv_
# SSSD queries _ldap._tcp.corp.com SRV records and gets all DCs automatically
</code></pre>
<p>SSSD monitors the connection health. If the current server becomes unreachable, it switches to the next in the list within seconds. Existing cached data keeps serving during the switchover. Clients using SSSD don&#8217;t need a load balancer for basic HA.</p>
<hr />
<h2 id="connection-pooling">Connection Pooling</h2>
<p>Every LDAP bind creates an authenticated session on the server. A server with connection limits (<code class="" data-line="">olcConnMaxPending</code>, <code class="" data-line="">olcConnMaxPendingAuth</code> in OLC) will reject new connections when those limits are hit.</p>
<p>The problem: applications that use <code class="" data-line="">libldap</code> directly tend to open a new connection per operation. At 500 requests/second, that&#8217;s 500 new TCP connections, 500 binds, 500 TLS handshakes per second — a directory that can handle 5000 concurrent connections starts refusing new ones.</p>
<p>The solutions:</p>
<p><strong>SSSD</strong> — handles this automatically. SSSD maintains one or a small number of persistent connections per domain and multiplexes all PAM/NSS queries through them.</p>
<p><strong>Application-level pooling</strong> — frameworks like <code class="" data-line="">python-ldap</code> with connection pooling, <code class="" data-line="">ldap3</code> with connection strategies, or dedicated middleware like <code class="" data-line="">389-DS</code>&#8216;s Directory Proxy Server.</p>
<p><strong><code class="" data-line="">ldap_maxconnections</code></strong> in OpenLDAP — sets a hard limit. When hit, new connections block until existing ones close. Set this to something reasonable (<code class="" data-line="">olcConnMaxPending: 100</code> in OLC) so you get a controlled failure mode instead of unbounded queuing.</p>
<hr />
<h2 id="monitoring-with-cnmonitor">Monitoring with cn=monitor</h2>
<p>OpenLDAP exposes live operational statistics via the <code class="" data-line="">cn=monitor</code> database — a virtual LDAP subtree that reflects the server&#8217;s current state. Enable it:</p>
<pre><code class="" data-line=""># enable-monitor.ldif
dn: cn=module,cn=config
objectClass: olcModuleList
cn: module
olcModulePath: /usr/lib/ldap
olcModuleLoad: back_monitor

dn: olcDatabase=monitor,cn=config
objectClass: olcDatabaseConfig
olcDatabase: monitor
olcAccess: to *
  by dn=&quot;cn=admin,dc=corp,dc=com&quot; read
  by * none
</code></pre>
<p>Query it:</p>
<pre><code class="" data-line=""># Overall statistics
ldapsearch -x -H ldap://localhost \
  -D &quot;cn=admin,dc=corp,dc=com&quot; -w password \
  -b &quot;cn=monitor&quot; -s sub &quot;(objectClass=*)&quot; \
  monitorOpInitiated monitorOpCompleted

# Connection counts
ldapsearch -x -H ldap://localhost \
  -D &quot;cn=admin,dc=corp,dc=com&quot; -w password \
  -b &quot;cn=Connections,cn=monitor&quot; -s one \
  monitorConnectionNumber

# Operations by type
ldapsearch -x -H ldap://localhost \
  -D &quot;cn=admin,dc=corp,dc=com&quot; -w password \
  -b &quot;cn=Operations,cn=monitor&quot; -s one \
  monitorOpInitiated monitorOpCompleted
</code></pre>
<p>Useful metrics to export to Prometheus (via <code class="" data-line="">prometheus-openldap-exporter</code> or similar):<br />
&#8211; <code class="" data-line="">monitorOpCompleted</code> per operation type (bind, search, modify)<br />
&#8211; <code class="" data-line="">monitorConnectionNumber</code> — current connection count<br />
&#8211; Backend-specific: <code class="" data-line="">olmMDBEntries</code>, <code class="" data-line="">olmMDBPagesMax</code>, <code class="" data-line="">olmMDBPagesUsed</code></p>
<hr />
<h2 id="389-ds-ldap-at-scale">389-DS: LDAP at Scale</h2>
<p>OpenLDAP is excellent for directories up to a few million entries. When you need:<br />
&#8211; 10M+ entries<br />
&#8211; High write throughput (more than a few hundred writes/second)<br />
&#8211; Fine-grained replication filtering<br />
&#8211; A dedicated web-based admin UI</p>
<p>…389-DS (Red Hat Directory Server, community edition) is the production answer. It&#8217;s what FreeIPA uses under the hood.</p>
<p>Key architectural differences from OpenLDAP:</p>
<p><strong>Multi-supplier replication</strong> — 389-DS&#8217;s replication engine uses a dedicated changelog (stored in LMDB) and Change Sequence Numbers (CSNs) for conflict resolution. Multi-supplier (multi-master) replication is first-class, not a bolted-on feature.</p>
<p><strong>Changelog</strong> — every change is written to a persistent changelog before being applied. This enables precise replication: a consumer can reconnect after a network partition and get exactly the changes it missed, rather than doing a full resync.</p>
<p><strong>Plugin architecture</strong> — 389-DS functionality (replication, managed entries, DNA for automatic UID allocation, memberOf, password policy) is all implemented as plugins that can be enabled/disabled per directory instance.</p>
<pre><code class="" data-line=""># Install 389-DS
dnf install -y 389-ds-base

# Create a new instance
dscreate interactive
# — or use a template:
dscreate from-file /path/to/instance.inf

# Manage with dsctl
dsctl slapd-corp status
dsctl slapd-corp start
dsctl slapd-corp stop

# Admin with dsconf
dsconf slapd-corp backend suffix list
dsconf slapd-corp replication status -suffix &quot;dc=corp,dc=com&quot;
</code></pre>
<p>The <code class="" data-line="">dsconf replication status</code> command gives a live view of replication lag across all suppliers and consumers — something OpenLDAP requires you to compute manually from contextCSN comparisons.</p>
<hr />
<h2 id="global-catalog-cross-domain-search-in-ad">Global Catalog: Cross-Domain Search in AD</h2>
<p>When your directory spans multiple AD domains in a forest, the Global Catalog solves a specific problem: a user in <code class="" data-line="">emea.corp.com</code> needs to be found by an app that only knows <code class="" data-line="">corp.com</code>.</p>
<pre><code class="" data-line="">Forest: corp.com
  ├── corp.com       → DC port 389    full directory: 500K entries
  ├── emea.corp.com  → DC port 389    full directory: 200K entries
  └── Global Catalog → GC port 3268  partial replica: 700K entries
                                       (not all attributes — just the most queried ones)
</code></pre>
<p>The GC replicates a subset of attributes from every domain in the forest. By default: <code class="" data-line="">cn</code>, <code class="" data-line="">mail</code>, <code class="" data-line="">sAMAccountName</code>, <code class="" data-line="">userPrincipalName</code>, <code class="" data-line="">memberOf</code>, and about 150 others. Attributes marked with <code class="" data-line="">isMemberOfPartialAttributeSet</code> in the schema are replicated to the GC.</p>
<p>If an application is configured to use port 3268 instead of 389, it&#8217;s using the GC — and it won&#8217;t see attributes not included in the partial attribute set. This surprises teams that add a custom attribute to AD and then wonder why their application can&#8217;t see it on 3268 but can on 389.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>HAProxy TCP health checks don&#8217;t verify LDAP is responsive.</strong> A server can accept TCP connections but have <code class="" data-line="">slapd</code> in a degraded state (database corruption, out-of-memory). Build a proper LDAP health check: a script that binds and searches a known entry and checks the result.</p>
<p><strong>replication lag under write load.</strong> SyncRepl consumers can fall behind under sustained write load. Monitor the contextCSN difference between provider and consumers. If consumers are more than a few seconds behind, investigate the provider&#8217;s write throughput and the consumer&#8217;s processing speed.</p>
<p><strong>Directory size and the MDB mapsize.</strong> LMDB requires a pre-configured maximum database size (<code class="" data-line="">olcDbMaxSize</code>). If the database grows beyond this, <code class="" data-line="">slapd</code> starts failing writes. Set it to 2–4x your expected data size and monitor <code class="" data-line="">olmMDBPagesUsed / olmMDBPagesMax</code>.</p>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>HAProxy in TCP mode provides LDAP load balancing — use <code class="" data-line="">balance first</code> for write routing (provider only), <code class="" data-line="">balance roundrobin</code> for reads</li>
<li>SSSD has native failover via <code class="" data-line="">ldap_uri</code> — for SSSD clients, a load balancer adds HA but isn&#8217;t strictly required</li>
<li><code class="" data-line="">cn=monitor</code> is the built-in OpenLDAP monitoring endpoint — export its counters to Prometheus for operational visibility</li>
<li>389-DS is the right choice for &gt;1M entries, high write throughput, or multi-supplier replication as a first-class feature</li>
<li>Global Catalog (port 3268/3269) is a partial replica of all AD domains — useful for forest-wide searches, but missing non-replicated attributes</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP07 covers the infrastructure layer. EP08 zooms out to FreeIPA — what you get when LDAP, Kerberos, DNS, PKI, and HBAC are integrated into a single Linux-native identity stack, and why most Linux shops running their own directory should be running FreeIPA instead of bare OpenLDAP.</p>
<p><em>Next: <a href="/freeipa-linux-identity-management/">FreeIPA: LDAP + Kerberos + PKI in a Single Linux Identity Stack</a></em></p>
<p>Get EP08 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&amp;linkname=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fldap-high-availability%2F&#038;title=LDAP%20High%20Availability%3A%20Load%20Balancing%20and%20Production%20Architecture" data-a2a-url="https://linuxcent.com/ldap-high-availability/" data-a2a-title="LDAP High Availability: Load Balancing and Production Architecture"></a></p><p>The post <a href="https://linuxcent.com/ldap-high-availability/">LDAP High Availability: Load Balancing and Production Architecture</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ldap-high-availability/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1790</post-id>	</item>
		<item>
		<title>OpenLDAP Setup and Replication: Running Your Own Directory</title>
		<link>https://linuxcent.com/openldap-setup-replication/</link>
					<comments>https://linuxcent.com/openldap-setup-replication/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 05 May 2026 05:00:00 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Directory Services]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[OpenLDAP]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SyncRepl]]></category>
		<guid isPermaLink="false">https://linuxcent.com/?p=1787</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 5</span> <span class="rt-label rt-postfix">minutes</span></span>Set up OpenLDAP with SyncRepl replication, OLC (cn=config), and MDB backend. Hands-on guide for a production-ready two-node replicated directory.</p>
<p>The post <a href="https://linuxcent.com/openldap-setup-replication/">OpenLDAP Setup and Replication: Running Your Own Directory</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 5</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 6</em><br />
<a href="/what-is-ldap/">EP01</a> → &#8230; → <a href="/how-kerberos-works-tickets-kdc/">EP05: Kerberos</a> → <strong>EP06</strong> → <a href="/ldap-high-availability-scale/">EP07: LDAP HA</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>OpenLDAP&#8217;s server process is <code class="" data-line="">slapd</code> — the backend that stores data is MDB (LMDB), a memory-mapped B-tree that replaced the old Berkeley DB backend</li>
<li>Configuration lives in the directory itself: <code class="" data-line="">cn=config</code> (OLC — Online Configuration) lets you modify <code class="" data-line="">slapd</code> at runtime without restarting</li>
<li>SyncRepl is the replication protocol: a consumer subscribes to a provider and stays in sync via either polling (<code class="" data-line="">refreshOnly</code>) or a persistent connection (<code class="" data-line="">refreshAndPersist</code>)</li>
<li>Multi-Provider (formerly Multi-Master) lets multiple nodes accept writes — conflict resolution uses CSN (Change Sequence Number), last-writer-wins</li>
<li>The essential tools: <code class="" data-line="">slapd</code>, <code class="" data-line="">ldapadd</code>, <code class="" data-line="">ldapmodify</code>, <code class="" data-line="">ldapsearch</code>, <code class="" data-line="">slapcat</code>, <code class="" data-line="">slaptest</code></li>
<li>Always build indexes on the attributes you search most — <code class="" data-line="">uid</code>, <code class="" data-line="">cn</code>, <code class="" data-line="">memberOf</code> — or every search is a full scan</li>
</ul>
<hr />
<h2 id="the-big-picture-slapd-architecture">The Big Picture: slapd Architecture</h2>
<pre><code class="" data-line="">ldapsearch / ldapadd / SSSD / any LDAP client
              │ TCP 389 / 636
              ▼
         ┌─────────────────────────────────┐
         │  slapd (OpenLDAP server)         │
         │                                 │
         │  Frontend (protocol layer)       │
         │    • parse BER requests          │
         │    • ACL enforcement             │
         │    • schema validation           │
         │                                 │
         │  Backend (storage layer)         │
         │    • MDB (LMDB) — default       │
         │    • memory-mapped file I/O      │
         │    • ACID transactions           │
         └────────────┬────────────────────┘
                      │
              /var/lib/ldap/
              data.mdb   (the directory data)
              lock.mdb   (LMDB lock file)
</code></pre>
<p>EP05 showed Kerberos in isolation. OpenLDAP is where you run the identity store that Kerberos references — and where SSSD looks up user and group attributes. This episode builds a working two-node replicated directory from scratch.</p>
<hr />
<h2 id="installation">Installation</h2>
<pre><code class="" data-line=""># Ubuntu / Debian
apt-get install -y slapd ldap-utils

# RHEL / Rocky / AlmaLinux
dnf install -y openldap-servers openldap-clients

# After install — Ubuntu runs a configuration wizard
# Skip it: dpkg-reconfigure slapd
# Or answer it and then switch to OLC management
</code></pre>
<p>On RHEL-family systems, <code class="" data-line="">slapd</code> is not configured after install — you work entirely through OLC from the start.</p>
<hr />
<h2 id="olc-the-directory-configures-itself">OLC: The Directory Configures Itself</h2>
<p>The old way was <code class="" data-line="">slapd.conf</code> — a static file that required a full restart on every change. OLC (Online Configuration) replaced it: <code class="" data-line="">slapd</code>&#8216;s own configuration is stored as LDAP entries under <code class="" data-line="">cn=config</code>. You modify configuration the same way you modify data — with <code class="" data-line="">ldapmodify</code>. Changes take effect immediately.</p>
<pre><code class="" data-line="">cn=config                        ← root config entry
├── cn=schema,cn=config          ← schema definitions
│     ├── cn={0}core             ← core schema
│     ├── cn={1}cosine           ← RFC 1274 attributes
│     └── cn={2}inetorgperson    ← inetOrgPerson object class
├── olcDatabase={-1}frontend     ← default settings for all databases
├── olcDatabase={0}config        ← the config database itself
└── olcDatabase={1}mdb           ← your actual directory data
      ├── olcAccess              ← ACLs
      ├── olcSuffix              ← base DN (e.g., dc=corp,dc=com)
      └── olcDbIndex             ← search indexes
</code></pre>
<p>Everything under <code class="" data-line="">cn=config</code> has attributes prefixed with <code class="" data-line="">olc</code> (OpenLDAP Configuration). You query and modify it just like any other LDAP subtree — with one restriction: only the <code class="" data-line="">cn=config</code> admin (usually <code class="" data-line="">gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth</code> — the local root via SASL EXTERNAL) can write to it.</p>
<hr />
<h2 id="bootstrapping-a-directory">Bootstrapping a Directory</h2>
<p>The quickest way to get a working directory is a set of LDIF files applied in order.</p>
<h3 id="1-load-schemas">1. Load schemas</h3>
<pre><code class="" data-line=""># Apply the schemas OpenLDAP ships with
ldapadd -Y EXTERNAL -H ldapi:/// \
  -f /etc/ldap/schema/cosine.ldif
ldapadd -Y EXTERNAL -H ldapi:/// \
  -f /etc/ldap/schema/inetorgperson.ldif
ldapadd -Y EXTERNAL -H ldapi:/// \
  -f /etc/ldap/schema/nis.ldif       # adds posixAccount, posixGroup
</code></pre>
<h3 id="2-configure-the-mdb-database">2. Configure the MDB database</h3>
<pre><code class="" data-line=""># mdb-config.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=corp,dc=com
-
replace: olcRootDN
olcRootDN: cn=admin,dc=corp,dc=com
-
replace: olcRootPW
olcRootPW: {SSHA}hashed_password_here
</code></pre>
<p>Generate the hash: <code class="" data-line="">slappasswd -s yourpassword</code></p>
<pre><code class="" data-line="">ldapmodify -Y EXTERNAL -H ldapi:/// -f mdb-config.ldif
</code></pre>
<h3 id="3-add-indexes">3. Add indexes</h3>
<pre><code class="" data-line=""># indexes.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcDbIndex
olcDbIndex: uid eq,pres
olcDbIndex: cn eq,sub
olcDbIndex: sn eq,sub
olcDbIndex: mail eq
olcDbIndex: memberOf eq
olcDbIndex: entryCSN eq
olcDbIndex: entryUUID eq
</code></pre>
<p>The last two (<code class="" data-line="">entryCSN</code>, <code class="" data-line="">entryUUID</code>) are required for SyncRepl replication to work efficiently.</p>
<h3 id="4-load-initial-data">4. Load initial data</h3>
<pre><code class="" data-line=""># base.ldif
dn: dc=corp,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: Corp
dc: corp

dn: ou=people,dc=corp,dc=com
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=corp,dc=com
objectClass: organizationalUnit
ou: groups

dn: uid=vamshi,ou=people,dc=corp,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Vamshi Krishna
sn: Krishna
uid: vamshi
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/vamshi
loginShell: /bin/bash
mail: vamshi@corp.com
userPassword: {SSHA}hashed_password_here
</code></pre>
<pre><code class="" data-line="">ldapadd -x -H ldap://localhost \
  -D &quot;cn=admin,dc=corp,dc=com&quot; \
  -w adminpassword \
  -f base.ldif
</code></pre>
<hr />
<h2 id="acls-who-can-read-what">ACLs: Who Can Read What</h2>
<p>OpenLDAP ACLs are evaluated top-to-bottom; first match wins.</p>
<pre><code class="" data-line=""># acls.ldif — set via OLC
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
# Users can change their own passwords
olcAccess: to attrs=userPassword
  by self write
  by anonymous auth
  by * none
# Users can read their own entry
olcAccess: to dn.base=&quot;ou=people,dc=corp,dc=com&quot;
  by self read
  by users read
  by * none
# Service accounts can read everything (for SSSD)
olcAccess: to *
  by dn=&quot;cn=svc-ldap,ou=services,dc=corp,dc=com&quot; read
  by self read
  by * none
</code></pre>
<p>A service account (<code class="" data-line="">cn=svc-ldap</code>) that SSSD uses to search the directory needs read access to <code class="" data-line="">ou=people</code> and <code class="" data-line="">ou=groups</code>. Never give SSSD admin (write) access.</p>
<hr />
<h2 id="syncrepl-replication">SyncRepl Replication</h2>
<p>SyncRepl is a pull-based replication protocol built on the LDAP Sync operation (RFC 4533). A consumer connects to a provider and requests changes. The provider sends them. The consumer stays in sync.</p>
<h3 id="on-the-provider-enable-the-syncprov-overlay">On the Provider: Enable the syncprov overlay</h3>
<pre><code class="" data-line=""># syncprov.ldif
dn: olcOverlay=syncprov,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10     # checkpoint every 100 ops or 10 minutes
olcSpSessionLog: 100        # keep last 100 changes for delta-sync
</code></pre>
<pre><code class="" data-line="">ldapadd -Y EXTERNAL -H ldapi:/// -f syncprov.ldif
</code></pre>
<h3 id="on-the-consumer-configure-syncrepl">On the Consumer: Configure syncrepl</h3>
<pre><code class="" data-line=""># consumer-config.ldif
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcSyncrepl
olcSyncrepl: rid=001
  provider=ldap://ldap1.corp.com:389
  bindmethod=simple
  binddn=&quot;cn=repl-svc,dc=corp,dc=com&quot;
  credentials=replication-password
  searchbase=&quot;dc=corp,dc=com&quot;
  scope=sub
  schemachecking=on
  type=refreshAndPersist    # persistent connection (vs refreshOnly = polling)
  retry=&quot;5 5 60 +&quot;          # retry: 5 times every 5s, then every 60s forever
  interval=00:00:05:00      # (for refreshOnly) sync every 5 minutes
-
add: olcUpdateRef
olcUpdateRef: ldap://ldap1.corp.com   # redirect writes to provider
</code></pre>
<p><code class="" data-line="">refreshAndPersist</code> keeps a persistent connection open. Changes replicate within milliseconds. <code class="" data-line="">refreshOnly</code> polls on an interval — simpler, but adds latency.</p>
<h3 id="verify-replication">Verify Replication</h3>
<pre><code class="" data-line=""># On provider: check the contextCSN (the sync state token)
ldapsearch -x -H ldap://ldap1.corp.com \
  -D &quot;cn=admin,dc=corp,dc=com&quot; -w password \
  -b &quot;dc=corp,dc=com&quot; -s base contextCSN
# contextCSN: 20260427010000.000000Z#000000#000#000000

# On consumer: should match after sync
ldapsearch -x -H ldap://ldap2.corp.com \
  -D &quot;cn=admin,dc=corp,dc=com&quot; -w password \
  -b &quot;dc=corp,dc=com&quot; -s base contextCSN
# Same CSN = in sync
</code></pre>
<hr />
<h2 id="multi-provider-accepting-writes-on-both-nodes">Multi-Provider: Accepting Writes on Both Nodes</h2>
<p>Standard SyncRepl has one provider and one or more consumers — only the provider accepts writes. Multi-Provider (formerly Multi-Master) lets every node accept writes.</p>
<pre><code class="" data-line=""># On each node — add mirrormode to the database config
dn: olcDatabase={1}mdb,cn=config
changetype: modify
add: olcMirrorMode
olcMirrorMode: TRUE
</code></pre>
<p>With mirrormode enabled and each node configured as both provider and consumer of the other, writes on either node replicate to the other. Conflict resolution is CSN-based (Change Sequence Number) — a monotonically increasing timestamp. Last write wins at the attribute level.</p>
<p>Multi-Provider does not prevent split-brain conflicts — if two clients write the same attribute on two different nodes during a network partition, the higher CSN wins when the partition heals. For most directory use cases (user passwords, group memberships), this is acceptable. For others, it requires careful thought.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<p><strong>MDB data file grows monotonically.</strong> LMDB never shrinks the data file automatically. Deleted entries leave free space inside the file that gets reused, but the file on disk doesn&#8217;t shrink. Use <code class="" data-line="">slapcat</code> to export and <code class="" data-line="">slapadd</code> to reimport if you need to reclaim disk space.</p>
<p><strong><code class="" data-line="">slapcat</code> is the only safe backup.</strong> <code class="" data-line="">slapcat</code> reads the MDB database directly and exports LDIF — it does not go through <code class="" data-line="">slapd</code>. Run it while <code class="" data-line="">slapd</code> is running (LMDB is MVCC-safe for readers), but never copy the raw MDB files while <code class="" data-line="">slapd</code> is running.</p>
<p><strong>Schema changes on a replicated directory require coordination.</strong> Load the new schema on the provider first. SyncRepl will propagate it to consumers — but if a consumer gets a new entry using the new schema before the schema itself is replicated, the import will fail. Load schemas manually on all nodes before adding entries that use them.</p>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>OpenLDAP uses LMDB (MDB backend) — a memory-mapped, ACID-compliant storage engine with no external dependency</li>
<li>OLC (<code class="" data-line="">cn=config</code>) is the right way to configure <code class="" data-line="">slapd</code> — changes apply without restarts</li>
<li>SyncRepl pulls changes from a provider to a consumer — <code class="" data-line="">refreshAndPersist</code> for near-real-time, <code class="" data-line="">refreshOnly</code> for poll-based</li>
<li>Always index <code class="" data-line="">uid</code>, <code class="" data-line="">cn</code>, <code class="" data-line="">entryCSN</code>, and <code class="" data-line="">entryUUID</code> — unindexed searches are full scans</li>
<li>Multi-Provider allows writes on all nodes with CSN-based last-write-wins conflict resolution</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>A single OpenLDAP server works. Two nodes with SyncRepl work better. EP07 goes further: how you put multiple LDAP servers behind a load balancer, how connection pooling works, what to monitor, and how 389-DS handles directories with tens of millions of entries.</p>
<p><em>Next: <a href="/ldap-high-availability-scale/">LDAP High Availability: Load Balancing and Production Architecture</a></em></p>
<p>Get EP07 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&amp;linkname=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fopenldap-setup-replication%2F&#038;title=OpenLDAP%20Setup%20and%20Replication%3A%20Running%20Your%20Own%20Directory" data-a2a-url="https://linuxcent.com/openldap-setup-replication/" data-a2a-title="OpenLDAP Setup and Replication: Running Your Own Directory"></a></p><p>The post <a href="https://linuxcent.com/openldap-setup-replication/">OpenLDAP Setup and Replication: Running Your Own Directory</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/openldap-setup-replication/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1787</post-id>	</item>
		<item>
		<title>LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</title>
		<link>https://linuxcent.com/ldap-internals-directory-structure/</link>
					<comments>https://linuxcent.com/ldap-internals-directory-structure/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 24 Apr 2026 19:41:04 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Directory Services]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[LDAP Schema]]></category>
		<category><![CDATA[ldapsearch]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://linuxcent.com/ldap-internals-directory-structure/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 12</span> <span class="rt-label rt-postfix">minutes</span></span>Understand LDAP internals: the DIT hierarchy, DN syntax, object classes, schema, and the BER-encoded bytes that travel from directory server to authentication daemon.</p>
<p>The post <a href="https://linuxcent.com/ldap-internals-directory-structure/">LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 12</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 2</em><br />
<a href="/what-is-ldap/">EP01: What Is LDAP</a> → <strong>EP02</strong> → <a href="/ldap-authentication-linux-pam-nss/">EP03: LDAP Authentication on Linux</a> → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>The Directory Information Tree (DIT) is the hierarchical database LDAP stores — every entry lives at a unique path described by its Distinguished Name (DN)</li>
<li>Object classes define what attributes an entry is allowed or required to have — <code class="" data-line="">posixAccount</code> adds UID, GID, and home directory; <code class="" data-line="">inetOrgPerson</code> adds email and display name</li>
<li>Schema is the rulebook: which attribute types exist across the entire directory, what syntax each follows, and which object classes require or permit them</li>
<li>An LDAP Search sends four things: a base DN, a scope (base/one/sub), a filter like <code class="" data-line="">(uid=vamshi)</code>, and a list of attributes to return — the server traverses the tree and returns LDIF</li>
<li>Every LDAP message on the wire is BER-encoded (Basic Encoding Rules, a subset of ASN.1) — a compact binary format, not text</li>
<li><code class="" data-line="">ldapsearch</code> output is LDIF (LDAP Data Interchange Format) — the human-readable representation of what the BER payload carried</li>
</ul>
<hr />
<h2 id="the-big-picture-from-ldapsearch-to-directory-entry">The Big Picture: From ldapsearch to Directory Entry</h2>
<pre><code class="" data-line="">ldapsearch -x -H ldap://dc.corp.com -b &quot;dc=corp,dc=com&quot; &quot;(uid=vamshi)&quot; cn mail uidNumber
     │
     │  TCP port 389 (or 636 for LDAPS)
     │  BER-encoded SearchRequest
     ▼
┌─────────────────────────────────────────────────┐
│  LDAP Server (AD / OpenLDAP / 389-DS / FreeIPA)  │
│                                                   │
│  Directory Information Tree                       │
│                                                   │
│  dc=corp,dc=com                    ← search base  │
│    └── ou=engineers                ← scope: sub   │
│          ├── uid=alice                            │
│          └── uid=vamshi  ← filter match           │
│                cn: vamshi                         │
│                mail: vamshi@corp.com              │
│                uidNumber: 1001                    │
└─────────────────────────────────────────────────┘
     │
     │  BER-encoded SearchResultEntry
     ▼
# LDIF output on your terminal
dn: uid=vamshi,ou=engineers,dc=corp,dc=com
cn: vamshi
mail: vamshi@corp.com
uidNumber: 1001
</code></pre>
<p>LDAP internals are the mechanics between the command you type and the directory entry you get back. EP01 explained why LDAP was invented. This episode explains what it actually does when you run it.</p>
<hr />
<h2 id="the-directory-information-tree">The Directory Information Tree</h2>
<p>EP01 introduced the DIT as a concept inherited from X.500. Here&#8217;s what it actually looks like inside a directory.</p>
<p>Every LDAP directory has a root — the base DN — from which all entries descend. For a company called Corp with a domain <code class="" data-line="">corp.com</code>, the base is typically <code class="" data-line="">dc=corp,dc=com</code>. Below that, the tree branches into organizational units, and below those, individual entries for people, groups, services, and anything else the directory administrator decided to model.</p>
<pre><code class="" data-line="">dc=corp,dc=com                          ← domain root (base DN)
│
├── ou=people                           ← organizational unit: people
│     ├── uid=alice                     ← user entry
│     ├── uid=vamshi
│     └── uid=bob
│
├── ou=groups                           ← organizational unit: groups
│     ├── cn=engineers
│     └── cn=ops
│
├── ou=services                         ← organizational unit: service accounts
│     ├── cn=jenkins
│     └── cn=gitlab-runner
│
└── ou=hosts                            ← organizational unit: machines
      ├── cn=web01.corp.com
      └── cn=db01.corp.com
</code></pre>
<p>This hierarchy is not a file system and not a relational database. It is specifically optimized for reads — the query &#8220;give me everything about this user&#8221; is the operation the protocol is built around. Writes are infrequent. Reads are constant.</p>
<p>Every entry in the tree has exactly one parent. There are no cross-links between branches, no foreign keys. The tree is the structure. An entry&#8217;s position in the tree is what defines it.</p>
<hr />
<h2 id="distinguished-names-reading-the-path">Distinguished Names: Reading the Path</h2>
<p>The Distinguished Name (DN) is how you address any entry in the directory. It reads right-to-left, from the leaf to the root, with each component separated by a comma.</p>
<pre><code class="" data-line="">uid=vamshi,ou=engineers,dc=corp,dc=com

Reading right-to-left:
  dc=corp,dc=com       ← domain: corp.com
  ou=engineers         ← organizational unit: engineers
  uid=vamshi           ← this specific entry: user &quot;vamshi&quot;
</code></pre>
<p>Each component of a DN — <code class="" data-line="">uid=vamshi</code>, <code class="" data-line="">ou=engineers</code>, <code class="" data-line="">dc=corp</code> — is a Relative Distinguished Name (RDN). The RDN is the attribute-value pair that uniquely identifies the entry within its parent container. Two users in the same <code class="" data-line="">ou=engineers</code> cannot both have <code class="" data-line="">uid=vamshi</code> — that would create two entries with identical DNs, which the directory won&#8217;t allow.</p>
<p>Common RDN attribute types and what they mean:</p>
<table>
<thead>
<tr>
<th>Attribute</th>
<th>Stands for</th>
<th>Typical use</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">dc</code></td>
<td>Domain Component</td>
<td>Domain name segments (<code class="" data-line="">dc=corp,dc=com</code> = corp.com)</td>
</tr>
<tr>
<td><code class="" data-line="">ou</code></td>
<td>Organizational Unit</td>
<td>Container for grouping entries</td>
</tr>
<tr>
<td><code class="" data-line="">cn</code></td>
<td>Common Name</td>
<td>Groups, service accounts, human-readable name</td>
</tr>
<tr>
<td><code class="" data-line="">uid</code></td>
<td>User ID</td>
<td>Linux username — the standard RDN for user entries</td>
</tr>
<tr>
<td><code class="" data-line="">o</code></td>
<td>Organization</td>
<td>Top-level org containers (less common in modern setups)</td>
</tr>
</tbody>
</table>
<p>When your Linux system calls <code class="" data-line="">getent passwd vamshi</code>, SSSD translates that into an LDAP Search for an entry where <code class="" data-line="">uid=vamshi</code> somewhere under the configured base DN. The full DN comes back with the result, but what your system cares about are the attributes inside it.</p>
<hr />
<h2 id="object-classes-and-schema">Object Classes and Schema</h2>
<p>Every entry in the directory has a <code class="" data-line="">objectClass</code> attribute — usually several values. Object classes define what attributes the entry is allowed or required to have.</p>
<pre><code class="" data-line=""># A typical user entry&#039;s object classes
dn: uid=vamshi,ou=engineers,dc=corp,dc=com
objectClass: top
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
</code></pre>
<p>Each object class contributes a set of attributes — some required (<code class="" data-line="">MUST</code>), some optional (<code class="" data-line="">MAY</code>):</p>
<pre><code class="" data-line="">objectClass: posixAccount
  MUST: cn, uid, uidNumber, gidNumber, homeDirectory
  MAY:  userPassword, loginShell, gecos, description

objectClass: inetOrgPerson
  MUST: sn (surname), cn
  MAY:  mail, telephoneNumber, displayName, jpegPhoto, ...

objectClass: shadowAccount
  MUST: uid
  MAY:  shadowLastChange, shadowMin, shadowMax, shadowWarning, ...
</code></pre>
<p>When Linux authenticates a user via LDAP, it needs the <code class="" data-line="">posixAccount</code> attributes: <code class="" data-line="">uidNumber</code> (the numeric UID), <code class="" data-line="">gidNumber</code>, <code class="" data-line="">homeDirectory</code>, and <code class="" data-line="">loginShell</code>. Without <code class="" data-line="">posixAccount</code>, the user entry exists in the directory but can&#8217;t be used for Linux logins — <code class="" data-line="">getent passwd</code> will return nothing.</p>
<p>Object classes are grouped into three kinds:</p>
<p>Groups in LDAP use their own object class:</p>
<pre><code class="" data-line="">objectClass: groupOfNames
  MUST: cn, member
  MAY:  description, owner, ...

# A group entry looks like this:
dn: cn=engineers,ou=groups,dc=corp,dc=com
objectClass: groupOfNames
cn: engineers
member: uid=vamshi,ou=engineers,dc=corp,dc=com
member: uid=alice,ou=engineers,dc=corp,dc=com
</code></pre>
<p><code class="" data-line="">groupOfNames</code> stores members as full DNs — which is why the SSSD group search filter is <code class="" data-line="">(member=uid=vamshi,ou=...)</code> rather than <code class="" data-line="">(member=vamshi)</code>. The directory stores the exact path to each member entry. <code class="" data-line="">posixGroup</code> is the alternative, which stores the <code class="" data-line="">memberUid</code> as a bare username string instead of a DN — Active Directory uses <code class="" data-line="">groupOfNames</code>; pure POSIX environments often use <code class="" data-line="">posixGroup</code>.</p>
<p>Object classes are grouped into three kinds:</p>
<p><strong>Structural</strong> — defines what the entry fundamentally is. Every entry must have exactly one structural class. <code class="" data-line="">posixAccount</code> is structural.</p>
<p><strong>Auxiliary</strong> — adds additional attributes to an existing entry. <code class="" data-line="">shadowAccount</code> and <code class="" data-line="">inetOrgPerson</code> can be auxiliary. You can stack multiple auxiliary classes on a single entry.</p>
<p><strong>Abstract</strong> — base classes that other classes inherit from. <code class="" data-line="">top</code> is the root abstract class that every entry implicitly has. You never add <code class="" data-line="">top</code> to an entry; it&#8217;s always there.</p>
<h3 id="schema-the-directorys-type-system">Schema: The Directory&#8217;s Type System</h3>
<p>Schema is the global rulebook for the entire directory. It defines:</p>
<ul>
<li><strong>Attribute type definitions</strong> — what each attribute is named, what syntax it uses (a string? an integer? a binary blob?), whether it&#8217;s case-sensitive, whether multiple values are allowed</li>
<li><strong>Object class definitions</strong> — which attributes each class requires or permits</li>
<li><strong>Matching rules</strong> — how equality comparisons work for each attribute type</li>
</ul>
<p>The schema is stored in the directory itself, under a special entry at <code class="" data-line="">cn=schema,cn=config</code> (OpenLDAP) or <code class="" data-line="">cn=Schema,cn=Configuration</code> (Active Directory). You can query it:</p>
<pre><code class="" data-line=""># View the schema for the posixAccount object class
ldapsearch -x -H ldap://your-dc \
  -b &quot;cn=schema,cn=config&quot; \
  &quot;(objectClass=olcObjectClasses)&quot; \
  olcObjectClasses | grep -A 10 &quot;posixAccount&quot;

# Output:
# olcObjectClasses: ( 1.3.6.1.1.1.2.0
#   NAME &#039;posixAccount&#039;
#   DESC &#039;Abstraction of an account with POSIX attributes&#039;
#   SUP top
#   AUXILIARY
#   MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
#   MAY ( userPassword $ loginShell $ gecos $ description ) )
</code></pre>
<p>That OID (<code class="" data-line="">1.3.6.1.1.1.2.0</code>) is the globally unique identifier for the <code class="" data-line="">posixAccount</code> object class. Every object class and attribute type in every LDAP directory on the planet has a unique OID assigned by an authority. This is how schema interoperability works across different directory implementations — OpenLDAP, Active Directory, and 389-DS can all understand each other&#8217;s <code class="" data-line="">posixAccount</code> entries because they share the same OID.</p>
<hr />
<h2 id="ldap-operations-what-actually-runs">LDAP Operations: What Actually Runs</h2>
<p>LDAP defines eight operations. Day-to-day authentication uses two: Bind and Search.</p>
<pre><code class="" data-line="">LDAP Operation Set
──────────────────
Bind        ← authenticate (prove identity)
Search      ← query the directory
Add         ← create a new entry
Modify      ← change attributes on an existing entry
Delete      ← remove an entry
ModifyDN    ← rename or move an entry
Compare     ← test if an attribute has a specific value
Abandon     ← cancel an outstanding operation
</code></pre>
<h3 id="bind-proving-who-you-are">Bind: Proving Who You Are</h3>
<p>Before any authenticated operation, the client sends a Bind request. There are two types:</p>
<p><strong>Simple Bind</strong> — the client sends its DN and password in the clear (or over TLS). This is what <code class="" data-line="">-x</code> in <code class="" data-line="">ldapsearch</code> means: simple authentication.</p>
<pre><code class="" data-line=""># Simple bind as a service account
ldapsearch -x \
  -D &quot;cn=svc-ldap-reader,ou=services,dc=corp,dc=com&quot; \
  -w &quot;service-account-password&quot; \
  -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(uid=vamshi)&quot;
</code></pre>
<p><strong>SASL Bind</strong> — the client uses an authentication mechanism registered with SASL (Simple Authentication and Security Layer). Kerberos (via the GSSAPI mechanism) is the most common. EP05 covers Kerberos in detail.</p>
<pre><code class="" data-line=""># SASL bind using Kerberos (after kinit)
ldapsearch -Y GSSAPI \
  -H ldap://dc.corp.com \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(uid=vamshi)&quot;
</code></pre>
<p>An anonymous Bind (no DN, no password) is also valid for directories configured to allow anonymous reads. Many public LDAP directories (and some internal ones, misconfigured) allow this.</p>
<h3 id="search-the-core-operation">Search: The Core Operation</h3>
<p>A Search request has five required parameters:</p>
<pre><code class="" data-line="">baseObject   — where in the DIT to start (e.g., &quot;dc=corp,dc=com&quot;)
scope        — how deep to look
               base    = only the base entry itself
               one     = one level below base (immediate children)
               sub     = entire subtree below base (most common)
derefAliases — how to handle alias entries (usually derefAlways)
filter       — what to match (e.g., &quot;(uid=vamshi)&quot;)
attributes   — which attributes to return (empty = return all)
</code></pre>
<p>When SSSD authenticates a user login, it runs exactly two Search operations:</p>
<pre><code class="" data-line="">Search 1 — find the user&#039;s entry
  base:       dc=corp,dc=com
  scope:      sub
  filter:     (uid=vamshi)
  attributes: dn, uid, uidNumber, gidNumber, homeDirectory, loginShell

Search 2 — find the user&#039;s group memberships
  base:       dc=corp,dc=com
  scope:      sub
  filter:     (member=uid=vamshi,ou=engineers,dc=corp,dc=com)
  attributes: dn, cn, gidNumber
</code></pre>
<p>The first search locates the user entry and retrieves the POSIX attributes. The second finds all group entries that contain the user&#8217;s DN as a member. These two queries are the complete basis for a Linux login over LDAP.</p>
<h3 id="search-filters">Search Filters</h3>
<p>LDAP filters follow a prefix (Polish notation) syntax. Every filter is wrapped in parentheses:</p>
<pre><code class="" data-line=""># Simple equality
(uid=vamshi)

# Presence — entry has this attribute at all
(mail=*)

# Substring match
(cn=vam*)

# Comparison
(uidNumber&gt;=1000)

# Logical AND — both conditions must match
(&amp;(objectClass=posixAccount)(uid=vamshi))

# Logical OR — either condition matches
(|(uid=vamshi)(mail=vamshi@corp.com))

# Logical NOT
(!(uid=guest))

# Combined — posixAccount entries with UID &gt;= 1000 and no disabled flag
(&amp;(objectClass=posixAccount)(uidNumber&gt;=1000)(!(pwdAccountLockedTime=*)))
</code></pre>
<p>The <code class="" data-line="">&amp;</code> and <code class="" data-line="">|</code> operators take any number of operands. Filter syntax looks strange the first time but is unambiguous and compact — which matters when you&#8217;re encoding it into BER for the wire.</p>
<hr />
<h2 id="what-actually-travels-on-the-wire">What Actually Travels on the Wire</h2>
<p>Every LDAP message is encoded in BER (Basic Encoding Rules), a binary subset of ASN.1. LDAP is not a text protocol.</p>
<p>When you run <code class="" data-line="">ldapsearch</code>, the tool constructs a BER-encoded <code class="" data-line="">SearchRequest</code> message and sends it over TCP. The server responds with one or more <code class="" data-line="">SearchResultEntry</code> messages (one per matching entry), followed by a <code class="" data-line="">SearchResultDone</code>. All of these are BER.</p>
<p>BER uses a type-length-value (TLV) encoding:</p>
<pre><code class="" data-line="">Tag byte(s)    — what type of data this is
Length byte(s) — how many bytes of data follow
Value byte(s)  — the actual data
</code></pre>
<p>A minimal LDAP SearchRequest for <code class="" data-line="">ldapsearch -x -b &quot;dc=corp,dc=com&quot; &quot;(uid=vamshi)&quot; uid</code> looks like this on the wire:</p>
<pre><code class="" data-line="">30 45          ← SEQUENCE (LDAPMessage)
  02 01 01     ← INTEGER 1 (messageID = 1)
  63 40        ← [APPLICATION 3] SearchRequest
    04 11       ← OCTET STRING: baseObject
      64 63 3d  ← &quot;dc=corp,dc=com&quot; (20 bytes)
      63 6f 72
      70 2c 64
      63 3d 63
      6f 6d
    0a 01 02   ← ENUMERATED: scope = wholeSubtree (2)
    0a 01 03   ← ENUMERATED: derefAliases = derefAlways (3)
    02 01 00   ← INTEGER: sizeLimit = 0 (unlimited)
    02 01 00   ← INTEGER: timeLimit = 0 (unlimited)
    01 01 00   ← BOOLEAN: typesOnly = false
    a7 0f      ← [7] equalityMatch filter
      04 03 75 69 64   ← attributeDesc: &quot;uid&quot;
      04 06 76 61 6d   ← assertionValue: &quot;vamshi&quot;
             73 68 69
    30 05      ← SEQUENCE: AttributeDescriptionList
      04 03 75 69 64   ← &quot;uid&quot;
</code></pre>
<p>You don&#8217;t need to read BER by hand in practice. But knowing it&#8217;s binary — not HTTP, not JSON, not plain text — explains some things:</p>
<ul>
<li>Why <code class="" data-line="">tcpdump port 389</code> shows binary output you can&#8217;t read directly</li>
<li>Why LDAP on port 389 looks different in Wireshark than HTTP traffic</li>
<li>Why <code class="" data-line="">ldapsearch</code> output (LDIF) is a transformation of the wire data, not the wire data itself</li>
</ul>
<p>To see the wire protocol in action:</p>
<pre><code class="" data-line=""># Run ldapsearch with debug output (level 1 = protocol tracing)
ldapsearch -d 1 -x \
  -H ldap://ldap.forumsys.com \
  -b &quot;dc=example,dc=com&quot; \
  -D &quot;cn=read-only-admin,dc=example,dc=com&quot; \
  -w readonly \
  &quot;(uid=tesla)&quot; cn

# You&#039;ll see output like:
# ldap_connect_to_host: TCP ldap.forumsys.com:389
# ldap_new_connection 1 1 0
# ldap_connect_to_host: Trying ldap.forumsys.com:389
# ldap_pvt_connect: fd: 5 tm: -1 async: 0
# TLS: can&#039;t connect.
# ldap_open_defconn: successful
# ber_scanf fmt ({it) ber:     ← BER decoding of the response
# ber_scanf fmt ({) ber:
# ber_scanf fmt (W) ber:
# ...
</code></pre>
<p>The <code class="" data-line="">ber_scanf</code> lines are the BER decoder working through the server&#8217;s response. Each line represents one TLV element being read off the wire.</p>
<hr />
<h2 id="reading-ldapsearch-output-every-field">Reading ldapsearch Output: Every Field</h2>
<p><code class="" data-line="">ldapsearch</code> output is LDIF (LDAP Data Interchange Format), defined in RFC 2849. It&#8217;s the standard text serialization of LDAP entries.</p>
<pre><code class="" data-line="">ldapsearch -x \
  -H ldap://ldap.forumsys.com \
  -b &quot;dc=example,dc=com&quot; \
  -D &quot;cn=read-only-admin,dc=example,dc=com&quot; \
  -w readonly \
  &quot;(uid=tesla)&quot; \
  cn mail uid uidNumber objectClass
</code></pre>
<p>Output, annotated:</p>
<pre><code class="" data-line=""># extended LDIF
#
# LDAPv3                              ← protocol version confirmed
# base &lt;dc=example,dc=com&gt; with scope subtree
# filter: (uid=tesla)                 ← your search filter echoed back
# requesting: cn mail uid uidNumber objectClass
#

# tesla, example.com                  ← comment: CN, base DN
dn: uid=tesla,dc=example,dc=com      ← Distinguished Name — full path in the tree

objectClass: inetOrgPerson           ← structural class: person with org attrs
objectClass: organizationalPerson    ← auxiliary: adds telephoneNumber etc.
objectClass: person                  ← auxiliary: adds sn (surname)
objectClass: top                     ← every entry has this implicitly
cn: Tesla                            ← common name (from inetOrgPerson MUST)
mail: tesla@ldap.forumsys.com        ← email (from inetOrgPerson MAY)
uid: tesla                           ← userid (from inetOrgPerson MAY)

# search result
search: 2                            ← messageID of the SearchResultDone
result: 0 Success                    ← 0 = no error; 32 = no such object; 49 = invalid credentials

# numResponses: 2                    ← 1 result entry + 1 SearchResultDone
# numEntries: 1
</code></pre>
<p>The <code class="" data-line="">result:</code> line is the one to watch when debugging. LDAP result codes:</p>
<table>
<thead>
<tr>
<th>Code</th>
<th>Meaning</th>
<th>What it tells you</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>Success</td>
<td>Query ran, results returned (or no results found — check numEntries)</td>
</tr>
<tr>
<td>32</td>
<td>No Such Object</td>
<td>Base DN doesn&#8217;t exist in this directory</td>
</tr>
<tr>
<td>49</td>
<td>Invalid Credentials</td>
<td>Bind failed — wrong DN, wrong password, or account locked</td>
</tr>
<tr>
<td>50</td>
<td>Insufficient Access</td>
<td>Your bind DN doesn&#8217;t have read permission on these entries</td>
</tr>
<tr>
<td>53</td>
<td>Unwilling to Perform</td>
<td>Server refused the operation (e.g., password policy, anonymous bind disabled)</td>
</tr>
<tr>
<td>65</td>
<td>Object Class Violation</td>
<td>Add/Modify would violate schema (missing MUST attribute, unrecognized object class)</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="ports-389-636-and-3268">Ports: 389, 636, and 3268</h2>
<pre><code class="" data-line="">Port 389   — LDAP (plaintext, or StartTLS in-session upgrade)
Port 636   — LDAPS (LDAP wrapped in TLS from the start)
Port 3268  — Active Directory Global Catalog (plain)
Port 3269  — Active Directory Global Catalog over TLS
</code></pre>
<p><strong>Port 389 vs 636:</strong> Both carry the same BER-encoded LDAP protocol. The difference is when TLS starts. On 636 (LDAPS), the TLS handshake happens before the first LDAP message. On 389 with StartTLS, the client sends a plaintext <code class="" data-line="">ExtendedRequest</code> with OID <code class="" data-line="">1.3.6.1.4.1.1466.20037</code> to initiate the TLS upgrade, then both sides continue over TLS. In production, use one or the other — never unencrypted port 389. Your credentials transit the wire on every Bind.</p>
<p><strong>Ports 3268/3269 — Active Directory Global Catalog:</strong> AD organizes domains into forests. Each domain controller holds the full LDAP tree for its own domain. The Global Catalog is a read-only, partial replica of every domain in the forest — just the most-queried attributes from every object. When an application needs to find a user across domains in the same forest (not just in one domain), it queries the Global Catalog on 3268/3269 instead of a domain-specific DC on 389/636.</p>
<pre><code class="" data-line="">Forest: corp.com
  ├── Domain: corp.com       → DC at port 389/636   (full copy of corp.com)
  ├── Domain: emea.corp.com  → DC at port 389/636   (full copy of emea.corp.com)
  └── Global Catalog        → GC at port 3268/3269  (partial copy of ALL domains)
</code></pre>
<p>If your SSSD or application is configured to use port 3268 instead of 389, it&#8217;s talking to the Global Catalog — useful for forest-wide user lookups, but missing some less-common attributes that aren&#8217;t replicated to the GC.</p>
<hr />
<h2 id="try-it-ldapsearch-against-your-own-directory">Try It: ldapsearch Against Your Own Directory</h2>
<p>If your Linux machine is joined to AD or connected to an LDAP directory, you can run these right now:</p>
<pre><code class="" data-line=""># 1. Confirm your SSSD knows where the LDAP server is
grep -E &quot;ldap_uri|ad_domain|krb5_server&quot; /etc/sssd/sssd.conf

# 2. Look up your own user entry
ldapsearch -x \
  -H ldap://$(grep ldap_uri /etc/sssd/sssd.conf | awk -F= &#039;{print $2}&#039; | tr -d &#039; &#039;) \
  -b &quot;dc=$(hostname -d | sed &#039;s/\./,dc=/g&#039;)&quot; \
  &quot;(uid=$(whoami))&quot; \
  dn objectClass uid uidNumber gidNumber homeDirectory loginShell

# 3. Find the groups you&#039;re in
ldapsearch -x \
  -H ldap://your-dc \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(member=$(ldapsearch -x ... &quot;(uid=$(whoami))&quot; dn | grep ^dn | cut -d&#039; &#039; -f2-))&quot; \
  cn gidNumber

# 4. Check what object classes your entry has
ldapsearch -x \
  -H ldap://your-dc \
  -b &quot;dc=corp,dc=com&quot; \
  &quot;(uid=$(whoami))&quot; \
  objectClass
</code></pre>
<p>On a machine joined to Active Directory, the <code class="" data-line="">ldap_uri</code> in sssd.conf is your domain controller&#8217;s address. On FreeIPA or OpenLDAP, it&#8217;s the directory server. The same <code class="" data-line="">ldapsearch</code> commands work against all of them — because they all speak LDAP v3.</p>
<hr />
<h2 id="common-misconceptions"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Misconceptions</h2>
<p><strong>&#8220;The DN is like a file path.&#8221;</strong> The analogy holds for reading it, but the DIT is not a file system. Entries don&#8217;t inherit permissions from parent containers the way files inherit from directories. Access control in LDAP is defined by ACLs on the server — not by position in the tree.</p>
<p><strong>&#8220;LDAP is case-sensitive.&#8221;</strong> It depends on the attribute. Most string attributes (like <code class="" data-line="">cn</code> and <code class="" data-line="">mail</code>) use case-insensitive matching by default — <code class="" data-line="">(cn=Vamshi)</code> and <code class="" data-line="">(cn=vamshi)</code> return the same results. But some attributes (like <code class="" data-line="">userPassword</code> and most binary types) are case-sensitive. The schema&#8217;s matching rules define this per-attribute.</p>
<p><strong>&#8220;You need the full DN to search for a user.&#8221;</strong> No. The <code class="" data-line="">Search</code> operation with a <code class="" data-line="">sub</code> scope searches the entire subtree below the base DN. You search with a filter like <code class="" data-line="">(uid=vamshi)</code> without knowing the full DN. The DN comes back in the result.</p>
<p><strong>&#8220;LDAP accounts and Linux accounts are the same thing.&#8221;</strong> An LDAP user entry becomes a Linux account only if the entry has a <code class="" data-line="">posixAccount</code> object class with the required POSIX attributes (<code class="" data-line="">uidNumber</code>, <code class="" data-line="">gidNumber</code>, <code class="" data-line="">homeDirectory</code>). An LDAP entry without <code class="" data-line="">posixAccount</code> can exist in the directory but <code class="" data-line="">getent passwd</code> will not return it.</p>
<p><strong>&#8220;The objectClass attribute can be changed freely.&#8221;</strong> Structural object classes cannot be changed after an entry is created — you&#8217;d have to delete and recreate the entry. Auxiliary classes can be added or removed. This is why correctly choosing the structural class at entry creation time matters.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>DIT structure, DN addressing, object classes, and schema are the data model underpinning every enterprise identity store — understanding them is foundational to managing directory-based IAM</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>BER on port 389 is unencrypted; LDAPS (port 636) or StartTLS is required for production — wire-level understanding informs the transport security decision</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>Schema design and DIT hierarchy are architectural decisions with security consequences: overly permissive schemas enable privilege escalation; flat DITs make access delegation harder</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>The DIT is a hierarchical database — every entry has a unique DN that describes its path from leaf to root</li>
<li>Object classes define the schema rules for each entry: what attributes are required (<code class="" data-line="">MUST</code>) vs optional (<code class="" data-line="">MAY</code>), and what the entry fundamentally is</li>
<li>For a user to be usable for Linux logins, the directory entry needs the <code class="" data-line="">posixAccount</code> object class with <code class="" data-line="">uidNumber</code>, <code class="" data-line="">gidNumber</code>, and <code class="" data-line="">homeDirectory</code> populated</li>
<li>An LDAP login is two operations: a Bind (authenticate), then a Search (retrieve POSIX attributes and group memberships)</li>
<li>Everything on the wire is BER-encoded binary — <code class="" data-line="">ldapsearch</code> output is LDIF, a human-readable transformation of what the wire actually carries</li>
<li>LDAP result code 0 means success; 49 means bad credentials; 32 means the base DN doesn&#8217;t exist — these are the three you&#8217;ll debug most often</li>
</ul>
<hr />
<hr />
<p>Run <code class="" data-line="">ldapsearch</code> against your own directory and look at the object classes on your entry. Does it have <code class="" data-line="">posixAccount</code>? Does it have <code class="" data-line="">shadowAccount</code>? What attributes is your SSSD actually reading on every login — and what does it do when the LDAP server is unreachable? <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f447.png" alt="👇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP02 showed what&#8217;s inside the directory: the tree structure, the schema, the operations, and the wire protocol. What it left open is how Linux actually uses this information to grant a login.</p>
<p>LDAP is not, by itself, an authentication protocol. The Bind operation can verify a password — but that&#8217;s a tiny piece of what happens when you SSH into a machine joined to Active Directory. The full login flow runs through PAM, NSS, and SSSD before LDAP ever gets queried. EP03 traces that path.</p>
<p><em>Next: <a href="/ldap-authentication-linux-pam-nss/">LDAP Authentication on Linux: PAM, NSS, and the Login Stack</a></em></p>
<p>Get EP03 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&amp;linkname=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fldap-internals-directory-structure%2F&#038;title=LDAP%20Internals%3A%20The%20Directory%20Tree%2C%20Schema%2C%20and%20What%20Travels%20on%20the%20Wire" data-a2a-url="https://linuxcent.com/ldap-internals-directory-structure/" data-a2a-title="LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire"></a></p><p>The post <a href="https://linuxcent.com/ldap-internals-directory-structure/">LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/ldap-internals-directory-structure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1771</post-id>	</item>
		<item>
		<title>What Is LDAP — and Why It Was Invented to Replace Something Worse</title>
		<link>https://linuxcent.com/what-is-ldap/</link>
					<comments>https://linuxcent.com/what-is-ldap/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Fri, 24 Apr 2026 04:43:19 +0000</pubDate>
				<category><![CDATA[Identity & Authentication]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Directory Services]]></category>
		<category><![CDATA[Enterprise IT]]></category>
		<category><![CDATA[Identity Management]]></category>
		<category><![CDATA[LDAP]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[linux-security]]></category>
		<guid isPermaLink="false">https://linuxcent.com/what-is-ldap/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span>LDAP solved 1980s authentication chaos and still powers enterprise logins today. Learn what it replaced, how it works, and why it's still in your stack.</p>
<p>The post <a href="https://linuxcent.com/what-is-ldap/">What Is LDAP — and Why It Was Invented to Replace Something Worse</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 9</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>The Identity Stack, Episode 1</em><br />
<strong>EP01</strong> → <a href="/ldap-internals-directory-structure/">EP02: LDAP Internals</a> → EP03 → &#8230;</p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>LDAP (Lightweight Directory Access Protocol) is a protocol for reading and writing directory information — most commonly, who is allowed to do what</li>
<li>It was built in 1993 as a &#8220;lightweight&#8221; alternative to X.500/DAP, which ran over the full OSI stack and was impossible to deploy on anything but mainframe hardware</li>
<li>Before LDAP, every server had its own <code class="" data-line="">/etc/passwd</code> — 50 machines meant 50 separate user databases, managed manually</li>
<li>NIS (Network Information Service) was the first attempt to centralize this — it worked, then became a cleartext-credentials security liability</li>
<li>LDAP v3 (RFC 2251, 1997) is the version still in production today — 27 years of backwards compatibility</li>
<li>Everything you use today — Active Directory, Okta, Entra ID — is built on top of, or speaks, LDAP</li>
</ul>
<hr />
<h2 id="the-big-picture-50-years-of-who-are-you">The Big Picture: 50 Years of &#8220;Who Are You?&#8221;</h2>
<pre><code class="" data-line="">1969–1980s   /etc/passwd — per-machine, no network auth
     │        50 servers = 50 user databases, managed manually
     │
     ▼
1984         Sun NIS / Yellow Pages — first centralized directory
     │        broadcast-based, no encryption, flat namespace
     │        Revolutionary for its era. A liability by the 1990s.
     │
     ▼
1988         X.500 / DAP — enterprise-grade directory services
     │        OSI protocol stack. Powerful. Impossible to deploy.
     │        Mainframe-class infrastructure required just to run it.
     │
     ▼
1993         RFC 1487 — LDAP v1
     │        Tim Howes, University of Michigan.
     │        Lightweight. TCP/IP. Actually deployable.
     │
     ▼
1997         RFC 2251 — LDAP v3
     │        SASL authentication. TLS. Controls. Referrals.
     │        The version still in production today.
     │
     ▼
2000s–now    Active Directory, OpenLDAP, 389-DS, FreeIPA
             Okta, Entra ID, Google Workspace
             LDAP DNA in every identity system on the planet.
</code></pre>
<p>What is LDAP? It&#8217;s the protocol that solved one of the most boring and consequential problems in computing: how do you know who someone is, across machines, at scale, without sending their password in cleartext?</p>
<hr />
<h2 id="the-world-before-ldap">The World Before LDAP</h2>
<p>Before you understand why LDAP was invented, you need to feel the problem it solved.</p>
<p>Every Unix machine in the 1970s and 1980s managed its own users. When you created an account on a server, your username, UID, and hashed password went into <code class="" data-line="">/etc/passwd</code> on that machine. Another machine had no idea you existed. If you needed access to ten servers, an administrator created ten separate accounts — manually, one by one. When you changed your password, each account had to be updated separately.</p>
<p>For a university with 200 machines and 10,000 students, this was chaos. For a company with offices in three cities, it was a full-time job for multiple sysadmins.</p>
<pre><code class="" data-line="">Machine A           Machine B           Machine C
/etc/passwd         /etc/passwd         /etc/passwd
vamshi:x:1001       (vamshi unknown)    vamshi:x:1004
alice:x:1002        alice:x:1001        alice:x:1003
bob:x:1003          bob:x:1002          (bob unknown)

Same people, different UIDs, different machines, no central truth.
File permissions become meaningless when UID 1001 means
different users on different hosts.
</code></pre>
<p>For every new hire, an admin SSHed to every machine and ran <code class="" data-line="">useradd</code>. When someone left, you hoped whoever ran the offboarding remembered all the machines. Most organizations didn&#8217;t know their own attack surface because there was no single place to look.</p>
<hr />
<h3 id="sun-nis-the-first-attempt-at-centralization">Sun NIS: The First Attempt at Centralization</h3>
<p>Sun Microsystems released NIS (Network Information Service) in 1984, originally called Yellow Pages — a name they had to drop after a trademark dispute with British Telecom. The idea was elegant: one server holds the authoritative <code class="" data-line="">/etc/passwd</code> (and <code class="" data-line="">/etc/group</code>, <code class="" data-line="">/etc/hosts</code>, and a dozen other maps), and client machines query it instead of reading local files.</p>
<p>For the first time, you could create an account once and have it work across your entire network. For a generation of Unix administrators, NIS was liberating.</p>
<pre><code class="" data-line="">       NIS Master Server
       /var/yp/passwd.byname
              │
    ┌─────────┼──────────┐
    ▼         ▼          ▼
 Client A   Client B   Client C
 (query NIS — no local /etc/passwd needed)
</code></pre>
<p>NIS worked well — until it didn&#8217;t. The failure modes were structural:</p>
<p><strong>No encryption.</strong> NIS responses were cleartext UDP. An attacker on the same network segment could capture the full password database with a packet sniffer. In 1984, &#8220;the network&#8221; meant a trusted corporate LAN. By the mid-1990s, it meant ethernet segments that included lab workstations, and the assumptions no longer held.</p>
<p><strong>Broadcast-based discovery.</strong> NIS clients found servers by broadcasting on the local network. This worked on a single flat ethernet. It failed completely across routers, across buildings, and across WAN links. Multi-site organizations ended up running separate NIS domains with no connection between them — which partially defeated the purpose.</p>
<p><strong>Flat namespace.</strong> NIS had no organizational hierarchy. One domain. Everything flat. You couldn&#8217;t have <code class="" data-line="">engineering</code> and <code class="" data-line="">finance</code> as separate administrative units. You couldn&#8217;t delegate user management to a department. One person — usually one overworked sysadmin — managed the whole thing.</p>
<p><strong>UIDs had to match across all machines.</strong> If <code class="" data-line="">alice</code> was UID 1002 on one server but UID 1001 on another, NFS file ownership became wrong. NIS enforced consistency, but onboarding a new machine into an existing network required manually auditing UID conflicts across the entire directory. Get one wrong and files end up owned by the wrong person.</p>
<p>NIS worked for thousands of installations from 1984 to the mid-1990s. It also ended careers when it failed. What the industry needed was a hierarchical, structured, encrypted, scalable directory service.</p>
<hr />
<h2 id="x500-and-dap-the-right-idea-wrong-protocol">X.500 and DAP: The Right Idea, Wrong Protocol</h2>
<p>The OSI (Open Systems Interconnection) standards body had an answer: X.500 directory services. X.500 was comprehensive, hierarchical, globally federated. The ITU-T published the standard in 1988, and it looked like exactly what enterprises needed.</p>
<pre><code class="" data-line="">X.500 Directory Information Tree (DIT)
              c=US                   ← country
                │
         o=University                ← organization
                │
         ┌──────┴──────┐
     ou=CS           ou=Physics      ← organizational units
         │
     cn=Tim Howes                    ← common name (person)
     telephoneNumber: +1-734-...
     mail: tim@umich.edu
</code></pre>
<p>This data model — the hierarchy, the object classes, the distinguished names — is exactly what LDAP inherited. The DIT, the <code class="" data-line="">cn=</code>, <code class="" data-line="">ou=</code>, <code class="" data-line="">dc=</code> notation in every LDAP query you&#8217;ve ever read: all of it came from X.500.</p>
<p>The problem was DAP: the Directory Access Protocol that X.500 used to communicate.</p>
<p>DAP ran over the full OSI protocol stack. Not TCP/IP — OSI. Seven layers, all of which required specialized software that in 1988 only mainframe and minicomputer vendors had implemented. A university department wanting to run X.500 needed hardware and software licenses that cost as much as a small car. The vast majority of workstations couldn&#8217;t speak OSI at all.</p>
<p>The data model was sound. The transport was impractical.</p>
<pre><code class="" data-line="">X.500 / DAP (1988)              LDAP v1 (1993)
──────────────────              ──────────────
Full OSI stack (7 layers)  →    TCP/IP only
Mainframe-class hardware   →    Any Unix box with a TCP stack
$50,000+ deployment cost   →    Free (reference implementation)
Vendor-specific OSI impl.  →    Standard socket API
Zero internet adoption     →    Universities deployed immediately
</code></pre>
<hr />
<h2 id="the-invention-ldap-at-the-university-of-michigan">The Invention: LDAP at the University of Michigan</h2>
<p>Tim Howes was at the University of Michigan in the early 1990s. The university was running X.500 for its directory — faculty, staff, student contact information, credentials. The data model was good. The protocol was the problem.</p>
<p>His insight, working with colleagues Wengyik Yeong and Steve Kille: strip X.500 down to what actually needs to function over a TCP/IP connection. Keep the hierarchical data model. Throw away the OSI transport. The result was the Lightweight Directory Access Protocol.</p>
<p>RFC 1487, published July 1993, described LDAP v1. It preserved the X.500 directory information model — the hierarchy, the object classes, the distinguished name format — and mapped it onto a protocol that could run over a simple TCP socket on port 389.</p>
<p>No specialized hardware. No OSI. If you had a Unix machine and TCP/IP, you could run LDAP. By 1993, that meant virtually every workstation and server in every university and most enterprises.</p>
<p>The University of Michigan deployed it immediately. Within two years, organizations across the internet were running the reference implementation.</p>
<p>LDAP v2 (RFC 1777, 1995) cleaned up the protocol. LDAP v3 (RFC 2251, 1997) is the version in production today — adding SASL authentication (which enables Kerberos integration), TLS support, referrals for federated directories, and extensible controls for server-side operations. The RFC that standardized the internet&#8217;s primary identity protocol is 27 years old and still running.</p>
<hr />
<h2 id="what-ldap-actually-is">What LDAP Actually Is</h2>
<p>LDAP is a client-server protocol for reading and writing a directory — a structured, hierarchical database optimized for reads.</p>
<p>Every entry in the directory has a Distinguished Name (DN) that describes its position in the hierarchy, and a set of attributes defined by its object classes. A person entry looks like this:</p>
<pre><code class="" data-line="">dn: cn=vamshi,ou=engineers,dc=linuxcent,dc=com

objectClass: inetOrgPerson
objectClass: posixAccount
cn: vamshi
uid: vamshi
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/vamshi
loginShell: /bin/bash
mail: vamshi@linuxcent.com
</code></pre>
<p>The DN reads right-to-left: domain <code class="" data-line="">linuxcent.com</code> (<code class="" data-line="">dc=linuxcent,dc=com</code>) → organizational unit <code class="" data-line="">engineers</code> → common name <code class="" data-line="">vamshi</code>. Every entry in the directory has a unique path through the tree — there&#8217;s no ambiguity about which <code class="" data-line="">vamshi</code> you mean.</p>
<p>LDAP defines eight operations: Bind (authenticate), Search, Add, Modify, Delete, ModifyDN (rename), Compare, and Abandon. Most of what a Linux authentication system does with LDAP reduces to two: <strong>Bind</strong> (prove you are who you say you are) and <strong>Search</strong> (tell me everything you know about this user).</p>
<p>When your Linux machine authenticates an SSH login against LDAP:</p>
<pre><code class="" data-line="">1. User types password
2. PAM calls pam_sss (or pam_ldap on older systems)
3. SSSD issues a Bind to the LDAP server: &quot;I am cn=vamshi, and here is my credential&quot;
4. LDAP server verifies the bind → success or failure
5. SSSD issues a Search: &quot;give me the posixAccount attributes for uid=vamshi&quot;
6. LDAP returns uidNumber, gidNumber, homeDirectory, loginShell
7. PAM creates the session with those attributes
</code></pre>
<p>The entire login flow is two LDAP operations: one Bind, one Search.</p>
<hr />
<h2 id="try-it-right-now">Try It Right Now</h2>
<p>You don&#8217;t need to set up an LDAP server to run your first query. There&#8217;s a public test LDAP directory at <code class="" data-line="">ldap.forumsys.com</code>:</p>
<pre><code class="" data-line=""># Query a public LDAP server — no setup required
ldapsearch -x \
  -H ldap://ldap.forumsys.com \
  -b &quot;dc=example,dc=com&quot; \
  -D &quot;cn=read-only-admin,dc=example,dc=com&quot; \
  -w readonly \
  &quot;(objectClass=inetOrgPerson)&quot; \
  cn mail uid

# What you get back (abbreviated):
# dn: uid=tesla,dc=example,dc=com
# cn: Tesla
# mail: tesla@ldap.forumsys.com
# uid: tesla
#
# dn: uid=einstein,dc=example,dc=com
# cn: Albert Einstein
# mail: einstein@ldap.forumsys.com
# uid: einstein
</code></pre>
<p>Decode what you just ran:</p>
<ul>
<li><code class="" data-line="">-x</code> — simple authentication (username/password bind, not Kerberos/SASL)</li>
<li><code class="" data-line="">-H ldap://ldap.forumsys.com</code> — the LDAP server URI, port 389</li>
<li><code class="" data-line="">-b &quot;dc=example,dc=com&quot;</code> — the base DN, the top of the subtree to search</li>
<li><code class="" data-line="">-D &quot;cn=read-only-admin,dc=example,dc=com&quot;</code> — the bind DN (who you&#8217;re authenticating as)</li>
<li><code class="" data-line="">-w readonly</code> — the bind password</li>
<li><code class="" data-line="">&quot;(objectClass=inetOrgPerson)&quot;</code> — the search filter: return entries that are people</li>
<li><code class="" data-line="">cn mail uid</code> — the attributes to return (default returns all)</li>
</ul>
<p>That&#8217;s a live LDAP query returning real directory entries from a server running RFC 2251 — the same protocol Tim Howes designed in 1993.</p>
<p>On your own Linux system, if you&#8217;re joined to AD or LDAP, you can query it the same way with your domain credentials.</p>
<hr />
<h2 id="why-it-never-went-away">Why It Never Went Away</h2>
<p>LDAP v3 was finalized in 1997. In 2024, it&#8217;s still the protocol every enterprise directory speaks. Why?</p>
<p>Because it became the lingua franca of enterprise identity before any replacement existed. Every application that needs to authenticate users — VPN concentrators, mail servers, network switches, web applications, HR systems — implemented LDAP support. Every directory service Microsoft, Red Hat, Sun, and Novell shipped stored data in an LDAP-accessible tree.</p>
<p>When Microsoft built Active Directory in 1999, they built it on top of LDAP + Kerberos. When your Linux machine joins an AD domain, it speaks LDAP to enumerate users and groups, and Kerberos to verify credentials. When Okta or Entra ID syncs with your on-premises directory, it uses LDAP Sync (or a modern protocol that maps directly to LDAP semantics).</p>
<p>The protocol is old. The ecosystem built on top of it is so deep that replacing LDAP would mean simultaneously replacing every enterprise application that depends on it. Nobody has done that. Nobody has had to.</p>
<p>What happened instead is the stack got taller. LDAP at the bottom, Kerberos for network authentication, SSSD as the local caching daemon, PAM as the Linux integration layer, SAML and OIDC at the top for web-based federation. The directory is still LDAP. The interfaces above it evolved.</p>
<p>That full stack — from the directory at the bottom to Zero Trust at the top — is what this series covers.</p>
<hr />
<h2 id="common-misconceptions"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Misconceptions</h2>
<p><strong>&#8220;LDAP is an authentication protocol.&#8221;</strong> LDAP is a directory protocol. It stores identity information and can verify credentials (via Bind). Authentication in modern stacks is typically Kerberos or OIDC — LDAP provides the directory backing it.</p>
<p><strong>&#8220;LDAP is obsolete.&#8221;</strong> LDAP is the storage layer for Active Directory, OpenLDAP, 389-DS, FreeIPA, and every enterprise IdP&#8217;s on-premises sync. It is ubiquitous. What&#8217;s changed is the interface layer above it.</p>
<p><strong>&#8220;You need Active Directory to run LDAP.&#8221;</strong> Active Directory uses LDAP. OpenLDAP, 389-DS, FreeIPA, and Apache Directory Server are all standalone LDAP implementations. You can run a directory without Microsoft.</p>
<p><strong>&#8220;LDAP and LDAPS are different protocols.&#8221;</strong> LDAP is the protocol. LDAPS is LDAP over TLS on port 636. StartTLS is LDAP on port 389 with an in-session upgrade to TLS. Same protocol, different transport security.</p>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Domain</th>
<th>Relevance</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP Domain 5: Identity and Access Management</td>
<td>LDAP is the foundational directory protocol for centralized identity stores — the base layer of every enterprise IAM stack</td>
</tr>
<tr>
<td>CISSP Domain 4: Communications and Network Security</td>
<td>Port 389 (LDAP), 636 (LDAPS), 3268/3269 (AD Global Catalog) — transport security decisions affect every directory deployment</td>
</tr>
<tr>
<td>CISSP Domain 3: Security Architecture and Engineering</td>
<td>DIT hierarchy, schema design, replication topology — directory structure is an architectural security decision</td>
</tr>
<tr>
<td>NIST SP 800-63B</td>
<td>LDAP as a credential service provider (CSP) backing enterprise authenticators</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>LDAP was invented to solve a real, painful problem: the authentication chaos that NIS couldn&#8217;t fix and X.500/DAP was too expensive to deploy</li>
<li>It inherited the right thing from X.500 (the hierarchical data model) and replaced the right thing (the impractical OSI transport with TCP/IP)</li>
<li>NIS was the predecessor that worked until it didn&#8217;t — its failure modes (no encryption, flat namespace, broadcast discovery) are exactly what LDAP was designed to fix</li>
<li>LDAP v3 (RFC 2251, 1997) is still the production standard — 27 years later</li>
<li>Active Directory, OpenLDAP, FreeIPA, Okta, Entra ID — every enterprise identity system either runs LDAP or speaks it</li>
<li>The full authentication stack is deeper than LDAP: the next 12 episodes peel it apart layer by layer</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP01 stayed at the design level — the problem, the predecessor failures, the invention, the data model.</p>
<p>EP02 goes inside the wire. The DIT structure, DN syntax, object classes, schema, and the BER-encoded bytes that actually travel from the server to your authentication daemon. Run <code class="" data-line="">ldapsearch</code> against your own directory and read every line of what comes back.</p>
<p><em>Next: <a href="/ldap-internals-directory-structure/">LDAP Internals: The Directory Tree, Schema, and What Travels on the Wire</a></em></p>
<p>Get EP02 in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&amp;linkname=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fwhat-is-ldap%2F&#038;title=What%20Is%20LDAP%20%E2%80%94%20and%20Why%20It%20Was%20Invented%20to%20Replace%20Something%20Worse" data-a2a-url="https://linuxcent.com/what-is-ldap/" data-a2a-title="What Is LDAP — and Why It Was Invented to Replace Something Worse"></a></p><p>The post <a href="https://linuxcent.com/what-is-ldap/">What Is LDAP — and Why It Was Invented to Replace Something Worse</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/what-is-ldap/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1545</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-26 06:29:20 by W3 Total Cache
-->