Do you want redundancy? Don’t answer that question; the answer is “Yes!” This means you want your FortiClient EMS deployment to be redundant, and this gives you the problem of how to handle the FortiClients and FortiGate connection to your HA EMS nodes if you don’t have an external load balancer.
Well, dear reader, I’ve got just the solution for you, so let’s see how it’s done.
The setup
- FortiGate 70G on 7.6.7
- 2x FortiClient EMS on 7.4.7
- Windows 11 client on 25H2 with FortiClient 7.4.7
As far as layer 3 is concerned, I have a CLIENT and DMZ VLAN, where, respectively, the client and the EMS instances are located, as well as the UNUSED VLAN, which gets used later.

The configuration is very light this time, but it is still on the Fortinet resources GitHub repository.
FortiClient EMS HA basics
First things first. There are several HA deployment options, and I specifically cover the one using only VM appliances. As the documentation states, this requires FortiClient EMS 7.4.5, so keep this in mind.
Setting the cluster up is not hard; it took me about 5 minutes, and there are no additional licenses necessary; you just need the resources for the VM.
A few points here:
- In order to change the priority of the nodes, you use the
ha standbycommand on the current primary (useha get nodesto see the cluster state). There are two roles in the cluster: the database and EMS
- If you want to demote the current primary for both roles, you use
ha standby --type=”db”andha standby --type=”ems”. The documentation linked above only mentions it but doesn’t give an example of how to do it in the document. You need the CLI reference for this.- In the following example, EMS2 is the primary for both roles at first and gets demoted
- I have removed the “Preferred DCs” column so it fits nicely in here, and because it’s empty anyway
ems@EMS2 $> ha get nodes
EMS Node(s):
Name | Role | Status | Last Seen
--------------------------------------------------+---------+---------+----------------------------
EMS2 (*) | primary | online | 2026-06-18 18:32:57.841091
EMS1 | standby | online | 2026-06-18 18:33:00.042757
DB Node(s):
Host | Port | Role | Status | Latency (ms)
--------------------------------------------------+---------+---------+------------
192.168.1.177 | 5432 | standby | online | 28
127.0.0.1 | 5432 | primary | online | 27
ems@EMS2 $> ha standby --type="db"
Node demoted successfully!
ems@EMS2 $> ha standby --type="ems"
Node demoted successfully!
ems@EMS2 $> ha get nodes
EMS Node(s):
Name | Role | Status | Last Seen
--------------------------------------------------+---------+---------+----------------------------
EMS1 | primary | online | 2026-06-18 18:35:10.04539
EMS2 (*) | standby | online | 2026-06-18 18:35:01.322546
DB Node(s):
Host | Port | Role | Status | Latency (ms)
--------------------------------------------------+---------+---------+------------
192.168.1.177 | 5432 | primary | online | 27
127.0.0.1 | 5432 | standby | online | 26
- The telemetry service, TCP/8013, is only active on the current primary, and the HTTPS service is active on both nodes
- This is an important point for later
- EMS does not have its own virtual IP or load balancer service, like haproxy, so any outside connections to it have to get load balanced via something. If you have an external load balancer, this is simple, but that’s a luxury not everyone has
- The failover time is calculated using the following formula: High Availability Keep Alive Interval * 2 + 60
- The High Availability Keep Alive Interval is configured in the EMS Settings menu
- With default settings, this means it takes 80 seconds for a failover to occur
With the basics covered, let’s connect things to EMS.
The FortiClient to EMS connection
This one is easy. You can use the Load Balance feature on the FortiGate to accomplish this (it has to be enabled in Feature Visibility first), and then you can configure everything in the Virtual Servers menu under Policy & Objects.
For FortiClients, we need, at the minimum, TCP/8013 for the telemetry, so create your virtual server using that as the port and also the health check (either in-line or using the Health Check menu). If you need the installer port, default TCP/11443, you can do that too.

config firewall vip
edit "EMS-HA-FORTICLIENT"
set type server-load-balance
set server-type tcp
set extip 192.168.1.190
set extintf "CLIENTS"
set monitor "tcp8013"
set extport 8013
config realservers
edit 1
set ip 192.168.1.177
set port 8013
next
edit 2
set ip 192.168.1.178
set port 8013
next
end
next
end
With the virtual server created, you can create your policy accordingly. Note that if you use the type TCP in your virtual server, the policy can be created using the flow-based inspection mode, which means hardware offloading. You only need proxy-based for non-TCP/UDP/IP types. Attach security profiles to the policy as necessary.

