Configuring RADIUS Admin Auth on FortiGate SD-WAN: RBAC and Three User Profiles (Part 2 of 2)

Part 2 of a two-part series. Part 1 covered why RADIUS is the right AAA choice for most FortiGate SD-WAN deployments, the available RADIUS servers, and when TACACS+ is still defensible. This post is the implementation — what you actually type into the box, the three admin profiles that map to common roles, and the verification commands you’ll wish you’d had open during the first failed login.

The reference design uses Microsoft NPS as the RADIUS server (against on-prem AD) with Fortinet VSAs carrying the access-profile name. The same shape works against FortiAuthenticator, FreeRADIUS, or any RADIUS server that can return VSAs — only the server-side config changes.

Reference architecture

ComponentDetail
FortiGateBranch SD-WAN edge, FortiOS 7.6.x, management lives in VRF 20
RADIUS server primaryNPS-01, 10.20.50.10, in the data centre
RADIUS server secondaryNPS-02, 10.20.50.11, same DC, different rack
AD groupsFGT-NetEngineers, FGT-NOC, FGT-Auditors
FortiGate admin profilesengineer-admin, noc-operator, auditor-readonly
Shared secretStored encrypted on FortiGate, vaulted on the NPS side
Source IP for RADIUSThe mgmt VRF interface; NPS sees branch loopbacks
MFAAzure MFA NPS extension, push to Microsoft Authenticator

The three personas:

PersonaAD groupFortiGate profileWhat they need to doWhat they must not do
Senior network engineerFGT-NetEngineersengineer-adminFull read/write across system, network, security, VPN, SD-WAN, monitoring. Reboot.Nothing — this is the privileged role.
NOC operatorFGT-NOCnoc-operatorRead everything. Enable/disable existing policies. Bounce IPsec tunnels and interfaces. Take packet captures. Restart routing daemon.Edit security profiles, change system config, manage admins, modify SD-WAN rules.
Compliance auditorFGT-Auditorsauditor-readonlyRead config, view logs, list certificates, view policies and address objects.Make any change. View VPN pre-shared keys or other secrets in cleartext.

Step 1 — Define the admin profiles (RBAC)

Profiles are the FortiOS authorization model. Each profile is a matrix of features × none|read|read-write|custom. Profiles are evaluated locally — RADIUS only tells the FortiGate which profile to apply.

Profile A — engineer-admin

A near-superuser, but not super_admin. Keeping engineers out of super_admin means the audit trail shows a named profile, not the all-powerful built-in, and prevents accidental edits to admin accounts and HA settings without a deliberate elevation step.

config system accprofile
    edit "engineer-admin"
        set scope vdom
        set comments "Senior network engineer — full operational control, no admin/HA mgmt"
        set secfabgrp read-write
        set ftviewgrp read-write
        set authgrp read-write
        set sysgrp read-write
        set netgrp read-write
        set loggrp read-write
        set fwgrp read-write
        set vpngrp read-write
        set utmgrp read-write
        set wanoptgrp read-write
        set wifi read-write
        set system-diagnostics enable
        set system-execute-ssh enable
        set system-execute-telnet enable
        # Explicitly NOT super_admin — admin/HA mgmt is excluded:
        set admintimeout-override enable
        set admintimeout 30
    next
end

Profile B — noc-operator

Read across the box, plus a tightly scoped set of write operations. The trick on FortiOS is that read-write is per feature group, but you can drop into custom on fwgrp (firewall) and loggrp (log & report) to grant policy-level enable/disable without policy creation/deletion.

config system accprofile
    edit "noc-operator"
        set scope vdom
        set comments "NOC operator — read everywhere, bounce tunnels/interfaces, toggle policies"
        set secfabgrp read
        set ftviewgrp read
        set authgrp read
        set sysgrp read
        set netgrp custom
        set loggrp custom
        set fwgrp custom
        set vpngrp custom
        set utmgrp read
        set wanoptgrp read
        set wifi read
        set system-diagnostics enable
        set system-execute-ssh disable
        set system-execute-telnet disable
        config netgrp-permission
            set cfg read
            set packet-capture read-write
            set route-cfg read
        end
        config fwgrp-permission
            set policy custom
            set address read
            set service read
            set schedule read
            set others read
        end
        config loggrp-permission
            set config read
            set data-access read-write
            set report-access read
            set threat-weight read
        end
        config vpngrp-permission
            set ipsec custom
            set ssl read
        end
    next
end

The custom settings on fwgrp.policy, vpngrp.ipsec, and netgrp.packet-capture give the NOC the granular powers they need (toggle a policy, bounce an IPsec phase-2, run a sniffer) without giving them the ability to create or delete policies, change security profiles, or alter system config.

