SMB Authentication with AD on Linux
How to integrate Linux SMB file servers with Active Directory using SSSD, Samba, Kerberos, and realmd — tested on RHEL 8 and OpenSUSE 15.6.
After days of configuration and research, I couldn’t find a single source that covered everything needed for this setup end to end. My job requires centralized SSSD across all Linux servers, so here’s what I got working on both RHEL 8 and OpenSUSE 15.6.
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 sssdStep 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 -vStep 3 — Kerberos
/etc/krb5.conf:
[libdefaults]
default_realm = COMPANY.COM
dns_lookup_kdc = true
dns_lookup_realm = false
rdns = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
udp_preference_limit = 0
[realms]
COMPANY.COM = {
kdc = dc1.company.com
admin_server = dc1.company.com
default_domain = company.com
}
[domain_realm]
.company.com = COMPANY.COM
company.com = COMPANY.COM
[logging]
default = SYSLOG:NOTICE:DAEMONTest 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 testjoinStep 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:
[sssd]
domains = company.com
config_file_version = 2
services = nss, pam
[domain/company.com]
ad_domain = company.com
krb5_realm = COMPANY.COM
realmd_tags = manages-system joined-with-samba
cache_credentials = True
id_provider = ad
ad_update_samba_machine_account_password = True
full_name_format = %3$s\%1$s
use_fully_qualified_names = False
fallback_homedir = /home/%u
default_shell = /bin/bash
# Requires POSIX attributes (uidNumber, gidNumber) set in AD
ldap_id_mapping = False
krb5_store_password_if_offline = True
access_provider = simple
simple_allow_groups = linuxadmins@company.com, itstaff@company.com
simple_allow_users = testuser@company.com
enumerate = Falsesudo 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.
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 filesStep 8 — Samba
/etc/samba/smb.conf:
[global]
realm = COMPANY.COM
workgroup = COMPANY
security = ads
kerberos method = secrets and keytab
dedicated keytab file = /etc/krb5.keytab
log file = /var/log/samba/log.%m
log level = 2
vfs objects = acl_xattr
map acl inherit = yes
store dos attributes = yes
template homedir = /home/%U
template shell = /bin/bash
idmap config * : backend = tdb
idmap config * : range = 10000-199999
idmap config COMPANY : range = 200000-2147483647
idmap config COMPANY : backend = sss
client signing = mandatory
server signing = mandatory
socket options = TCP_NODELAY IPTOS_LOWDELAY
[shared]
path = /srv/shared
read only = no
browsable = yes
valid users = @linuxadmins, @itstaff
force group = linuxadmins
create mask = 0664
directory mask = 0775
[data]
path = /srv/data
read only = no
browsable = yes
valid users = @dataaccess
force group = dataaccess
create mask = 0660
directory mask = 0770
[homes]
comment = Home Directories
valid users = %S
browsable = no
read only = no
create mask = 0700
directory mask = 0700Validate 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.