Skip to main content

RADIUS Protocol: Network Access Control 2026

Created: March 11, 2026 Larry Qu 20 min read

Introduction

Every time you connect to a corporate WiFi, log into a VPN, or dial into an ISP, a RADIUS transaction happens behind the scenes. RADIUS (Remote Authentication Dial-In User Service) is the protocol that provides centralized Authentication, Authorization, and Accounting (AAA) for network access — it’s been the industry standard since 1991 and remains the backbone of enterprise network access control in 2026.

This guide covers RADIUS protocol mechanics, attributes, EAP methods, FreeRADIUS configuration, advanced deployments, security considerations, and troubleshooting. Whether you’re setting up 802.1X WiFi authentication, VPN access, or network device administration, understanding RADIUS is essential.

What is RADIUS?

RADIUS is a client-server protocol defined in RFC 2865 (Authentication/Authorization) and RFC 2866 (Accounting). It enables Network Access Servers (NAS) — WiFi access points, VPN gateways, switches — to authenticate users against a centralized server and receive authorization parameters for the session.

Architecture

                        ┌──────────────────┐
      User ───► NAS ────┤   RADIUS Server   │
                        │ (FreeRADIUS, NPS) │
                        └────────┬─────────┘
                                 │
                        ┌────────▼─────────┐
                        │  Identity Source  │
                        │ (LDAP, AD, SQL)   │
                        └──────────────────┘

The NAS acts as a RADIUS client, forwarding authentication requests from end users to the RADIUS server. The server validates credentials against an identity source (Active Directory, LDAP, local database, token server) and returns authorization attributes — IP address, VLAN assignment, access policy, session timeout, etc.

Key Features

  • Centralized AAA: Single policy enforcement point for all network devices
  • Attribute-Based: Flexible authorization via attribute-value pairs
  • Proxy Support: Chain multiple RADIUS servers across organizational boundaries
  • Accounting: Track usage, duration, and data transfer per session
  • Vendor Extensibility: Vendor-Specific Attributes (VSA) for device-specific policy
  • Stateless Protocol: UDP-based, simple request-response model

RADIUS vs Diameter

Aspect RADIUS Diameter
Transport UDP (1812/1813) TCP/SCTP (3868)
Security Shared secret + MD5 hash IPSec/TLS
Reliability Application-level retransmit Transport-level reliable
Session management Basic Rich (session binding, routing)
Complexity Low High
Primary use Network access (WiFi, VPN) Mobile/LTE, IMS
Adoption Ubiquitous enterprise Telco/core network

RADIUS remains dominant for enterprise LAN/WiFi/VPN access. Diameter is primarily used in carrier-grade environments (3G/4G/5G core networks).

Protocol Mechanics

Message Types

Type Code Direction Description
Access-Request 1 NAS → Server Authentication request
Access-Accept 2 Server → NAS Authentication approved + authorization
Access-Reject 3 Server → NAS Authentication denied
Accounting-Request 4 NAS → Server Start/stop/interim accounting
Accounting-Response 5 Server → NAS Accounting acknowledgment
Access-Challenge 11 Server → NAS Challenge for EAP/CHAP
Status-Server 12 Both Server health check
Status-Client 13 Both Client status
Disconnect-Request 40 Server → NAS Terminate session (RFC 5176)
Disconnect-ACK 41 NAS → Server Session terminated
Disconnect-NAK 42 NAS → Server Cannot terminate
CoA-Request 43 Server → NAS Change authorization (RFC 5176)
CoA-ACK 44 NAS → Server Authorization changed
CoA-NAK 45 NAS → Server Cannot change

Authentication Flow

User              NAS (Client)           RADIUS Server
  |                    |                       |
  | Connect request    |                       |
  |------------------->|                       |
  |                    | Access-Request        |
  |                    |  (User-Name,          |
  |                    |   User-Password/CHAP, |
  |                    |   NAS-IP-Address,     |
  |                    |   Service-Type)       |
  |                    |---------------------->|
  |                    |                       |
  |                    |  Access-Challenge     |
  |                    |  (if EAP/CHAP)        |
  |                    |<----------------------|
  | Challenge          |                       |
  |<-------------------|                       |
  | Response           |                       |
  |------------------->|                       |
  |                    | Access-Request v2     |
  |                    |---------------------->|
  |                    |                       |
  |                    | Access-Accept         |
  |                    |  (Framed-IP-Address,  |
  |                    |   Session-Timeout,    |
  |                    |   Filter-ID,          |
  |                    |   VLAN assignment)    |
  |                    |<----------------------|
  | Access Granted     |                       |
  |<-------------------|                       |
  |                    | Accounting-Request    |
  |                    |  (Start)              |
  |                    |---------------------->|
  |                    | Accounting-Response   |
  |                    |<----------------------|

Packet Format