Profile C — auditor-readonly

Pure read across everything that produces evidence — config, logs, policies, certificates — and explicit no on anything that could be a privacy or secrets-disclosure risk.

config system accprofile
    edit "auditor-readonly"
        set scope vdom
        set comments "Compliance auditor — read-only, no secret disclosure"
        set secfabgrp read
        set ftviewgrp read
        set authgrp read
        set sysgrp read
        set netgrp read
        set loggrp read
        set fwgrp read
        set vpngrp read
        set utmgrp read
        set wanoptgrp read
        set wifi read
        set system-diagnostics disable
        set system-execute-ssh disable
        set system-execute-telnet disable
    next
end

system-diagnostics disable is the important one — without it an auditor with read on sysgrp could still run diagnose hardware deviceinfo, diagnose debug crashlog, etc. With diagnostics disabled, the profile is truly observational. The vpngrp = read line means the auditor can see that an IPsec tunnel exists and which proposals it carries, but FortiOS will mask the pre-shared key and certificate private keys in the rendered config.

Step 2 — Configure the RADIUS server entry

config user radius
    edit "NPS-PRIMARY"
        set server "10.20.50.10"
        set secret ENC <vaulted-secret>
        set auth-type ms_chap_v2
        set radius-port 1812
        set source-ip "172.20.10.1"
        set interface-select-method specify
        set interface "mgmt"
        set timeout 5
        set rsso disable
        set h3c-compatibility disable
    next
    edit "NPS-SECONDARY"
        set server "10.20.50.11"
        set secret ENC <vaulted-secret>
        set auth-type ms_chap_v2
        set radius-port 1812
        set source-ip "172.20.10.1"
        set interface-select-method specify
        set interface "mgmt"
        set timeout 5
    next
end

Notes that aren’t obvious from the CLI:

  • auth-type ms_chap_v2 is required to talk to NPS by default. NPS will reject PAP unless you’ve explicitly allowed it, which you should not.
  • source-ip and interface-select-method specify pin the RADIUS conversation to the management VRF. This matters when the FortiGate has multiple VRFs or multiple uplinks; without it FortiOS picks the egress IP based on the routing table, which can land in the wrong VRF and make NPS see source addresses you don’t expect.
  • MFA timeout — if you’re using Azure MFA push, increase timeout to 30+ seconds. The default 5 will fail before the user has tapped Approve on their phone.
  • The secondary server is configured as a separate user radius entry rather than two servers under one entry. This gives you per-server statistics (diag test authserver radius NPS-PRIMARY <user> <pass>) and makes it easier to put each in different user groups for failover testing.

Step 3 — Map RADIUS groups to FortiGate profiles via VSA

This is where Part 1’s “RADIUS authorization is in the Access-Accept” comes home to roost. There are two patterns; pick one and don’t mix them on the same box.

Pattern A — VSA-based (preferred)

The RADIUS server returns the Fortinet-Access-Profile VSA (vendor ID 12356, attribute 6) containing the literal admin-profile name. FortiOS reads that VSA and assigns the matching profile.

On FortiGate:

config user group
    edit "fgt-radius-admins"
        set member "NPS-PRIMARY" "NPS-SECONDARY"
    next
end

config system admin
    edit "remote-admin"
        set remote-auth enable
        set accprofile "no_access"           ; default-deny if no VSA returned
        set vdom "root"
        set wildcard enable
        set remote-group "fgt-radius-admins"
    next
end

On NPS, three Network Policies, one per AD group, each returning a different VSA value:

Network PolicyAD group conditionVSA returned
FGT-NetEngineersMember of FGT-NetEngineersFortinet-Access-Profile = engineer-admin
FGT-NOCMember of FGT-NOCFortinet-Access-Profile = noc-operator
FGT-AuditorsMember of FGT-AuditorsFortinet-Access-Profile = auditor-readonly

Adding the VSA in NPS:

  1. Network Policies → properties → Settings → RADIUS Attributes → Vendor Specific → Add → Custom.
  2. Vendor code: 12356 (Fortinet).
  3. Attribute number: 6.
  4. Format: String. Value: engineer-admin (or whichever profile).

Why this is the better pattern: one wildcard admin entry, one user-group entry, all the policy lives on the RADIUS server. Adding a fourth role doesn’t require any FortiGate change — just a new admin profile and a new NPS network policy.

Pattern B — One wildcard admin per group

Skip the VSA. Instead, create one user group per role and one wildcard admin per group, hard-coding the access profile in the FortiGate config.

