Windows-Style SSO on Linux — No Domain Join Required
Domain join gets a machine a computer account. It turns out a user ticket never needed one at all.
Your domain-joined Windows box logs into internal web apps silently. Kerberos ticket, SPNEGO handshake, done — you never see a login form. The question was whether a Linux machine that AD has never heard of can pull off the same trick.
It can. Domain join is for machine accounts. A user ticket needs three things: a Kerberos client, five lines of config, and your password. Here’s the whole journey, including the two places I face-planted.
The setup
sudo apt install krb5-user
Then /etc/krb5.conf:
[libdefaults]
default_realm = EXAMPLE.COM
dns_lookup_kdc = true
dns_lookup_realm = true
rdns = falseThat’s it. No KDC addresses hardcoded — AD publishes its domain controllers in DNS, and the client finds them on its own:
dig +short SRV _kerberos._tcp.example.com # 0 100 88 dc01.example.com. # 0 100 88 dc02.example.com.
(Internal DNS only. Public resolvers know nothing. On VPN, check resolvectl status before blaming Kerberos.)
Bonus AD fact: every DC is simultaneously KDC, password server, and admin/LDAP endpoint. The MIT-Kerberos distinction between kdc and admin_server? Collapses. Any DC does everything.
Getting a ticket — and face-plant #1
First attempt:
kinit myuser@example.com
Result: a blank line. No prompt, no error, nothing. Just a cursor staring back at me. Pressing Enter produced another blank line. Great software.
The problem: everything after @ is a Kerberos realm, and realms are UPPERCASE. example.com looks like the domain — it looks like my email, even — but to Kerberos it’s an unknown realm, and instead of saying so it just… sulked.
The fix is almost insulting:
kinit myuser Password:
Leave out the realm entirely and default_realm from krb5.conf kicks in. Suddenly there’s an actual, visible Password: prompt — that’s how you know you’re talking to the right realm. Type the password (input is hidden, sudo-style), Enter, and:
klist # Default principal: myuser@EXAMPLE.COM # krbtgt/EXAMPLE.COM@EXAMPLE.COM valid ~10h
A krbtgt entry means you hold a TGT. You are now, cryptographically speaking, logged into the domain.
If kinit ever misbehaves again, this shows exactly what it’s doing — DNS lookups, which DC it contacts, where it dies:
KRB5_TRACE=/dev/stdout kinit myuser
Face-plant #2: “Unable to locate package nc”
Wanted to check whether port 88 was even reachable. apt install nc — no such package. On Debian/Ubuntu it’s called netcat-openbsd. Or skip the install entirely, bash has networking built in:
timeout 3 bash -c 'cat < /dev/null > /dev/tcp/dc01.example.com/88' && echo OPEN || echo CLOSED
Teaching the browser
Windows browsers negotiate automatically because they trust the intranet zone. Linux browsers trust nobody until told otherwise:
Firefox — about:config → network.negotiate-auth.trusted-uris → .example.com
Chrome/Chromium — /etc/opt/chrome/policies/managed/kerberos.json:
{ "AuthServerAllowlist": "*.example.com" }curl — because of course curl can do it:
curl --negotiate -u : https://internal-app.example.com/
Open an internal site. It logs you in. Silently. On a machine that isn’t in the domain.
Extremely satisfying.
The fine print
- Clocks: Kerberos allows ~5 min skew. Keep NTP running or nothing works and the errors won’t tell you why.
- Use FQDNs: the hostname must match the service’s SPN. IPs and random aliases fall back to NTLM or fail.
- Tickets expire (~10h). Re-kinit daily, or automate with
k5start, a keytab, or sssd + KCM.
krb5.conf with an UPPERCASE realm → kinit myuser → browser allowlist. Three steps, zero domain join, and the same silent ticket dance Windows has been doing behind your back all along.
And if kinit ever greets you with a blank line instead of Password: — check your realm. It’s shouting-case or nothing.