Every RADIUS packet has a fixed header followed by variable-length attributes:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─────────────────────────────────────────────────────────────────┤
│     Code (1)     │  Identifier (1) │          Length            │
├─────────────────────────────────────────────────────────────────┤
│                                                               │
│                     Authenticator (16 bytes)                   │
│                                                               │
│                                                               │
├─────────────────────────────────────────────────────────────────┤
│  Attributes (variable length) ...                             │
└─────────────────────────────────────────────────────────────────┘
  • Code: Message type (1–45)
  • Identifier: Matches requests to responses (sequence number)
  • Length: Total packet length (20–4096 bytes)
  • Authenticator: Request authenticator (MD5 hash of shared secret + packet)
  • Attributes: Type-Length-Value triplets

RADIUS Security Model

RADIUS uses a shared secret between client and server (not per-user passwords). The secret is used to:

  1. Encrypt the User-Password attribute (MD5 XOR, weak — vulnerable to dictionary attacks)
  2. Generate the Response Authenticator for packet integrity
  3. Hide attributes using Tunnel-Password encryption

Warning: The MD5-based encryption for User-Password is cryptographically weak. For production deployments, use EAP methods with TLS (EAP-TLS, PEAP) instead of PAP/CHAP, and tunnel RADIUS over RadSec or IPsec.

RADIUS Attributes

Standard Attributes Reference

Attributes are the heart of RADIUS — every policy decision is expressed as attribute-value pairs.

ID Name Type Description
1 User-Name String User login name
2 User-Password Encrypted Password (MD5 XOR)
3 CHAP-Password Binary CHAP response
4 NAS-IP-Address IP Address of the NAS
5 NAS-Port Integer Physical/virtual port on NAS
6 Service-Type Enum Framed, Login, Authenticate-Only, etc.
7 Framed-Protocol Enum PPP, SLIP, ARAP, etc.
8 Framed-IP-Address IP Assign this IP to user
9 Framed-IP-Netmask IP Subnet mask
10 Framed-Routing Enum Routing on interface
11 Filter-ID String ACL/filter name
12 Framed-MTU Integer MTU for this connection
13 Framed-Compression Enum Van-Jacobson, etc.
14 Login-IP-Host IP Host for telnet/rlogin
15 Login-Service Enum Telnet, Rlogin, etc.
16 Login-TCP-Port Integer TCP port for login
17 Reply-Message String Display to user
18 Callback-Number String Number for callback
19 Callback-ID String Callback identifier
20 Framed-Route String Route to add
22 Framed-IPX-Network IPX IPX network (legacy)
25 Class Binary Server-to-server opaque data
26 Vendor-Specific VSA Vendor extensions
27 Session-Timeout Integer Max session duration (seconds)
28 Idle-Timeout Integer Idle timeout (seconds)
29 Termination-Action Enum What to do on termination
30 Called-Station-ID String Phone number dialed (or BSSID)
31 Calling-Station-ID String Caller phone number (or MAC)
32 NAS-Identifier String Human-readable NAS name
33 Proxy-State Binary Proxy forwarding data
34 Login-LAT-Service String LAT service
35 Login-LAT-Node String LAT node
36 Login-LAT-Group Binary LAT group
37 Framed-AppleTalk-Link Integer AppleTalk link
38 Framed-AppleTalk-Network Integer AppleTalk network
39 Framed-AppleTalk-Zone String AppleTalk zone
40 Acct-Status-Type Enum Start, Stop, Interim, etc.
41 Acct-Delay-Time Integer Delay in seconds
42 Acct-Input-Octets Integer Bytes received
43 Acct-Output-Octets Integer Bytes sent
44 Acct-Session-Id String Unique session ID
45 Acct-Authentic Enum RADIUS, Local, Remote
46 Acct-Session-Time Integer Session duration
47 Acct-Input-Packets Integer Packets received
48 Acct-Output-Packets Integer Packets sent
49 Acct-Terminate-Cause Enum Why session ended
50 Acct-Multi-Session-Id String Multi-link session
51 Acct-Link-Count Integer Multi-link links
52 Acct-Input-Gigawords Integer High bits of input octets
53 Acct-Output-Gigawords Integer High bits of output octets
61 NAS-Port-Type Enum Ethernet, Wireless, ADSL, etc.
62 Port-Limit Integer Maximum ports
63 Login-LAT-Port String LAT port
64 Tunnel-Type Enum L2TP, PPTP, GRE, etc.
65 Tunnel-Medium-Type Enum IPv4, IPv6, etc.
66 Tunnel-Client-Endpoint String Tunnel client address
67 Tunnel-Server-Endpoint String Tunnel server address
68 Acct-Tunnel-Connection String Tunnel connection ID
69 Tunnel-Password Encrypted Tunnel auth password
70 ARAP-Password Binary AppleTalk password
71 ARAP-Features Binary AppleTalk features
72 ARAP-Zone-Access Enum AppleTalk zone access
73 ARAP-Security Integer AppleTalk security
74 ARAP-Security-Data String AppleTalk security data
75 Password-Retry Integer Retry attempts
76 Prompt Enum Echo/No-Echo
77 Connect-Info String Connection info
78 Configuration-Token Binary Configuration data
79 EAP-Message Binary EAP frame transport
80 Message-Authenticator Binary HMAC-MD5 integrity check
81 Tunnel-Private-Group-ID String Group/VLAN assignment
82 Tunnel-Assignment-ID String Tunnel assignment
83 Tunnel-Preference Integer Tunnel priority
84 ARAP-Challenge-Response Binary AppleTalk challenge
85 Acct-Interim-Interval Integer Interim update interval
86 Acct-Tunnel-Packets-Lost Integer Lost packets
87 NAS-Port-Id String Port name
88 Framed-Pool String IP pool name
90 Tunnel-Client-Auth-ID String Tunnel client auth
91 Tunnel-Server-Auth-ID String Tunnel server auth
95 NAS-IPv6-Address IPv6 NAS IPv6 address
97 Framed-Interface-Id Binary IPv6 interface ID
98 Framed-IPv6-Prefix IPv6 IPv6 prefix
99 Login-IPv6-Host IPv6 Login host IPv6
100 Framed-IPv6-Route String IPv6 route