config user group
    edit "fgt-engineers"
        set member "NPS-PRIMARY" "NPS-SECONDARY"
        config match
            edit 1
                set server-name "NPS-PRIMARY"
                set group-name "FGT-NetEngineers"
            next
        end
    next
    edit "fgt-noc"
        set member "NPS-PRIMARY" "NPS-SECONDARY"
        config match
            edit 1
                set server-name "NPS-PRIMARY"
                set group-name "FGT-NOC"
            next
        end
    next
    edit "fgt-auditors"
        set member "NPS-PRIMARY" "NPS-SECONDARY"
        config match
            edit 1
                set server-name "NPS-PRIMARY"
                set group-name "FGT-Auditors"
            next
        end
    next
end

config system admin
    edit "remote-engineer"
        set remote-auth enable
        set accprofile "engineer-admin"
        set vdom "root"
        set wildcard enable
        set remote-group "fgt-engineers"
    next
    edit "remote-noc"
        set remote-auth enable
        set accprofile "noc-operator"
        set vdom "root"
        set wildcard enable
        set remote-group "fgt-noc"
    next
    edit "remote-auditor"
        set remote-auth enable
        set accprofile "auditor-readonly"
        set vdom "root"
        set wildcard enable
        set remote-group "fgt-auditors"
    next
end

The config match block uses NPS to send the AD group name as a RADIUS attribute (Class attribute is the typical carrier, or Filter-Id). This works but ties role membership to FortiGate config — every new role needs a FortiGate change.

For an SD-WAN fleet of more than a handful of boxes, Pattern A wins because it externalises the role definition entirely. Add a new profile once on a golden config, push via FortiManager, and from that day forward role assignment is purely an AD-group + NPS-policy change.

Step 4 — Local break-glass admin

Before you cut over, make absolutely sure you have a working local account that is not dependent on RADIUS:

config system admin
    edit "breakglass"
        set accprofile "super_admin"
        set vdom "root"
        set password ENC <long-vaulted-password>
        set trusthost1 10.20.10.0 255.255.255.0    ; jump-host subnet only
        set two-factor email
        set email-to "[email protected]"
    next
end

The trusthost lock keeps this account unusable from anywhere outside the jump-host subnet. The password lives in a password vault, rotated quarterly, and the audit trail picks up any login as a breakglass event. If RADIUS dies for any reason — NPS down, certificate expired, MFA outage — this account is how you get back in. Skipping it is how people get locked out of their own gear.

Step 5 — Order RADIUS in the admin login chain

config system global
    set admin-https-redirect enable
    set admin-port 443
    set admintimeout 30
end

config system admin-radius        ; (where supported in your FortiOS version)
    set status enable
end

FortiOS evaluates admins in this order:

  1. Local admin with matching username → use it.
  2. Wildcard admin → forward credentials to RADIUS.

This means a local breakglass user always works regardless of RADIUS state, and any other username falls through to the RADIUS chain. Don’t create a local user with the same name as a RADIUS user — local always wins, which can mask credential changes upstream.

Step 6 — Verify

The order to verify in:

# 1. Can the FortiGate reach NPS at all?
execute ping-options source 172.20.10.1
execute ping 10.20.50.10

# 2. Does the shared secret work and does NPS accept the auth-type?
diagnose test authserver radius NPS-PRIMARY ms_chap_v2 alice 'TestPassword123!'

# Expected output snippet on success:
#   authenticate 'alice' against 'ms_chap_v2' succeeded, server=10.20.50.10
#   group_list=FGT-NetEngineers
#   Fortinet-Access-Profile=engineer-admin

# 3. Group resolution working?
diagnose debug enable
diagnose debug application fnbamd 7
# attempt a login from the GUI / SSH, watch the debug
diagnose debug disable

# 4. Profile is being applied as expected?
get system admin list
# Look for the remote session and which accprofile it's running under

# 5. Live admin session check
get system status
diagnose sys session list | grep <admin-source-ip>

The fnbamd debug is the single most useful command when this isn’t working. It shows the exact RADIUS conversation — the request the FortiGate sends, the attributes NPS returns, and the decision FortiOS makes about which profile to apply. Almost every failure manifests as one of:

  • wrong shared secret → fix the set secret line.
  • auth-type mismatch → flip between ms_chap_v2, pap, or chap to match what NPS allows.
  • no Fortinet-Access-Profile attribute returned → NPS Network Policy isn’t returning the VSA, or the AD group condition didn’t match.
  • unknown group → the wildcard admin’s remote-group doesn’t match the user-group name.

Gotchas

These are the ones that have eaten my afternoon at one point or another.

1. auth-type mismatch. NPS by default rejects PAP. If your secondary server happened to be FreeRADIUS configured for PAP, you can end up with primary auth working and secondary silently failing. Pick one auth-type and force it on every server.

