Tested on RHEL 8 and OpenSUSE 15.6. Core steps apply to newer versions — package names may differ slightly on RHEL 9+ and newer OpenSUSE releases.
The stack: Samba for SMB/CIFS, SSSD for AD integration, Kerberos for authentication, and realmd for domain join.
Before you start: DNS that resolves your domain controllers, Chrony running and synced, a domain admin account with join privileges, and your domain name and DC hostname to hand — company.com and dc1.company.com throughout below.
Step 1 — Packages#
sudo dnf install -y realmd sssd oddjob oddjob-mkhomedir adcli \
samba-common-tools krb5-workstation chrony samba samba-client \
samba-winbind sssd-winbind-idmap cifs-utils policycoreutils-python-utils
sudo systemctl enable --now chronyd
sudo systemctl enable sssdsudo apt update
sudo apt install -y realmd sssd sssd-tools libnss-sss libpam-sss \
adcli samba-common-bin krb5-user chrony samba smbclient \
winbind cifs-utils
sudo systemctl enable --now chrony
sudo systemctl enable sssdsudo zypper install -y realmd sssd sssd-ad adcli \
samba samba-winbind krb5-client chrony samba-client cifs-utils
sudo systemctl enable --now chronyd
sudo systemctl enable sssdSSSD is only enabled here, not started — it has no configuration yet. realm join (Step 4) will create the initial sssd.conf and start the service automatically.
Ubuntu and OpenSUSE use AppArmor, not SELinux. The policycoreutils-python-utils package and semanage/restorecon commands throughout this guide are RHEL/CentOS-specific. Skip them on Debian-based and OpenSUSE systems.
Step 2 — DNS and time#
AD is found through DNS SRV records, so name resolution has to point at the domain controllers and nothing else. The result should look like this /etc/resolv.conf — but set it where your system manages it (nmcli con mod <connection> ipv4.dns "192.168.1.10 192.168.1.11" ipv4.dns-search company.com under NetworkManager), or the next DHCP renewal writes over a hand edit:
nameserver 192.168.1.10
nameserver 192.168.1.11
search company.com
domain company.comThe second query is the one that matters. If the SRV lookup comes back empty, realm join will fail later with an error about the domain not being found:
dig company.com
dig _ldap._tcp.company.com SRVThen point the clock at the same machines — /etc/chrony.conf, or /etc/chrony/chrony.conf on Debian and Ubuntu:
server dc1.company.com iburst prefer
server dc2.company.com iburstsudo systemctl restart chronyd
chronyc sources -vsudo systemctl restart chrony
chronyc sources -vsudo systemctl restart chronyd
chronyc sources -vKerberos requires time to be within 5 minutes of the domain controllers. If clock skew exceeds this, authentication will fail with KRB5KRB_AP_ERR_SKEW.
Step 3 — Kerberos#
/etc/krb5.conf:
| |
Test it:
kinit administrator@COMPANY.COM
klist
kdestroyStep 4 — Joining the domain#
sudo realm discover company.com
sudo realm join company.com -U administrator \
--client-software=sssd \
--membership-software=samba
sudo realm list
net ads testjoinrealm join --membership-software=samba calls net ads join internally — no need to run it again. It auto-configures /etc/sssd/sssd.conf, /etc/krb5.conf, and /etc/krb5.keytab. The custom sssd.conf in Step 6 intentionally replaces what realm join generated with a production-ready configuration.
Step 5 — PAM#
Home directories have to be created on first login, or AD users land in a shell with no $HOME:
sudo authselect select sssd with-mkhomedir --force
sudo systemctl enable --now oddjobdsudo pam-auth-update --enable mkhomedirlibpam-sss registers its own PAM profile when it’s installed; only home directory creation needs switching on.
sudo pam-config --add --sss
sudo pam-config --add --mkhomedirStep 6 — SSSD#
This replaces what realm join generated. The generated file works; this one is what you’d want in production — /etc/sssd/sssd.conf:
| |
sudo chmod 600 /etc/sssd/sssd.conf
sudo systemctl restart sssdrealm join already enabled and started it; a restart is all the new file needs. SSSD refuses to start if the file is readable by anyone but root, which is what the chmod is for.
ldap_id_mapping = False requires that all AD users and groups have POSIX attributes (uidNumber, gidNumber) configured in Active Directory. If they don’t, user lookups will fail silently. Set to True to let SSSD generate consistent IDs algorithmically instead.
Step 7 — NSS#
On RHEL, authselect owns /etc/nsswitch.conf and the sssd profile from Step 5 already wrote this — edit it by hand and the next authselect run puts it back. On Debian, Ubuntu and openSUSE, /etc/nsswitch.conf:
passwd: files sss
group: files sss
shadow: files sss
netgroup: sss filesDo not add winbind to passwd or group here. In this setup, SSSD handles all NSS resolution. Winbind runs only as an internal Samba component for ID mapping via the sss backend. Mixing sss and winbind in nsswitch causes duplicate lookups and inconsistent results.
Step 8 — Samba#
/etc/samba/smb.conf:
| |
idmap config COMPANY : backend = sss tells Samba’s winbind to delegate ID mapping to SSSD for the COMPANY domain. The default tdb backend handles the * (fallback) range. The sss backend is read-only, so a writable fallback is required.
Group names in valid users must match how SSSD exposes them. With use_fully_qualified_names = False, groups resolve as linuxadmins — not linuxadmins@company.com. Using the FQDN form here will silently block all access.
Validate the configuration before starting services:
sudo testparm -sThe share directories#
sudo mkdir -p /srv/shared /srv/data
sudo chgrp linuxadmins /srv/shared
sudo chmod 775 /srv/shared
sudo chgrp dataaccess /srv/data
sudo chmod 770 /srv/data
# SELinux (RHEL only): label the shares, and allow [homes]
sudo semanage fcontext -a -t samba_share_t "/srv/shared(/.*)?"
sudo semanage fcontext -a -t samba_share_t "/srv/data(/.*)?"
sudo restorecon -R /srv/shared /srv/data
sudo setsebool -P samba_enable_home_dirs onLabel the share directories rather than turning on samba_export_all_rw: that boolean lets Samba write anywhere on the filesystem, and the labels make it unnecessary.
Step 9 — Start it and test it#
The service is smb on RHEL and OpenSUSE, smbd on Debian and Ubuntu:
sudo systemctl enable --now smb winbind
sudo systemctl status smb winbindsudo systemctl enable --now smbd winbind
sudo systemctl status smbd winbindsudo systemctl enable --now smb winbind
sudo systemctl status smb winbindResolution first — if these three don’t return anything, nothing else will work and Samba is not the problem:
getent passwd testuser@company.com
id testuser@company.com
getent group linuxadmins@company.comThen the share itself:
# List shares
smbclient -L localhost -U testuser@company.com
# Test share access
smbclient //localhost/shared -U testuser@company.comFrom a Windows client: \\linux-server\shared
Step 10 — Firewall#
The named service already covers 137/udp, 138/udp, 139/tcp and 445/tcp — there’s no need to open them individually:
sudo firewall-cmd --permanent --add-service=samba
sudo firewall-cmd --reloadsudo ufw allow Samba # the app profile name is case-sensitive
sudo ufw statussudo firewall-cmd --permanent --add-service=samba
sudo firewall-cmd --reloadTroubleshooting#
Clearing caches#
sudo sss_cache -E
sudo systemctl restart sssd
sudo net cache flush
# Stale winbind ID cache: drop only the cache file
sudo systemctl stop smb winbind # smbd on Debian/Ubuntu
sudo rm -f /var/lib/samba/winbindd_cache.tdb
sudo systemctl start winbind smbDon’t clear /var/lib/samba/*.tdb wholesale. Some of those files are state, not cache — the ID mappings among them — and losing them can change which UID owns which file on disk.
Logs#
sudo tail -f /var/log/sssd/sssd_company.com.log
sudo tail -f /var/log/samba/log.smbdFor deeper SSSD debugging, add debug_level = 9 in the [domain/company.com] section and restart sssd.
Checking the AD side#
sudo net ads info
sudo net ads user info testuser
sudo net ads group info "linuxadmins"
klist -k /etc/krb5.keytab
# Winbind checks
sudo wbinfo -t # verify trust secret
sudo wbinfo -n "COMPANY\testuser" # resolve a specific user (safe)
# Avoid wbinfo -u / wbinfo -g in large AD environments — enumerates all
# domain users/groups and can cause significant load on domain controllers
# Active connections
sudo smbstatusThe three errors you’ll actually hit#
NT_STATUS_ACCESS_DENIED — the user resolves but the share refuses. Group name form and SELinux context are the two usual causes:
id username@company.com
sudo testparm -s
ls -Z /srv/shared # SELinux context (RHEL only)Users not resolving — almost always a stale cache after a config change:
sudo sss_cache -E
sudo systemctl restart sssd winbind
getent passwd username@company.comAuthentication failures — check the clock before anything else:
klist
kinit username@COMPANY.COM
chronyc sources -v
net ads testjoinKeeping it working#
/usr/local/bin/samba-maintenance.sh:
#!/bin/bash
# SSSD manages machine account password rotation automatically via
# ad_update_samba_machine_account_password = True — do not run
# net ads changetrustpw here as it conflicts with SSSD's management.
sss_cache -E
tdbbackup /var/lib/samba/*.tdb 2>/dev/null || true
net ads testjoin
echo "Maintenance completed: $(date)"sudo chmod +x /usr/local/bin/samba-maintenance.sh
# Weekly, Sunday 02:00
echo "0 2 * * 0 root /usr/local/bin/samba-maintenance.sh >> /var/log/samba-maintenance.log 2>&1" \
| sudo tee /etc/cron.d/samba-maintenanceA file in /etc/cron.d rather than crontab -: piping into crontab - replaces root’s whole crontab with this one line.
The two settings that cost me the most time are both in Step 6 and Step 8, and neither fails loudly. ldap_id_mapping = False needs POSIX attributes in AD, and without them lookups return nothing rather than an error. valid users = @linuxadmins@company.com blocks everyone while looking exactly like a working line. If something is refusing access and the logs say nothing useful, check those two before anything else.