That’s everything you need for FortiClient to connect to EMS. Use the FQDN or an invitation code in the FortiClient GUI and hit Connect. Simple, right?
The FortiGate to EMS connection
This is the one that required some time to get working because the FortiGate needs to load-balance its own traffic, which I didn’t think was actually possible, but apparently it is.
We, again, start with the virtual server, and we need TCP/443 here, but crucially, the health check must not be TCP/443. As mentioned above, the HTTPS service is active on both nodes, so this would create immediate issues depending on your load-balancing method and, in general, create issues.
What service is only active on the primary node? The telemetry service, so we reuse the health check for TCP/8013, we already used for the FortiClient connection. The real servers still use TCP/443, however.
I am binding the virtual server to the UNUSED VLAN because I only need to put the virtual server in a policy to activate it. This is done more so to show that this is possible. In reality, you would probably bind it to a management interface, so if you go to the virtual server IP, you always land on the active EMS node’s GUI.

config firewall vip
edit "EMS-HA-FORTIGATE"
set type server-load-balance
set server-type tcp
set extip 192.168.1.190
set extintf "LOOPBACK"
set monitor "tcp8013"
set extport 443
config realservers
edit 1
set ip 192.168.1.177
set port 443
next
edit 2
set ip 192.168.1.178
set port 443
next
end
next
end
With this in mind, the policy we need is nothing special. The virtual server just needs to be active, and again, we can use a flow-based policy. Attach security profiles to the policy as necessary.

In the Fabric Connector for EMS, we use the FQDN, which resolves to the virtual server IP, and at least in my configuration, nothing happens because the traffic gets sourced incorrectly.

To solve this, you go to the CLI and set a source IP for this connection. I am using the IP that the DMZ interface has, so that will be the one I see on EMS.
Since we’re already in the CLI, we can use the command execute fctems verify <ID> (insert your ID) to start the verification, which should give us a certificate to accept.
FortiGate EMS fabric CLI configuration
70G-EMSHA # config endpoint-control fctems
70G-EMSHA (fctems) # edit 1
70G-EMSHA (1) # set source-ip 192.168.1.202
70G-EMSHA (1) # end
The configuration will not be effective unless server certificate is verified.
You can get and verify server certificate by the following command:
"execute fctems verify 1" (ems table id)
70G-EMSHA # execute fctems verify 1
Subject: CN = ems-ha.ad.labdomain.com
Issuer: DC = com, DC = labdomain, DC = ad, CN = WIN-CA
Valid from: 2026-06-18 08:10:35 GMT
Valid to: 2028-06-17 08:10:35 GMT
Fingerprint: 6D:6B:80:BC:10:9B:31:F7:15:7A:CC:03:71:01:98:9C:97:A0:4D:C5:21:12:C8:10:15:31:F3:43:E8:9B:2E:A7
Root CA: No
Version: 3
Serial Num:
32:00:00:00:44:88:6a:ed:ca:e6:cb:5b:39:00:02:00:00:00:44
Extensions:
Name: X509v3 Subject Key Identifier
Critical: no
Content:
BC:33:E8:D9:39:2F:29:36:21:E2:B7:88:9A:70:8D:79:63:F7:C6:55
Name: X509v3 Key Usage
Critical: yes
Content:
Digital Signature, Key Encipherment
Name: X509v3 Subject Alternative Name
Critical: no
Content:
DNS:ems-ha.ad.labdomain.com, DNS:ems1.ad.labdomain.com, DNS:ems2.ad.labdomain.com
Name: X509v3 Authority Key Identifier
Critical: no
Content:
F3:CD:D6:6C:D4:C1:35:68:D7:EE:AA:07:7A:A8:5A:73:B8:48:6C:D5
Name: X509v3 CRL Distribution Points
Critical: no
Content:
Full Name:
URI:ldap:///CN=WIN-CA(2),CN=WIN-AD,CN=CDP,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=ad,DC=labdomain,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint
URI:http://WIN-AD.ad.labdomain.com/CertEnroll/WIN-CA(2).crl
Name: Authority Information Access
Critical: no
Content:
CA Issuers - URI:ldap:///CN=WIN-CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=ad,DC=labdomain,DC=com?cACertificate?base?objectClass=certificationAuthority
CA Issuers - URI:http://WIN-AD.ad.labdomain.com/CertEnroll/WIN-AD.ad.labdomain.com_WIN-CA(2).crt
Name: Microsoft certificate template
Critical: no
Content:
0-.%+.....7.....n...z...%...w........G..f..d...
Name: X509v3 Extended Key Usage
Critical: no
Content:
TLS Web Server Authentication
Name: Microsoft Application Policies Extension
Critical: no
Content:
0.0
..+.......
EMS configuration needs user to confirm server certificate.
Do you wish to add the above certificate to trusted remote certificates? (y/n)y
Certificate successfully configured and verified.
Once we have entered the good old y, we can go to EMS and authorize the FortiGate that appeared.

Wrapping up
And that’s that. You can do your failover tests (mine worked), and hopefully, dear reader, you can now connect your FortiClients and your FortiGate to your EMS HA deployment. Additional FortiGates should be much easier to handle; it’s just the first one that requires a bit of special attention.
And if you don’t have enough of FortiClient EMS yet, you can read about how you can use its certificate management capabilities to help with VPN authentication, full SSL/TLS inspection and 802.1X.
Leave a Reply