2. Source IP and management VRF. If RADIUS lives in a different VRF from the FortiGate’s default route, you must set source-ip and set interface-select-method specify. Otherwise the request leaves out of wan1 (VRF 0), NPS sees a public IP it doesn’t recognise, and the request is dropped with no useful log on either side.

3. MFA timeout. Default 5 seconds is too short for push MFA. Bump to 30+ and test with the user actually approving and rejecting.

4. Two wildcards, ambiguous match. If you use Pattern B and a user is in two AD groups (say, FGT-NOC and FGT-Auditors), the wildcard admin selected is the first match in FortiOS evaluation order. Document which group wins, or use Pattern A and let the RADIUS server pick exactly one VSA value.

5. RADIUS over IPsec — return path. If RADIUS rides an IPsec tunnel back to the hub, ensure the tunnel selectors include the FortiGate source-IP and the RADIUS server destination-IP. A tunnel that comes up but doesn’t include the AAA flow makes failures look like RADIUS issues when they’re actually crypto issues.

6. NPS connection request policies. NPS evaluates Connection Request Policies before Network Policies. A misconfigured CRP will reject the conversation before any group lookup happens. If diagnose test authserver returns Access-Reject and NPS Event Log shows Reason Code: 8 (The specified user account is disabled) when the account is fine, suspect a CRP mismatch.

7. VSA values are case-sensitive. Engineer-Admin from NPS will not match engineer-admin on FortiGate. Match them exactly, lowercase by convention.

8. accprofile no_access as default-deny. When using Pattern A, the wildcard admin’s local accprofile should be no_access (or a custom equivalent). If RADIUS returns no VSA, the user logs in with the wildcard’s profile — making it super_admin is a recipe for an accidental privilege grant.

9. RADIUS accounting and FortiAnalyzer. RADIUS accounting from the FortiGate goes to the RADIUS server, not FortiAnalyzer. If your audit trail comes from FortiAnalyzer’s event log, you don’t need accounting on. If it comes from the RADIUS server’s logs (e.g. SIEM ingest from NPS), enable set acct-server for each RADIUS server entry.

10. Profile drift across the fleet. The whole point of central AAA is that role definitions are centralised. If five engineers have manually tweaked the engineer-admin profile across twenty branch FortiGates, you’ve lost that property. Define profiles once in a FortiManager template, treat them as version-controlled config, and stop hand-editing.

11. Renaming an AD group breaks everything silently. NPS condition matches the group SID or the literal name depending on configuration. Renaming FGT-NOC to Network-Operations-Center may pass a CRP using SID-match but fail a User Groups match using name. Always re-test after any AD-side rename.

12. set wildcard enable is required. Without it, the FortiGate looks for an exact local user match and falls back to local-deny. The wildcard flag is what tells FortiOS to forward unknown usernames to the configured remote group.

Verification commands worth memorising

# Test RADIUS from the FortiGate
diagnose test authserver radius NPS-PRIMARY ms_chap_v2 <user> '<pass>'
diagnose test authserver radius NPS-SECONDARY ms_chap_v2 <user> '<pass>'

# Live debug of the next auth attempt
diagnose debug reset
diagnose debug console timestamp enable
diagnose debug application fnbamd 7
diagnose debug enable
# ... user logs in ...
diagnose debug disable

# What admin profile is each session using right now?
get system admin list
diagnose sys session list | grep "policy_id"

# Show all profiles and what they grant
show system accprofile
show system accprofile engineer-admin

# Confirm the wildcard mapping
show system admin remote-admin

# RADIUS server-side stats
diagnose test authserver radius-direct NPS-PRIMARY 1812

Closing

Centralised admin AAA on FortiGate SD-WAN comes down to three things done in this order: define the profiles that capture each role’s actual job; configure the RADIUS conversation with the right source IP, auth-type, and timeout; and choose VSA-based mapping so role definitions live on the RADIUS side instead of being baked into FortiOS config. Once those three are in place, scaling from one branch to fifty is just FortiManager pushes and AD group adds.

The break-glass account is non-negotiable and the local-account-always-wins evaluation order is the property that lets you keep one. Set it up, document the password vault entry, and you’ll never be the person who locked themselves out of their own SD-WAN edge during a Friday-afternoon RADIUS outage.

If you came in from Part 1, that’s the full picture — protocol choice, server choice, FortiOS config, role design, and verification, end to end. The next thing worth writing about is wiring the same RADIUS chain into SSL VPN with Azure MFA, and how the user-group definitions there interact with the admin-side ones; that’ll be a separate post.