Vendor-Specific Attributes (VSA)

Attribute 26 (Vendor-Specific) is the extensibility mechanism. The format is:

├─ Type=26 (1) ── Length (1) ── Vendor-ID (4) ── Vendor-Type (1) ── Vendor-Length (1) ── Value (varies) ─┤

Each vendor gets a private namespace identified by their IANA enterprise number. Common VSAs:

Cisco (Vendor-ID: 9)

Vendor-Type Name Example
1 Cisco-AVPair shell:priv-lvl=15
9 Cisco-NAS-Port Port identifier
25 Cisco-Account-Info Acct-Input-Octets
41 Cisco-CLI-Line Line authorization

Cisco AV-Pairs are the most flexible — they can encode almost any Cisco device configuration:

# Privilege level authorization
Cisco-AVPair = "shell:priv-lvl=15"

# Download ACL
Cisco-AVPair = "ip:inacl#1=permit tcp any any eq 80"
Cisco-AVPair = "ip:inacl#2=deny ip any any"

# VLAN assignment
Cisco-AVPair = "tunnel-private-group-id=100"

# URL redirect (for web auth)
Cisco-AVPair = "url-redirect=https://captive.example.com"
Cisco-AVPair = "url-redirect-acl=redirect-acl"

Microsoft (Vendor-ID: 311)

Name Type Description
MS-CHAP-Response 1 MS-CHAP response
MS-CHAP-Error 2 Error message
MS-CHAP-CPW-1 3 Change password
MS-CHAP-Domain 10 User domain
MS-MPPE-Encryption-Types 11 MPPE encryption
MS-MPPE-Encryption-Policy 12 MPPE policy
MS-MPPE-Send-Key 16 Session key
MS-MPPE-Recv-Key 17 Session key
MS-Quarantine-IPFilter 22 NAP filter
MS-Quarantine-Session-Timeout 23 NAP timeout
MS-Quarantine-Grade 25 NAP grade

Juniper (Vendor-ID: 4874)

# Static IP assignment
Juniper-Framed-IP-Pool = "pool-name"

# Routing instance
Juniper-Local-Routing-Instance = "customer-a"

# CoS parameters  
Juniper-Juniper-Service-Name = "qos-profile"

EAP Methods

RADIUS transports EAP (Extensible Authentication Protocol) frames inside EAP-Message attributes (ID 79). The Message-Authenticator attribute (ID 80) provides integrity protection for the entire RADIUS packet.

EAP-TLS

EAP-TLS uses PKI certificates for mutual authentication. Both client and server present certificates. This is the most secure EAP method — no passwords, no shared secrets (beyond the CA trust chain).

# FreeRADIUS EAP-TLS configuration
# /etc/freeradius/3.0/mods-enabled/eap
eap {
    default_eap_type = tls
    timer_expire = 60
    ignore_unknown_eap_types = no
    cisco_accounting_username_bug = no
    max_sessions = 4096

    tls {
        certdir = /etc/freeradius/3.0/certs
        private_key_password = 
        private_key_file = ${certdir}/server.key
        certificate_file = ${certdir}/server.pem
        ca_file = ${certdir}/ca.pem
        dh_file = ${certdir}/dh
        random_file = /dev/urandom
        fragment_size = 1024
        include_length = yes
        check_crl = yes
        cipher_list = "HIGH:+3DES"
        cipher_server_preference = no
        ecdh_curve = "prime256v1"
        cache {
            enable = yes
            lifetime = 3600
            max_entries = 255
        }
        verify {
            # Require client certificate
            require_cert = yes
        }
        ocsp {
            enable = yes
            override_cert_url = no
            ocsp_default_url = "http://ocsp.example.com"
            softfail = no
        }
    }
}

