Symptoms
SSL-VPN connection failures don't always announce themselves clearly. The symptoms depend on where in the handshake things break down, and I've seen everything from hard authentication errors to silent timeouts that leave users staring at a spinning FortiClient icon with no useful feedback.
The most common things users report:
- FortiClient displays "Unable to establish the VPN connection" or "Login failed" immediately after entering credentials
- The web portal at
https://vpn.solvethenetwork.com
loads but after authentication redirects to a blank page or shows "No portal available" - The tunnel appears to connect — the lock icon in FortiClient turns green — but no internal resources are reachable
- A two-factor prompt never appears, or the OTP code is consistently rejected even when entered correctly
- Connection works for most users but fails for one specific user or one specific group
- A certificate warning appears in the browser before the login page even loads
Each of these failure modes points to a different layer of the SSL-VPN stack. Let's walk through the most likely culprits, starting with the ones that are easiest to miss because they produce misleading error messages.
Root Cause 1: Realm Not Matching
Why It Happens
FortiGate SSL-VPN portals are tied to realms — URL path prefixes that determine which portal and authentication profile a connecting user lands on. The default realm is the root path (
/), but many organizations configure named realms like
/remoteor
/contractorsto segregate user populations. When a user hits the wrong URL — or when an admin renames a realm without updating the FortiClient profile pushed to endpoints — the FortiGate either presents the wrong portal or returns a login failure with no actionable error message.
In my experience, this bites hardest after SSL-VPN URL migrations. Half the team connects fine because their FortiClient profile was updated; the other half gets a blank portal or a generic login error because their saved profile still points to an old realm path. The maddening part is that their credentials are correct — the realm routing is what's broken.
How to Identify It
Check the configured realms on the FortiGate CLI and compare them against your portal-to-group authentication rules:
sw-infrarunbook-01 # config vpn ssl web realm
sw-infrarunbook-01 (realm) # show
config vpn ssl web realm
edit "contractors"
set url-path "/contractors"
set virtual-host ""
next
edit ""
set url-path "/"
next
end
sw-infrarunbook-01 # config vpn ssl settings
sw-infrarunbook-01 (settings) # show
config vpn ssl settings
set servercert "solvethenetwork-ssl-cert"
set tunnel-ip-pools "SSLVPN_TUNNEL_ADDR1"
set dns-server1 10.10.1.10
set port 443
config authentication-rule
edit 1
set groups "SSL-VPN-Users"
set portal "full-access"
set realm "contractors"
next
end
end
Then pull the event log. A realm mismatch produces a very specific log entry:
sw-infrarunbook-01 # execute log filter category 1
sw-infrarunbook-01 # execute log filter field action login
sw-infrarunbook-01 # execute log display
date=2026-04-19 time=09:14:22 logid="0101037127" type="event" subtype="vpn"
level="error" action="ssl-login-fail" reason="sslvpn_login_unknown_user"
user="infrarunbook-admin" realm="" profile="no-access"
msg="SSL user failed to login"
The
realm=""field combined with
profile="no-access"is the tell — the user hit the root URL but no authentication rule maps their group to a portal under that realm.
How to Fix It
Either update the FortiClient profile on affected machines to point at the correct realm URL, or add the user's group to an authentication rule that covers the realm they're hitting. To map a group to the default (root) realm:
sw-infrarunbook-01 # config vpn ssl settings
sw-infrarunbook-01 (settings) # config authentication-rule
sw-infrarunbook-01 (authentication-rule) # edit 2
sw-infrarunbook-01 (2) # set groups "SSL-VPN-Users"
sw-infrarunbook-01 (2) # set portal "full-access"
sw-infrarunbook-01 (2) # set realm ""
sw-infrarunbook-01 (2) # end
No service interruption for existing sessions — realm rule changes take effect for new connections only.
Root Cause 2: IP Pool Exhausted
Why It Happens
Every SSL-VPN tunnel client gets a virtual IP address assigned from a tunnel IP pool. The default pool —
SSLVPN_TUNNEL_ADDR1— ships from the factory with a range of ten addresses (typically something like 10.212.134.200–10.212.134.209). In a small office that's adequate. Scale past ten concurrent VPN users and the pool empties. New connection attempts fail because the FortiGate has no IP to assign, and the error users see is a generic login failure with absolutely no indication of the real problem.
This one catches administrators off guard because everything works perfectly right up until the eleventh simultaneous connection. It's also easy to dismiss as an intermittent issue because the pool drains and fills throughout the day — a morning standup where everyone connects at once is a classic trigger, and by the time someone investigates, a few users have disconnected and the pool looks fine again.
How to Identify It
Check current active tunnel sessions against the pool capacity:
sw-infrarunbook-01 # diagnose vpn ssl list
SSL VPN Login Users:
Index User Auth-Type Timeout From HTTP in/out HTTPS in/out
0 infrarunbook-admin 1 290 10.50.0.15 0/0 2/0
1 jsmith 1 288 10.50.0.22 0/0 1/0
2 mlopez 1 285 10.50.0.31 0/0 1/0
...
Total: 10 users
sw-infrarunbook-01 # diagnose firewall ippool-all list
name: SSLVPN_TUNNEL_ADDR1
type: overload
startip: 10.212.134.200
endip: 10.212.134.209
total: 10
used: 10
free: 0
free: 0is your answer. This will also appear in the event log with a clear reason code:
date=2026-04-19 time=09:31:05 logid="0101037146" type="event" subtype="vpn"
level="error" action="tunnel-down" reason="ip-pool-empty"
user="bwilliams" msg="SSL VPN tunnel interface IP allocation failed"
How to Fix It
Expand the tunnel IP pool. The subnet must not overlap with any existing interface or routing table entry on the FortiGate:
sw-infrarunbook-01 # config firewall address
sw-infrarunbook-01 (address) # edit "SSLVPN_TUNNEL_ADDR1"
sw-infrarunbook-01 (SSLVPN_TUNNEL_ADDR1) # set type iprange
sw-infrarunbook-01 (SSLVPN_TUNNEL_ADDR1) # set start-ip 10.212.134.200
sw-infrarunbook-01 (SSLVPN_TUNNEL_ADDR1) # set end-ip 10.212.134.254
sw-infrarunbook-01 (SSLVPN_TUNNEL_ADDR1) # end
That takes the pool from 10 to 55 addresses without touching existing sessions. If you need more room, create a second pool object and add it to the SSL-VPN settings under
tunnel-ip-pools. No reboot, no service disruption.
Root Cause 3: Split Tunneling Misconfigured
Why It Happens
Split tunneling controls which traffic from the VPN client actually routes through the tunnel versus out the user's local internet connection. When it's wrong, users connect successfully — FortiClient shows the tunnel as green — but then either can't reach internal resources because the target subnet isn't in the routing address list, or find that all their internet traffic breaks because the portal is in full-tunnel mode when split tunnel was expected. Both scenarios manifest as "the VPN doesn't work" even though the VPN itself is functioning correctly.
The misconfiguration usually creeps in one of two ways: a new internal subnet goes live but nobody updates the SSL-VPN portal's routing addresses, or a portal gets accidentally toggled to full-tunnel mode during an upgrade or config restore.
How to Identify It
Pull the portal configuration and inspect the split tunneling settings:
sw-infrarunbook-01 # config vpn ssl web portal
sw-infrarunbook-01 (portal) # edit "full-access"
sw-infrarunbook-01 (full-access) # show
config vpn ssl web portal
edit "full-access"
set tunnel-mode enable
set web-mode enable
set split-tunneling enable
set split-tunneling-routing-address "Internal_Subnets" "DMZ_Servers"
set ip-pools "SSLVPN_TUNNEL_ADDR1"
next
end
Then verify what subnets are actually inside those address objects:
sw-infrarunbook-01 # show firewall addrgrp "Internal_Subnets"
config firewall addrgrp
edit "Internal_Subnets"
set member "10.10.1.0/24" "10.10.2.0/24"
next
end
If the user is trying to reach 10.10.5.0/24 — the storage VLAN that was added last month — and it's not in that group, traffic to it bypasses the tunnel entirely and hits the user's default gateway instead. You can confirm this from the client side with a
route print(Windows) or
ip route show(Linux) while connected: the missing subnet won't appear under the tunnel interface.
For the opposite problem — full tunnel when split was expected — one field tells the whole story:
sw-infrarunbook-01 (full-access) # get | grep split
split-tunneling : disable
That single line explains an entire category of "internet stopped working when I connected to VPN" tickets.
How to Fix It
Add the missing subnets to the routing address group and correct the split tunneling flag:
sw-infrarunbook-01 # config firewall addrgrp
sw-infrarunbook-01 (addrgrp) # edit "Internal_Subnets"
sw-infrarunbook-01 (Internal_Subnets) # set member "10.10.1.0/24" "10.10.2.0/24" "10.10.5.0/24"
sw-infrarunbook-01 (Internal_Subnets) # end
sw-infrarunbook-01 # config vpn ssl web portal
sw-infrarunbook-01 (portal) # edit "full-access"
sw-infrarunbook-01 (full-access) # set split-tunneling enable
sw-infrarunbook-01 (full-access) # end
Already-connected users will need to disconnect and reconnect to receive the updated routing table pushed by the portal on tunnel establishment.
Root Cause 4: Certificate Not Trusted
Why It Happens
FortiGate ships with a self-signed certificate for its HTTPS interface. Most organizations replace this with a certificate issued by an internal CA or a public CA. When the certificate chain isn't trusted by the connecting client — because it's still self-signed, because the CA root hasn't been deployed to endpoints, or because a certificate expired — FortiClient either refuses the connection outright or presents a warning that confuses non-technical users into abandoning the attempt.
The sneaky variant I see most often is an expired intermediate certificate. The root CA is trusted, the end-entity certificate is valid, but an intermediate in the chain expired and wasn't renewed. The OpenSSL validation fails at the intermediate, the user sees a generic TLS error, and nobody immediately thinks to check intermediate expiry.
How to Identify It
Start by confirming which certificate the FortiGate is presenting for SSL-VPN:
sw-infrarunbook-01 # config vpn ssl settings
sw-infrarunbook-01 (settings) # get | grep servercert
servercert : solvethenetwork-ssl-cert
Then verify the certificate's chain from an external host using OpenSSL:
openssl s_client -connect vpn.solvethenetwork.com:443 -showcerts
CONNECTED(00000003)
depth=0 CN=*.solvethenetwork.com
verify error:num=20:unable to get local issuer certificate
verify return:1
---
Certificate chain
0 s:CN=*.solvethenetwork.com
i:CN=SolveTheNetwork Internal CA
---
No client certificate CA names sent
SSL handshake has read 2143 bytes and written 0 bytes
Verify return code: 20 (unable to get local issuer certificate)
Return code 20 means the issuer certificate isn't in the presented chain — the FortiGate is sending the end-entity cert but not the intermediate, so clients that don't already have the intermediate cached can't validate the chain. You can also check certificate expiry directly:
openssl s_client -connect vpn.solvethenetwork.com:443 2>/dev/null | openssl x509 -noout -dates
notBefore=Mar 1 00:00:00 2025 GMT
notAfter=Mar 1 23:59:59 2026 GMT
How to Fix It
For a publicly-facing portal, the cleanest fix is a certificate from a publicly trusted CA. For internal CA scenarios, ensure the full chain including intermediates is imported into the FortiGate and bound to the SSL-VPN service certificate. When importing, bundle the intermediate with the end-entity cert in the same PEM file. Then push the CA root to all managed endpoints via Group Policy or your MDM platform so validation works without manual certificate installation on every client machine.
To bind the corrected certificate to SSL-VPN:
sw-infrarunbook-01 # config vpn ssl settings
sw-infrarunbook-01 (settings) # set servercert "solvethenetwork-ssl-cert-fullchain"
sw-infrarunbook-01 (settings) # end
Root Cause 5: Two-Factor Authentication Issue
Why It Happens
FortiGate supports multiple 2FA mechanisms for SSL-VPN: FortiToken hardware or mobile app, email OTP, SMS OTP via FortiAuthenticator, or RADIUS-delivered OTP from a third-party MFA provider like Duo or Microsoft NPS. Any broken link in this chain results in the second factor never arriving or always being rejected — and the error message the user sees gives almost no indication of which link failed.
FortiToken clock drift is the one I encounter most in production. FortiToken Mobile generates TOTP codes tied to the phone's system clock. If that clock drifts more than 60 seconds from the FortiGate's reference time, the generated codes will never match what the FortiGate expects, even though the user is entering them correctly and promptly.
How to Identify It
Check the FortiToken status and drift value:
sw-infrarunbook-01 # diagnose user fortitoken status FTK123456789
Token serial: FTK123456789
Token status: active
User : infrarunbook-admin
Clock drift : +127 seconds
A drift of 127 seconds is why every code fails — the FortiGate and the token app are over two minutes apart. For email OTP, test the SMTP relay directly from the FortiGate:
sw-infrarunbook-01 # config system email-server
sw-infrarunbook-01 (email-server) # show
config system email-server
set reply-to "noreply@solvethenetwork.com"
set server "10.10.1.25"
set port 25
set auth-type none
end
sw-infrarunbook-01 # diagnose sys email test noreply@solvethenetwork.com
If the test email doesn't arrive, the SMTP relay is the problem — not the VPN. Check the relay server's mail logs and verify that port 25 isn't being blocked between the FortiGate management IP and the relay. For FortiAuthenticator-backed RADIUS 2FA, test the full authentication path:
sw-infrarunbook-01 # diagnose test auth radius FortiAuthenticator pap infrarunbook-admin Password123 654321
Authenticate 'infrarunbook-admin' against 'FortiAuthenticator' succeeded
A timeout response here means the FortiGate can't reach the FortiAuthenticator on UDP/1812 — check routing and ACLs. A reject response with
reason=invalid OTPmeans the user or token is misconfigured on the FortiAuthenticator side.
How to Fix It
For FortiToken clock drift, the correction command is straightforward:
sw-infrarunbook-01 # execute fortitoken-drift-correction FTK123456789
FortiToken FTK123456789 drift corrected successfully.
This adjusts the FortiGate's expected offset for that specific token without requiring the user to re-enroll. For email OTP, resolve the SMTP relay issue or switch to an authenticated relay with TLS if the current relay is rejecting unauthenticated connections. For FortiAuthenticator RADIUS, verify that the RADIUS shared secret matches on both sides and that the FortiGate's source IP is listed as an authorized NAS client in the FortiAuthenticator admin console. If the user's local account isn't configured for the correct 2FA method, fix it directly:
sw-infrarunbook-01 # config user local
sw-infrarunbook-01 (local) # edit "infrarunbook-admin"
sw-infrarunbook-01 (infrarunbook-admin) # set two-factor fortitoken
sw-infrarunbook-01 (infrarunbook-admin) # set fortitoken "FTK123456789"
sw-infrarunbook-01 (infrarunbook-admin) # set email-to "admin@solvethenetwork.com"
sw-infrarunbook-01 (infrarunbook-admin) # end
Root Cause 6: User Group Not Mapped to a Portal
Why It Happens
A user can authenticate successfully — correct password, correct 2FA code — and still be refused or land on a blank portal because their group isn't referenced in any SSL-VPN authentication rule. FortiGate's portal assignment is entirely group-driven. If someone is placed in the wrong LDAP group, or if the local user group on the FortiGate doesn't include the user, the FortiGate has no portal to hand them and the session is dropped.
This is common after LDAP group restructuring. An admin reorganizes AD groups for a department move but doesn't update the SSL-VPN authentication rules to reference the new group names. The users authenticate fine at the directory level but get blocked at the portal assignment step.
How to Identify It
The log entry is usually unambiguous:
date=2026-04-19 time=10:44:11 logid="0101037130" type="event" subtype="vpn"
level="warning" action="ssl-login-fail" reason="sslvpn_login_permission_denied"
user="infrarunbook-admin" group="N/A" portal="N/A"
msg="User does not belong to any SSL VPN portal group"
group="N/A"and
portal="N/A"confirm that authentication succeeded but group resolution failed. Test LDAP group membership directly from the CLI:
sw-infrarunbook-01 # diagnose test authserver ldap LDAP_Server infrarunbook-admin Password123
authenticate 'infrarunbook-admin' against 'LDAP_Server' succeeded!
Group membership(s) - CN=HelpDesk,OU=Groups,DC=solvethenetwork,DC=com
The user is a member of
HelpDeskin LDAP, but the SSL-VPN authentication rules only reference
SSL-VPN-Users. That's the gap. The user authenticated but fell through all rules with no match.
How to Fix It
Add the
HelpDeskgroup to an authentication rule, either the existing one or a new rule pointing to an appropriate portal:
sw-infrarunbook-01 # config vpn ssl settings
sw-infrarunbook-01 (settings) # config authentication-rule
sw-infrarunbook-01 (authentication-rule) # edit 3
sw-infrarunbook-01 (3) # set groups "HelpDesk"
sw-infrarunbook-01 (3) # set portal "limited-access"
sw-infrarunbook-01 (3) # set realm ""
sw-infrarunbook-01 (3) # end
Root Cause 7: Firewall Policy Missing for SSL-VPN Traffic
Why It Happens
The tunnel comes up, the user gets an IP from the pool, split tunneling is configured correctly — but nothing inside the network is reachable. This almost always means a missing or incorrectly scoped firewall policy. Traffic from the SSL-VPN interface (
ssl.root) to internal zones requires an explicit allow policy. FortiOS does not create this automatically when SSL-VPN is enabled. It's a manual step that's easy to skip, especially when building a new FortiGate from scratch or after restoring a partial configuration backup.
How to Identify It
Check whether any policy exists with
ssl.rootas the source interface:
sw-infrarunbook-01 # show firewall policy | grep -B2 ssl.root
edit 10
set name "SSL-VPN to Internal"
set srcintf "ssl.root"
If that returns nothing, there's no policy. You can also use the built-in iprope lookup to simulate a specific traffic flow:
sw-infrarunbook-01 # diagnose firewall iprope lookup 10.212.134.201 10.10.1.100 6 443 0 ssl.root
Matched policy id: 0 (no matching policy found)
Policy ID 0 means implicit deny — traffic from the VPN pool hitting port 443 on an internal host has no permit rule.
How to Fix It
Create the policy. Scope it to the tunnel address pool as the source and whatever internal resources VPN users should reach as the destination:
sw-infrarunbook-01 # config firewall policy
sw-infrarunbook-01 (policy) # edit 10
sw-infrarunbook-01 (10) # set name "SSL-VPN to Internal"
sw-infrarunbook-01 (10) # set srcintf "ssl.root"
sw-infrarunbook-01 (10) # set dstintf "internal"
sw-infrarunbook-01 (10) # set srcaddr "SSLVPN_TUNNEL_ADDR1"
sw-infrarunbook-01 (10) # set dstaddr "Internal_Subnets"
sw-infrarunbook-01 (10) # set action accept
sw-infrarunbook-01 (10) # set schedule "always"
sw-infrarunbook-01 (10) # set service "ALL"
sw-infrarunbook-01 (10) # set logtraffic all
sw-infrarunbook-01 (10) # end
The change takes effect immediately. Existing tunnel sessions that were already connected will start routing correctly without needing to reconnect.
Prevention
Most of these issues are repeatable and preventable. The realm and portal mapping problems almost always come from changes made without a checklist. Someone restructures an AD group, forgets to update the SSL-VPN authentication rule, and discovers the gap two weeks later when a user calls the helpdesk. Maintaining a simple runbook that maps every user group to its expected realm and portal — and making that runbook a required review step for any directory change — closes that gap.
IP pool exhaustion is easy to get ahead of. Set a monitoring threshold: if active SSL-VPN sessions exceed 70% of your pool capacity, trigger an alert. FortiGate exposes active session counts via SNMP, and any NMS worth running can graph this and page you before you hit saturation. Don't wait for users to report it — by then the pool is already full.
For certificates, calendar the expiry dates and build in 30-day lead time for renewal. A week of runway isn't enough when you factor in change control windows. If you're using an ACME provider, automate both the renewal and the import to FortiGate using the REST API — the FortiGate supports certificate management via API endpoints and it's worth the one-time automation investment.
FortiToken clock drift is best caught proactively. A weekly script that runs
diagnose user fortitoken statusacross all enrolled tokens and alerts if drift exceeds 30 seconds gives you time to correct it before it starts rejecting codes. The correction command is non-disruptive and takes seconds.
Split tunnel routing address groups should be part of your subnet provisioning checklist — not an afterthought. When a new VLAN is created and a subnet is assigned, adding it to the SSL-VPN portal routing addresses should be a required step, the same as adding a firewall policy for it. Treat the portal routing list the same way you treat your routing table: it needs to be updated when the network changes.
Finally, always validate SSL-VPN configuration changes with a test account before rolling them out to a production group. A five-minute connection test from a clean machine — not your own workstation with a cached session — catches the majority of the issues above. The cost of that test is negligible. The cost of fifty people unable to work from home while you debug a realm misconfiguration is not.
