Two hundred staff, one directory, one MFA server
Kestrel Logistics keeps its 200 user accounts in Active Directory on DC1 (10.0.1.5, domain corp.example.com). Finance staff must reach the finance server FIN-SRV (10.0.2.30) in the DMZ; everyone else must not. The network team already uses a Windows NPS server, NPS1 (10.0.1.7), as a RADIUS server with multi-factor authentication for its VPN, and wants the same server to decide who gets the NetOps access on FGT1. Creating 200 local users on FGT1 would mean a second set of passwords that nobody would keep up to date. Both servers already know the answer, so FGT1 should ask them.
The previous lesson showed where user groups fit into a policy. This one is about the server side: how the FortiGate talks to LDAP and RADIUS, how a remote group becomes a FortiGate group, and how to prove each step from the CLI.
Learn more: Users and Firewall Authentication
Active and passive: where these servers fit
FortiOS identifies users in two ways. Active authentication asks for credentials: the captive portal, an IPsec dial-up VPN with XAUTH or EAP, an explicit proxy login, an administrator login. LDAP and RADIUS servers answer those requests. Passive authentication learns who is on an IP address without asking, from Windows logons (FSSO) or from RADIUS accounting messages (RSSO). This lesson covers the servers used for active authentication; the same LDAP server object is reused by FSSO for group lookups, which is one more reason to get it right first.
What FGT1 does with a username and password
The bind type controls the first two steps:
| Bind type | What happens | Use it when |
|---|---|---|
| Simple (CLI default) | Binds as cnid=user,dn directly; no search below the dn | All users sit in one flat container |
| Anonymous | Searches without credentials | The directory allows anonymous search (AD normally doesn't) |
| Regular | Binds with a service account, then searches the dn and its subtrees | Active Directory, users spread across OUs |
For Active Directory, regular is the practical choice. Users live in many OUs, and simple bind only finds those directly below the dn.
Building the LDAPS connection to DC1
New LDAP Server
- Name
- KESTREL-AD
- Server IP/Name
- dc1.corp.example.comA name that appears in the server certificate, so the identity check can pass.
- Server Port
- 636
- Common Name Identifier
- sAMAccountName
- Distinguished Name
- dc=corp,dc=example,dc=com
- Bind Type
- Simple Anonymous Regular
- Username
- fgt-ldap@corp.example.com
- Password
- ••••••••
- Secure Connection
- On
- Protocol
- STARTTLS LDAPS
- Certificate
- KESTREL-ROOT-CAWithout a CA certificate selected, the server certificate is not validated.
- Server identity check
- On
config user ldap
edit "KESTREL-AD"
set server "dc1.corp.example.com"
set cnid "sAMAccountName"
set dn "dc=corp,dc=example,dc=com"
set type regular
set username "fgt-ldap@corp.example.com"
set password <service-account-password>
set secure ldaps
set port 636
set ca-cert "KESTREL-ROOT-CA"
set server-identity-check enable
next
endThe company's root CA certificate must first be imported (System › Certificates › Import › CA Certificate). The FortiGate must be able to resolve dc1.corp.example.com, so its DNS servers should include DC1.
| Setting | What it does here |
|---|---|
cnid | sAMAccountName lets users type alice; the attribute name is case sensitive |
dn | Search from the domain root, so users in any OU are found |
username | UPN, DOMAIN\name or full DN formats are accepted |
secure ldaps | TLS from the first packet on port 636 (STARTTLS upgrades a port 389 connection instead) |
ca-cert | Accept only a server certificate signed by this CA |
server-identity-check | The certificate's SAN (or CN, if there is no SAN) must match the server name or IP (default enable) |
The bind account only needs to read users and groups. A standard domain user is enough in most AD environments; a Domain Admin account here is a gift to whoever later reads the FortiGate's configuration backup. In practice, LDAP logins that "suddenly stopped working" on a Monday are very often this account's password expiring, so give it a password policy of its own and a calendar reminder.
Users in nested groups
By default FortiOS reads only the groups a user is a direct member of. If FIN-Staff contains a group AP-Clerks and Bob is in AP-Clerks, Bob does not match a FortiGate group mapped to FIN-Staff. For Active Directory, set search-type recursive in the LDAP server entry follows the nesting. It costs extra searches, so enable it only if your AD design really nests groups.
Adding NPS1 as a RADIUS server
RADIUS (Remote Authentication Dial-In User Service, RFC 2865) works differently. FGT1 sends an Access-Request over UDP 1812 containing the username and the password, hidden with the shared secret. NPS1 checks its own policies (and here, the MFA step) and replies Access-Accept or Access-Reject. An Accept can carry attributes, such as the Fortinet-Group-Name vendor-specific attribute (VSA) that FortiOS uses for group matching.
config user radius
edit "KESTREL-NPS"
set server "10.0.1.7"
set secret <shared-secret>
set auth-type auto
set nas-ip 10.0.1.1
next
endGUI: User & Authentication › RADIUS Servers. auth-type auto tries PAP, MSCHAPv2 and CHAP in that order; set a single method if the server only accepts one. nas-ip is optional: without it the FortiGate uses the address of the interface facing the server.
NPS1 must list FGT1 as a RADIUS client with the same shared secret and the source address it will see, here 10.0.1.1 (FGT1's port2). A request from an address the server doesn't know as a client is usually dropped without a reply, which on the FortiGate looks exactly like a server that is down.
The FortiGate waits five seconds by default for any remote server to answer (remoteauthtimeout under config system global, 1–300). An MFA push that the user approves on a phone usually takes longer, so RADIUS servers with push MFA normally need this raised, for example to 60.
Because of the Blast-RADIUS vulnerability (CVE-2024-3596), FortiOS 7.6 requires the Message-Authenticator attribute in RADIUS responses over UDP and TCP (require-message-authenticator, enabled by default). An unpatched RADIUS server that omits it will fail every login. Patch the server; disabling the check is a temporary workaround only. RADIUS over TLS (RadSec, set transport-protocol tls) is the stronger fix where the path is untrusted. This page has not yet been reviewed against FortiOS 7.4.
Turning remote groups into FortiGate groups
A FortiGate user group of type Firewall can hold local users, remote servers, or both. Adding a server without a group match is the trap: every valid account on that server becomes a member. For LDAP that means every enabled AD user. Add a match so only the intended directory group qualifies.
config user group
edit "GRP-Finance"
set member "KESTREL-AD"
config match
edit 1
set server-name "KESTREL-AD"
set group-name "CN=FIN-Staff,OU=Groups,DC=corp,DC=example,DC=com"
next
end
next
edit "GRP-NetOps"
set member "KESTREL-NPS"
config match
edit 1
set server-name "KESTREL-NPS"
set group-name "NetOps"
next
end
next
endGUI: User & Authentication › User Groups › Remote Groups › Add. For LDAP the GUI lets you browse the directory and pick the group; the stored name is its full DN. For RADIUS you type the value that NPS1 returns in Fortinet-Group-Name.
Group names are compared as text. For RADIUS, setting the group name to Any does not mean "any group": it matches a server that literally returns "Any". To go back to accepting every user on the server, delete the match entry. When a user tries to log in, local members are checked first, then the remote servers in the group.
config firewall policy
edit 12
set name "Finance-to-FIN-SRV"
set srcintf "port2"
set dstintf "port3"
set srcaddr "LAN-subnet"
set dstaddr "FIN-SRV"
set groups "GRP-Finance"
set service "HTTPS"
set action accept
next
endSchedule, logging and profiles as in any other policy. The group decides who; the address still decides from where.
Proving a login from the CLI
Before touching a policy, test the server with a real test account. The first argument is the name of the FortiGate's server object, not the server's hostname:
FGT1 # diagnose test authserver ldap KESTREL-AD alice <password> authenticate 'alice' against 'KESTREL-AD' succeeded! Group membership(s) - CN=FIN-Staff,OU=Groups,DC=corp,DC=example,DC=com CN=Domain Users,CN=Users,DC=corp,DC=example,DC=com
FGT1 # diagnose test authserver radius KESTREL-NPS pap dana <password> authenticate 'dana' against 'pap' succeeded, server=primary ... Group membership(s) - NetOps
The GUI has the same tests: Test Connectivity and Test User Credentials on the LDAP server page. For more detail, the authentication daemon fnbamd can print every step:
Debug output and passwords
The test command puts a password in the CLI history and the debug can print usernames and directory data. Use a dedicated test account, run the debug only while reproducing the problem, and stop it with diagnose debug disable and diagnose debug reset.
diagnose debug reset
diagnose debug console timestamp enable
diagnose debug application fnbamd -1
diagnose debug enable
diagnose test authserver ldap KESTREL-AD alice <password>
diagnose debug disable
diagnose debug resetRead it in order: connection and TLS to the server, the service bind, the search result, the user bind, then the groups and the match. The first step that fails is the fault.
To watch the result in production, open Dashboard › Assets & Identities and expand the Firewall Users widget. It lists each authenticated user, the IP address, the method and the groups, and lets you deauthenticate one user. The CLI equivalent is diagnose firewall auth list.
diagnose firewall auth clear logs everyone out
Without a filter it removes every authenticated user on the VDOM, and every portal user must log in again. Set diagnose firewall auth filter first, or deauthenticate the single user in the GUI.
Five ways LDAP and RADIUS logins fail
Every LDAP test fails, even for known-good accounts
The service bind is failing, so nothing after it runs. The fnbamd debug shows the bind rejected (in Active Directory, result 49 with data 52e means invalid credentials, data 532 an expired password).
Show the fix
Check the bind account: password, expiry, lockout and the username format. Re-enter the password in the LDAP server entry (it is stored encrypted, so a pasted show output can't be compared). Then rerun the test.
LDAPS fails after the domain controller's certificate was renewed
Plain LDAP to the same server works; LDAPS doesn't. Either the new certificate is signed by a different CA than the one selected, or it no longer contains the name (or IP address) in Server IP/Name.
Show the fix
Import the issuing root CA and select it, and point the server field at a name that appears in the certificate's SAN. Using an IP address works only if that IP address is in the certificate. Turning off the identity check makes the connection work but stops the FortiGate from knowing it is talking to the real DC.
The test succeeds, but the user is not in the group
The group DN in the match differs from what the test returns, or the user is in a nested group.
Show the fix
Compare the match's group-name with the "Group membership(s)" line, character for character. If the user's group is inside the matched one, enable search-type recursive or match the inner group too.
RADIUS: "no response" from a server that is clearly running
NPS1 doesn't answer FGT1. Common causes: FGT1 isn't a client on NPS1, the request leaves from a different interface (after a routing or SD-WAN change) so the source IP isn't the registered one, or a firewall in between blocks UDP 1812.
Show the fix
Sniff the exchange with diagnose sniffer packet any 'host 10.0.1.7 and port 1812' 4. Requests with no replies point at the server side: check the NPS event log for a discarded request from an unknown client. Fix the client entry or pin the source with source-ip (or nas-ip) on the RADIUS server object.
Anyone with an AD account reaches FIN-SRV
GRP-Finance contains the KESTREL-AD server with no match entries, so every valid directory account is a member. Nothing errors, which is why this one survives until an audit.
Show the fix
Add the FIN-Staff group match, then deauthenticate current users so they are checked again. Review every user group with show user group: a remote member with no config match block is the pattern to look for.
Designing remote authentication that survives a bad day
- Give each server a backup:
secondary-serverfor LDAP and RADIUS (a third RADIUS server is possible in the CLI), pointing at a second DC or NPS server. - Use LDAPS with a selected CA and the identity check enabled, or RadSec, whenever the traffic crosses a network you don't fully trust.
- Match specific remote groups in every user group, and name FortiGate groups after what they allow (GRP-Finance), not after the server.
- Keep a local emergency administrator; if both DCs are unreachable, remote logins fail with them.
- Raise
remoteauthtimeoutonly as far as MFA needs; a long timeout also makes a dead server feel slow.
Try it: connect a lab FortiGate to a test directory
In a lab with a Windows Server domain controller, create a bind account, a group LAB-Finance with one test user, and a nested group inside it with a second user. Configure an LDAPS server entry on the FortiGate, then run diagnose test authserver ldap for both users and note the group lists. Create a user group matching LAB-Finance and see which user the policy accepts; then enable search-type recursive and test again. Finally, change the server field to the DC's IP address and explain the LDAPS result.
- Connection: can the FortiGate reach the server, and does TLS validate?
- Credentials: does
diagnose test authserversucceed for a test user? - Group: does the returned group list contain the matched group exactly?
- Policy: does the group sit in the policy that actually matches the traffic?
Test yourself on LDAP and RADIUS
GRP-Staff has KESTREL-AD as a remote member and no group match. Who can authenticate as a member of GRP-Staff?
diagnose test authserver ldap KESTREL-AD bob <pw> succeeds and lists CN=AP-Clerks,... only. GRP-Finance matches CN=FIN-Staff, which contains AP-Clerks. What fixes Bob's access?
LDAPS to 10.0.1.5 fails with server-identity-check enabled. The DC certificate's SAN lists only dc1.corp.example.com. What is the cleanest fix?
diagnose test authserver radius KESTREL-NPS pap dana <pw> reports no response. A sniffer shows requests leaving port2 and nothing coming back. Where do you look next?
After a NPS update, every RADIUS login fails on FortiOS 7.6, while the server logs show Access-Accept being sent. Which setting is involved?