When to use EAP-TLS:

  • Corporate-managed devices (IT can deploy certificates)
  • High-security environments (government, finance)
  • Environments without AD/domain password infrastructure

PEAP (Protected EAP)

PEAP creates a TLS tunnel, then authenticates the user inside that tunnel using MS-CHAPv2. The server has a certificate; the client authenticates via username/password inside the encrypted tunnel.

# FreeRADIUS PEAP configuration
eap {
    default_eap_type = peap

    peap {
        default_eap_type = mschapv2
        copy_request_to_tunnel = no
        use_tunneled_reply = no
        proxy_tunneled_request_as_eap = yes
        virtual_server = "inner-tunnel"
        
        tls {
            # Same TLS config as EAP-TLS
            certdir = /etc/freeradius/3.0/certs
            private_key_file = ${certdir}/server.key
            certificate_file = ${certdir}/server.pem
            ca_file = ${certdir}/ca.pem
        }
    }
}

When to use PEAP:

  • User-supplied devices (BYOD)
  • Domain-joined Windows machines (native support)
  • Environments with Active Directory password authentication

EAP-TTLS

Similar to PEAP — creates a TLS tunnel, then authenticates inside it. More flexible than PEAP (supports PAP, CHAP, MS-CHAPv2, or any inner method), but less native OS support.

eap {
    default_eap_type = ttls

    ttls {
        default_eap_type = mschapv2
        copy_request_to_tunnel = no
        use_tunneled_reply = no
        virtual_server = "inner-tunnel"
        
        tls {
            certdir = /etc/freeradius/3.0/certs
            private_key_file = ${certdir}/server.key
            certificate_file = ${certdir}/server.pem
            ca_file = ${certdir}/ca.pem
        }
    }
}

EAP-FAST

Cisco’s alternative to PEAP/TTLS. Uses a shared secret (PAC — Protected Access Credential) instead of a server certificate. Less common today.

Comparison

Method Server Cert Client Cert Password OS Support Security
EAP-TLS Required Required No All (config needed) Highest
PEAP Required No Yes (MSCHAPv2) Native Windows, macOS, Android High
EAP-TTLS Required No Yes (any) Most High
EAP-FAST Optional Optional Yes Cisco-focused Medium
LEAP No No Yes (MSCHAPv2) Legacy Weak

FreeRADIUS Configuration

FreeRADIUS is the most widely deployed open-source RADIUS server. This section covers production-grade configuration.

Installation

# Ubuntu/Debian
sudo apt install freeradius freeradius-ldap freeradius-mysql

# RHEL/Fedora
sudo dnf install freeradius freeradius-ldap freeradius-mysql

# Verify installation
sudo radiusd -X  # Debug mode (foreground)

Client Configuration

RADIUS clients (NAS devices) are configured in /etc/freeradius/3.0/clients.conf:

# /etc/freeradius/3.0/clients.conf
client wifi-controller {
    ipaddr = 10.0.10.0/24
    secret = secure-shared-secret-here
    shortname = wifi-controller
    require_message_authenticator = yes
    nas_type = cisco
}

client vpn-gateway {
    ipaddr = 10.0.20.5
    secret = another-secret
    shortname = vpn-gw
    nas_type = other
}

# Default client (last resort)
client localhost {
    ipaddr = 127.0.0.1
    secret = testing123  # CHANGE FOR PRODUCTION
    require_message_authenticator = no
}

LDAP Backend

Authenticate users against an LDAP directory (Active Directory, OpenLDAP, FreeIPA):

# /etc/freeradius/3.0/mods-enabled/ldap
ldap {
    server = 'dc01.example.com'
    port = 389
    identity = 'cn=radius,cn=Users,dc=example,dc=com'
    password = 'service-account-password'
    base_dn = 'dc=example,dc=com'
    
    # Active Directory specific
    user {
        base_dn = 'cn=Users,dc=example,dc=com'
        filter = "(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})"
        scope = 'sub'
    }
    
    group {
        base_dn = 'cn=Users,dc=example,dc=com'
        filter = '(objectClass=group)'
        membership_attribute = 'memberOf'
    }
    
    # TLS
    start_tls = yes
    tls_cacertfile = /etc/ssl/certs/ca.pem
    tls_certfile = /etc/ssl/certs/server.pem
    tls_keyfile = /etc/ssl/private/server.key
}

SQL Backend

For environments without LDAP, or when you need fine-grained attribute control:

