29 March 2026

📌 Spent the night building centralised authentication for the homelab. Blind Guardian in the headphones. The goal: one LDAP source of truth for every host, no more per-machine root access.

lldap on Proxmox

The choice was lldap over slapd. OpenLDAP proper is a configuration nightmare - the cn=config format alone is reason enough to look elsewhere. lldap is a Rust binary exposing a compatible LDAP subset with a usable web UI. Sufficient for auth and group management, transparent to clients.

Deployed as a Docker container on a dedicated Ubuntu 24.04 LXC (CT 108, 192.168.0.108):

services:
  lldap:
    image: lldap/lldap:stable
    container_name: lldap
    restart: unless-stopped
    ports:
      - "3890:3890"
      - "17170:17170"
    volumes:
      - /var/lib/lldap:/data
    environment:
      - LLDAP_JWT_SECRET=<redacted>
      - LLDAP_LDAP_BASE_DN=dc=jolek78,dc=dev
      - LLDAP_LDAP_USER_PASS=<redacted>

Two notes: the apt version of docker-compose on Ubuntu is broken on Python 3.12 - distutils was removed. Solution is to add the official Docker repo and install docker-compose-plugin instead.

schema adjustments

lldap doesn't expose uidNumber or gidNumber by default. nslcd needs both to resolve users to POSIX identities. The fix is adding them as custom attributes via the web UI (User schema → Create an attribute, type Integer), then assigning values to each user and group. jolek78 and homelab_admins both got 10000.

nslcd on every host

The same config applied to Proxmox itself and all twenty-odd LXC containers:

uri ldap://192.168.0.108:3890/
base dc=jolek78,dc=dev
binddn uid=admin,ou=people,dc=jolek78,dc=dev
base passwd ou=people,dc=jolek78,dc=dev
base group ou=groups,dc=jolek78,dc=dev
filter passwd (objectClass=person)
filter group (objectClass=groupOfUniqueNames)
map passwd homeDirectory "/home/$uid"
map passwd loginShell "/bin/bash"
map group member uniqueMember

The filter group and map group member uniqueMember lines are non-obvious. lldap uses groupOfUniqueNames rather than posixGroup, and the member attribute is uniqueMember not member. Without the mapping, getent group returns nothing.

sudo access via group membership:

echo "%homelab_admins ALL=(ALL:ALL) ALL" > /etc/sudoers.d/homelab_admins

PAM

One line in /etc/pam.d/common-session:

session required pam_mkhomedir.so skel=/etc/skel umask=0022

Ensures the home directory is created on first login. Worth using grep -v before appending to avoid duplicates if the script is run more than once.

SSH key auth

Distributed id_ed25519_jolek78-dev to all hosts via ssh-copy-id. Root SSH disabled on Proxmox. From here, jolek78 is the only entry point everywhere.

Termix updated with all 26 hosts via JSON bulk import, credential ID 1 (SSH key).

what's left

lldap integration pending for: Nextcloud, Gitea, Portainer, the AI server, the Tor relay. Also: librenms dashboarding for lldap itself, and SSH host verification on a few containers that were missing sshd entirely.