Andrew Mercer
on this page

FreeRADIUS Setup Guide (RHEL/CentOS + Debian/Ubuntu)

Condensed reference for installing, configuring, and testing FreeRADIUS with LDAP backend and proxying to Windows/AD RADIUS servers. All example secrets, IPs, and domains below are placeholders — replace before use.


1. Install

RHEL/CentOS:

yum -y install freeradius freeradius-utils freeradius-mysql freeradius-ldap freeradius-client

Debian/Ubuntu:

sudo apt install freeradius

2. Core Config — radiusd.conf

Key settings (defaults mostly fine to leave as-is):

security {
    allow_core_dumps = no
    max_attributes = 200
    status_server = yes
}

proxy_requests = yes
$INCLUDE proxy.conf
$INCLUDE clients.conf

thread pool {
    start_servers = 5
    max_servers = 32
    min_spare_servers = 3
    max_spare_servers = 10
}

log {
    destination = files
    file = ${logdir}/radius.log
    auth = yes
    auth_badpass = no   # keep 'no' outside of debugging  logs plaintext-adjacent failures otherwise
    auth_goodpass = no
}

Security note: auth_badpass / auth_goodpass should stay no in production. Only flip to yes temporarily while debugging auth issues, then revert.


3. Proxying to Upstream (e.g. Windows AD/NPS) RADIUS Servers

Define home servers and a pool, then map a realm to that pool:

# proxy.conf
home_server hs1_<server_ip_1> {
    type = auth+acct
    ipaddr = <server_ip_1>
    port = 1645
    secret = <shared_secret>       # coordinate with the Windows RADIUS admin
}

home_server hs2_<server_ip_2> {
    type = auth+acct
    ipaddr = <server_ip_2>
    port = 1645
    secret = <shared_secret>
}

home_server_pool pool_<hostname> {
    type = fail-over            # or load-balancing
    home_server = <server_ip_1>
    home_server = <server_ip_2>
}

realm <your_domain> {
    pool = pool_<hostname>
    nostrip
}

Naming convention used: - Home servers: hs1_<ip>, hs2_<ip> - Pool: pool_<hostname>

The shared secret and this FreeRADIUS server's IP need to be provided to the upstream Windows RADIUS/NPS administrator so both sides agree.


4. LDAP Backend — mods-available/ldap

ldap {
    server = "localhost"
    base_dn = "<dc=example,dc=local>"

    update {
        control:Password-With-Header += 'userPassword'
    }

    user {
        base_dn = "${..base_dn}"
        filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})"
    }

    group {
        base_dn = "${..base_dn}"
        filter = "(objectClass=posixGroup)"
        membership_attribute = "memberOf"
    }

    client {
        base_dn = "${..base_dn}"
        filter = '(objectClass=frClient)'
        attribute {
            identifier = 'radiusClientIdentifier'
            secret     = 'radiusClientSecret'
        }
    }

    accounting {
        reference = "%{tolower:type.%{Acct-Status-Type}}"
        type {
            start          { update { description := "Online at %S" } }
            interim-update { update { description := "Last seen at %S" } }
            stop           { update { description := "Offline at %S" } }
        }
    }

    post-auth {
        update { description := "Authenticated at %S" }
    }

    options {
        chase_referrals = yes
        rebind = yes
        timeout = 10
        timelimit = 3
    }

    tls {
        # start_tls = yes
        # ca_file = ${certdir}/cacert.pem
        # certificate_file = /path/to/radius.crt
        # private_key_file = /path/to/radius.key
    }

    pool {
        start = 5
        min = 4
        max = ${thread[pool].max_servers}
        spare = 3
    }
}

Related: OpenLDAP install/config is a prerequisite for the above — see separate OpenLDAP notes.


5. Clients — clients.conf

Register hosts allowed to send RADIUS requests to this server:

client localhost {
    ipaddr = 127.0.0.1
    secret = <secret>
    require_message_authenticator = no
    nastype = other
}

client <friendly-name> {
    ipaddr = <client_ip>
    secret = <secret>
    require_message_authenticator = no
    nastype = other
}

6. Test Users (local, non-LDAP)

Useful for isolating whether an issue is FreeRADIUS-side vs. LDAP/AD-side.

# /etc/raddb/users  or  /etc/freeradius/users
"<testuser>" Cleartext-Password := "<testpass>"
    Framed-IP-Address = 127.0.0.1,
    Reply-Message = "Hello, %{User-Name}"

DEFAULT Framed-Protocol == PPP
    Framed-Protocol = PPP,
    Framed-Compression = Van-Jacobson-TCP-IP

For VLAN assignment via RADIUS (e.g. WPA2-Enterprise):

"<testuser>" Cleartext-Password := "<testpass>"
    Tunnel-Type = VLAN,
    Tunnel-Medium-Type = IEEE-802,
    Tunnel-Private-Group-ID = "<vlan_id>"

7. Start / Enable

systemctl enable radiusd      # RHEL/CentOS
systemctl start radiusd

# or on Debian/Ubuntu
service freeradius start

8. Testing

Debug mode

systemctl stop radiusd     # or: service freeradius stop
radiusd -X                 # or: freeradius -X

Local test

radtest <user> '<password>' 127.0.0.1 1812 <shared_secret>

Expect rad_recv: Access-Accept on success. Confirm in the log:

tail -F /var/log/radius/radius.log
# Auth: Login OK: [<user>/<user>] (from client localhost port 1812)

Proxied test (to upstream/AD realm)

radtest <user>@<realm> '<AD_password>' 127.0.0.1 1645 <shared_secret>

A successful response returns standard attributes (Framed-Protocol, Service-Type, Class, etc.) — presence of Access-Accept confirms the proxy chain and realm suffix stripping/matching are working correctly.

Packet capture (optional deep debug)

tcpdump -i <iface> udp port 1812 or udp port 1645

Confirms the Access-Request is actually leaving with the expected username/attributes.


9. WPA2-Enterprise (802.1X) — Not Yet Documented

Two sections remain outstanding from the original notes and need to be written up: - Configuring the wireless controller/AP for WPA2-Enterprise (RADIUS server IP, shared secret, port) - Configuring wireless clients (EAP method, trust anchor / CA cert for the RADIUS server)


10. Troubleshooting

Home server marked "zombie" / no response:

Marking home server <ip> port 1645 as zombie (it looks like it is dead).
radclient: no response from server for ID <n> socket <n>

Usually a network-reachability issue between the FreeRADIUS box and the upstream RADIUS server — confirm the relevant UDP ports (1812/1813 or 1645/1646) are open, and engage the network team if not.


Notes on This Version

  • All example secrets (testing123, etc.), real IPs, internal domain names, and email addresses from the original notes have been replaced with placeholders.
  • Default FreeRADIUS test secret is conventionally testing123 — fine for local/lab testing, but confirm it's changed before any client-facing or production use.
  • Optional related component: a Drupal RADIUS client module was referenced in the original notes for web-app integration — worth a separate write-up if that's still in use, since it's unrelated to the core RADIUS/LDAP setup above.