# /etc/freeradius/3.0/mods-enabled/sql
sql {
    driver = "rlm_sql_mysql"
    dialect = "mysql"
    
    server = "localhost"
    port = 3306
    login = "radius"
    password = "db-password"
    
    radius_db = "radius"
    acct_table1 = "radacct"
    authcheck_table = "radcheck"
    authreply_table = "radreply"
    groupcheck_table = "radgroupcheck"
    groupreply_table = "radgroupreply"
    usergroup_table = "radusergroup"
}

Create the database schema:

CREATE DATABASE radius;
GRANT ALL ON radius.* TO 'radius'@'localhost' IDENTIFIED BY 'db-password';

USE radius;

CREATE TABLE radcheck (
    id int(11) NOT NULL AUTO_INCREMENT,
    username varchar(64) NOT NULL,
    attribute varchar(64) NOT NULL,
    op char(2) NOT NULL DEFAULT '==',
    value varchar(253) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE radreply (
    id int(11) NOT NULL AUTO_INCREMENT,
    username varchar(64) NOT NULL,
    attribute varchar(64) NOT NULL,
    op char(2) NOT NULL DEFAULT '=',
    value varchar(253) NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE radacct (
    radacctid bigint(20) NOT NULL AUTO_INCREMENT,
    acctsessionid varchar(64) NOT NULL,
    username varchar(64) NOT NULL,
    nasipaddress varchar(15) NOT NULL,
    acctstarttime datetime DEFAULT NULL,
    acctstoptime datetime DEFAULT NULL,
    acctinputoctets bigint(20) DEFAULT NULL,
    acctoutputoctets bigint(20) DEFAULT NULL,
    PRIMARY KEY (radacctid)
);

# Add users
INSERT INTO radcheck (username, attribute, op, value) 
VALUES ('john', 'Cleartext-Password', ':=', 'password123');

# Add authorization attributes
INSERT INTO radreply (username, attribute, op, value)
VALUES ('john', 'Framed-IP-Address', '=', '10.10.10.100'),
       ('john', 'Session-Timeout', '=', '28800');

Realm Configuration

Realms allow routing different user groups to different backends:

# /etc/freeradius/3.0/proxy.conf
realm example.com {
    type = radius
    authhost = radius.example.com:1812
    accthost = radius.example.com:1813
    secret = inter-realm-secret
}

realm local {
    type = radius
    authhost = localhost:18120
    accthost = localhost:18130
    secret = local-secret
}

# Default realm
realm NULL {
    type = radius
    authhost = localhost:1812
    accthost = localhost:1813
    secret = testing123
}

Virtual Server Configuration

The main policy file is /etc/freeradius/3.0/sites-enabled/default:

server default {
    listen {
        type = auth
        ipaddr = *
        port = 0
    }
    
    listen {
        type = acct
        ipaddr = *
        port = 0
    }
    
    authorize {
        preprocess
        chap
        mschap
        suffix
        eap {
            ok = return
        }
        files
        sql
        ldap
        expiration
        logintime
        pap
    }
    
    authenticate {
        Auth-Type PAP {
            pap
        }
        Auth-Type CHAP {
            chap
        }
        Auth-Type MS-CHAP {
            mschap
        }
        eap
    }
    
    preacct {
        preprocess
        acct_unique
        suffix
        files
    }
    
    accounting {
        detail
        sql
        exec
        attr_filter.accounting_response
    }
    
    session {
        radutmp
        sql
    }
    
    post-auth {
        Post-Auth-Type REJECT {
            attr_filter.access_reject
        }
    }
}

VLAN Assignment

Assign users to specific VLANs via RADIUS attributes:

# For any user matching a specific group
# /etc/freeradius/3.0/mods-config/sql/main/mysql/queries.conf
groupreply {
    Tunnel-Type = "VLAN"
    Tunnel-Medium-Type = "IEEE-802"
    Tunnel-Private-Group-ID = "100"
}

Or per-user in SQL:

INSERT INTO radreply (username, attribute, op, value)
VALUES ('john', 'Tunnel-Type', '=', '13'),
       ('john', 'Tunnel-Medium-Type', '=', '6'),
       ('john', 'Tunnel-Private-Group-ID', '=', '100');

Change of Authorization (CoA) and Disconnect

RFC 5176 defines two real-time session management extensions:

Disconnect Messages (DM): Force-terminate an active session.

Change of Authorization (CoA): Modify session parameters mid-session — change VLAN, apply new ACL, limit bandwidth.

# Initiate a disconnect from the RADIUS server
# Using radclient (FreeRADIUS)
echo "User-Name = john, Acct-Session-Id = \"12345678\"" | \
    radclient -r 1 -t 3 10.0.10.5:3799 disconnect shared-secret

# Change authorization (apply new VLAN)
echo "User-Name = john, Tunnel-Private-Group-ID = \"200\"" | \
    radclient -r 1 -t 3 10.0.10.5:3799 coa shared-secret

Use cases:

  • Quarantine a compromised device (CoA → VLAN 999/guest)
  • Re-authenticate user after posture change
  • Rate-limit abusers mid-session
  • Policy-based VLAN steering based on real-time context

Python RADIUS Client (pyrad)

A complete Python implementation for RADIUS authentication and accounting:

pip install pyrad
#!/usr/bin/env python3
"""RADIUS authentication and accounting client using pyrad."""

import pyrad.packet
from pyrad.client import Client, Timeout
from pyrad.dictionary import Dictionary
import socket
import sys


class RadiusClient:
    def __init__(self, server, secret, auth_port=1812, acct_port=1813,
                 timeout=5, retries=3):
        self.client = Client(
            server=server,
            secret=secret.encode(),
            dict=Dictionary("dictionary"),
            auth_port=auth_port,
            acct_port=acct_port,
            timeout=timeout,
            retries=retries,
        )

    def authenticate(self, username, password, nas_ip=None,
                     nas_identifier="NAS-01", service_type=1,
                     nas_port_type=5):
        """Authenticate a user. Returns (success, reply_attributes)."""
        req = self.client.CreateAuthPacket(
            code=pyrad.packet.AccessRequest,
            User_Name=username,
            NAS_Identifier=nas_identifier,
            Service_Type=service_type,
            NAS_Port_Type=nas_port_type,
        )
        req["User-Password"] = req.PwCrypt(password)
        req["NAS-IP-Address"] = nas_ip or socket.gethostbyname(
            socket.gethostname()
        )

        try:
            reply = self.client.SendPacket(req)
        except Timeout:
            return False, {"error": "RADIUS server timeout"}
        except pyrad.client.ServerPacketError as e:
            return False, {"error": str(e)}

        if reply.code == pyrad.packet.AccessAccept:
            attrs = self._parse_reply(reply)
            return True, attrs
        elif reply.code == pyrad.packet.AccessReject:
            return False, {"error": "Access denied"}
        elif reply.code == pyrad.packet.AccessChallenge:
            return False, {"error": "Challenge required", "eap": True}
        return False, {"error": f"Unexpected code: {reply.code}"}

    def accounting_start(self, username, session_id, nas_ip=None,
                         nas_identifier="NAS-01"):
        """Send accounting start packet."""
        req = self.client.CreateAcctPacket(
            code=pyrad.packet.AccountingRequest,
            User_Name=username,
            NAS_Identifier=nas_identifier,
            Acct_Status_Type=1,  # Start
            Acct_Session_Id=session_id,
        )
        req["NAS-IP-Address"] = nas_ip or socket.gethostbyname(
            socket.gethostname()
        )
        try:
            reply = self.client.SendPacket(req)
            return reply.code == pyrad.packet.AccountingResponse
        except Timeout:
            return False

    def accounting_stop(self, username, session_id, session_time=0,
                        input_octets=0, output_octets=0,
                        terminate_cause=1):
        """Send accounting stop packet."""
        req = self.client.CreateAcctPacket(
            code=pyrad.packet.AccountingRequest,
            User_Name=username,
            Acct_Status_Type=2,  # Stop
            Acct_Session_Id=session_id,
            Acct_Session_Time=session_time,
            Acct_Input_Octets=input_octets,
            Acct_Output_Octets=output_octets,
            Acct_Terminate_Cause=terminate_cause,
        )
        try:
            reply = self.client.SendPacket(req)
            return reply.code == pyrad.packet.AccountingResponse
        except Timeout:
            return False

    def _parse_reply(self, reply):
        """Convert RADIUS reply attributes to a dict."""
        attrs = {}
        for attr in reply.keys():
            try:
                val = reply[attr]
                if isinstance(val, list):
                    val = [str(v) for v in val]
                else:
                    val = str(val)
                attrs[attr] = val
            except Exception:
                continue
        return attrs


if __name__ == "__main__":
    client = RadiusClient(
        server="127.0.0.1",
        secret="testing123",
    )

    success, result = client.authenticate("john", "password123")
    if success:
        print(f"Authenticated: {result}")
    else:
        print(f"Failed: {result}")
        sys.exit(1)

Accounting Deep Dive

RADIUS accounting tracks user sessions for billing, auditing, and capacity planning.

Accounting Flow

1. NAS sends Accounting-Request (Start)    — session begins
2. Server responds Accounting-Response     — acknowledged
3. NAS sends Accounting-Request (Interim)  — periodic (every N minutes)
4. Server responds Accounting-Response     — acknowledged
5. NAS sends Accounting-Request (Stop)     — session ends
6. Server responds Accounting-Response     — acknowledged

Accounting Attributes

# Typical accounting packet
Acct-Status-Type = Interim-Update (3)
Acct-Session-Id = "550E8400-E29B-41D4-A716-446655440000"
Acct-Input-Octets = 1048576
Acct-Output-Octets = 2097152
Acct-Input-Packets = 15000
Acct-Output-Packets = 25000
Acct-Session-Time = 3600
Acct-Input-Gigawords = 0
Acct-Output-Gigawords = 0
NAS-IP-Address = 10.0.10.5
NAS-Identifier = "wifi-controller-01"
User-Name = "john"

FreeRADIUS Accounting Queries

-- Get total data usage per user (last 30 days)
SELECT username,
       SUM(acctinputoctets + acctoutputoctets) AS total_bytes,
       ROUND(SUM(acctinputoctets + acctoutputoctets) / 1073741824, 2) AS total_gb,
       COUNT(*) AS sessions,
       ROUND(SUM(acctsessiontime) / 3600, 1) AS total_hours
FROM radacct
WHERE acctstarttime >= NOW() - INTERVAL 30 DAY
GROUP BY username
ORDER BY total_bytes DESC;

-- Active sessions right now
SELECT username, nasipaddress, acctstarttime, acctinputoctets, acctoutputoctets
FROM radacct
WHERE acctstoptime IS NULL;

-- Peak concurrent usage by hour
SELECT DATE_FORMAT(acctstarttime, '%Y-%m-%d %H:00:00') AS hour_slot,
       COUNT(*) AS concurrent
FROM radacct
WHERE acctstarttime >= NOW() - INTERVAL 7 DAY
GROUP BY hour_slot
ORDER BY concurrent DESC
LIMIT 10;

RadSec (RADIUS over TLS)

Standard RADIUS sends packets over UDP with only MD5-based integrity — no encryption, no confidentiality. RadSec (RFC 6614) tunnels RADIUS over TLS, solving:

  • Confidentiality: Full encryption of all attributes (including User-Password)
  • Integrity: TLS-level message authentication
  • Reliability: TCP retransmission instead of application-level retry
  • Dynamic peers: No need to pre-configure shared secrets for inter-domain roaming

FreeRADIUS RadSec Configuration

# /etc/freeradius/3.0/sites-enabled/tls
listen {
    type = auth
    proto = tcp
    tls {
        certdir = /etc/freeradius/3.0/certs
        private_key_file = ${certdir}/server.key
        certificate_file = ${certdir}/server.pem
        ca_file = ${certdir}/ca.pem
    }
}

# RadSec client
client radsec-peer {
    ipaddr = 10.0.30.0/24
    proto = tls
    secret =  # Not needed when using TLS client certs
    shortname = radsec-peer
}

When to Use RadSec

  • RADIUS traffic across the internet or untrusted networks
  • Federated eduroam deployments
  • Multi-site enterprise with inter-controller roaming
  • Compliance requirements (PCI-DSS, HIPAA, FedRAMP)

Troubleshooting

Debug Mode

FreeRADIUS’s -X flag runs in full debug mode — the most valuable troubleshooting tool:

# Stop the service, run in foreground debug
sudo systemctl stop freeradius
sudo radiusd -X

Look for these key patterns in the debug output:

# Successful authentication
rlm_pap: Login OK: [john] (from client wifi-controller)
Login OK: [john] (from client wifi-controller port 0)
# Attributes sent back:
Sending Access-Accept of id 123 to 10.0.10.5 port 1645
    Framed-IP-Address = 10.10.10.100
    Session-Timeout = 28800

# Failed authentication
rlm_pap: Login incorrect: [john] (from client wifi-controller)
Sending Access-Reject of id 123 to 10.0.10.5 port 1645
    Reply-Message = "Authentication failed"

Common Issues

NAS sends request but gets no response

# Check firewall
sudo iptables -L -n | grep 1812
sudo ufw status

# Verify client is configured
grep -A5 "client wifi" /etc/freeradius/3.0/clients.conf

# Test packet reachability
radtest john password123 127.0.0.1 0 testing123

EAP authentication fails

# Common causes:
# 1. Certificate issues — check expiration
openssl x509 -in /etc/freeradius/3.0/certs/server.pem -text -noout

# 2. Wrong EAP type — ensure client and server agree
# 3. Inner method mismatch — PEAP expects MSCHAPv2 inside
# 4. TLS version mismatch — configure ciphers
tail -f /var/log/freeradius/radius.log | grep -i "eap\|tls\|ssl"

Accounting data missing

# Check if accounting is enabled in the NAS
# Verify SQL connection
sudo radiusd -X | grep sql

# Test accounting
radacct -d /etc/freeradius/3.0/ -n 1

Shared secret mismatch

# FreeRADIUS debug output:
# "Ignoring request to authentication port"
# "Request from unknown client IP"

# Fix: verify secret in clients.conf matches NAS config
# Always restart after changing secrets

Fragmentation issues with EAP

Large EAP messages (especially certificate chains) may exceed UDP packet limits:

# Configure EAP fragment size
eap {
    tls {
        fragment_size = 1024  # Max fragment size
        include_length = yes  # Include total length
    }
}

Monitoring Commands

# Check RADIUS server status
sudo systemctl status freeradius
sudo radiusd -C  # Configuration check

# View live authentication attempts
sudo tail -f /var/log/freeradius/radius.log

# Count recent authentications
grep -c "Login OK" /var/log/freeradius/radius.log
grep -c "Login incorrect" /var/log/freeradius/radius.log

# Check active sessions
sudo radwho

# Verify packet counters
sudo radiusd -C -l stdout

Security Considerations

Known Weaknesses

  1. User-Password encryption: Uses MD5 XOR with the shared secret — reversible if you know the secret, and susceptible to offline dictionary attacks on captured packets.

  2. No built-in confidentiality: Attributes like Tunnel-Password, MS-MPPE-Send-Key, and MS-MPPE-Recv-Key are encrypted with weak algorithms.

  3. Request authentication not mandatory: The Message-Authenticator attribute (ID 80) is optional in older implementations — without it, packets can be forged.

  4. UDP amplification: RADIUS over UDP can be used in reflection attacks if the server is exposed to the internet.

Hardening Checklist

  • Use EAP-TLS or PEAP instead of PAP/CHAP (passwords never transmitted in the clear)
  • Require Message-Authenticator on all packets (require_message_authenticator = yes in clients.conf)
  • Tunnel RADIUS over RadSec or IPsec for inter-site traffic
  • Rotate shared secrets periodically (store in a secret manager)
  • Restrict RADIUS server access to NAS IP ranges only
  • Use strong secrets (>20 characters, random)
  • Enable TLS for LDAP backend connections
  • Log all authentication attempts and monitor for anomalies
  • Deploy redundant RADIUS servers (active-active with load balancing)
  • Disable Status-Server if not needed (reduces reconnaissance surface)

Network Segmentation

                    Internet
                        │
              ┌─────────▼─────────┐
              │  Firewall (deny   │
              │  RADIUS from WAN) │
              └─────────┬─────────┘
                        │
        ┌───────────────┼───────────────┐
        │               │               │
  ┌─────▼─────┐   ┌────▼────┐   ┌──────▼─────┐
  │ RADIUS    │   │ RADIUS  │   │ LDAP / AD   │
  │ Server 1  │   │ Server 2│   │ Domain Ctrl │
  │ (mgmt)    │   │ (mgmt)  │   │ (mgmt)      │
  └─────┬─────┘   └────┬────┘   └──────┬──────┘
        │               │               │
  ┌─────▼───────────────▼───────────────▼──────┐
  │            Management VLAN (10.x.x.x)       │
  └─────────────────────────────────────────────┘
        │
  ┌─────▼─────┐
  │ NAS/WiFi  │
  │ Controller│
  └───────────┘

Performance Tuning

FreeRADIUS Performance

# /etc/freeradius/3.0/radiusd.conf
thread_pool {
    start_servers = 5
    max_servers = 32
    min_spare_servers = 3
    max_spare_servers = 10
    max_requests_per_server = 0
    autoscale = yes
}

# Increase UDP buffer sizes
# /etc/sysctl.d/99-radius.conf
net.core.rmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_max = 16777216
net.core.wmem_default = 1048576

Benchmarking

# Install radclient for benchmarking
# Test authentication throughput
time seq 1 1000 | parallel -j 10 \
    'echo "User-Name = test{}-password:testing123" | \
     radclient -r 1 -t 2 127.0.0.1:1812 auth testing123'

# Monitor server performance
sudo radiusd -C -l stdout | grep -e "requests" -e "average"

Expected performance (modern hardware):

  • PAP authentication: 5,000–10,000 req/s per core
  • PEAP/MSCHAPv2: 500–1,000 auth/s per core
  • EAP-TLS (full handshake): 100–300 auth/s per core
  • Accounting: 10,000–20,000 req/s per core

Conclusion

RADIUS remains the foundation of enterprise network access control in 2026. Despite its age, the protocol’s extensibility via attributes, support for modern EAP methods, and proxy architecture keep it relevant for WiFi authentication, VPN access, network device administration, and IoT onboarding.

Key takeaways:

  • Use EAP methods with TLS — PAP/CHAP passwords are transmitted with weak encryption
  • Deploy redundantly — RADIUS is a single point of failure; use at least two servers
  • Monitor aggressively — authentication failures often indicate attacks or misconfiguration
  • Tunnel RADIUS over RadSec/IPsec for inter-site traffic and cloud-based RADIUS services
  • Invest in FreeRADIUS — it’s the most capable open-source RADIUS server, with LDAP, SQL, EAP, CoA, and proxy support built in

Resources

Comments

Share this article

Scan to read on mobile