FortiGate Certificate Inspection for LDAP/Active Directory: Why Secure LDAP Breaks

FortiGate Certificate Inspection for LDAP/Active Directory: Why Secure LDAP Breaks

When Our VPN Logins Went Quiet

After migrating Active Directory to use LDAPS on port 636, FortiGate authentication for VPN users silently failed for 8 hours before the help desk escalated it. We had FortiGate units running FortiOS 7.4.3 in front of our remote access VPN, domain controllers on Windows Server, and a manufacturing floor that does not care whether the problem is LDAP, TLS, certificates, or my change window notes.

At first, I assumed the LDAP bind account was locked out or that the Base DN had been copied wrong during cleanup. That was my mistake. The DC certificate was issued by our internal Microsoft CA, which was not in the FortiGate trusted CA store, so FortiGate could open a TCP session to the domain controller but would not trust the certificate chain presented during LDAPS negotiation.

The ugly part was the user experience. The VPN prompt did not say “certificate validation failed.” Users just saw rejected authentication, and the help desk saw a pile of password reset tickets that were technically useless. Before the CA import, LDAP authentication success rate was effectively 0% for affected VPN users; after importing the internal CA certificate into FortiGate, LDAP authentication success rate restored to 100%.

Trust broke before LDAP did.

My opinion after that incident is simple: when FortiGate talks LDAPS to Active Directory, certificate trust deserves the same change-control attention as firewall policy.

What Changed When We Moved From LDAP to LDAPS

Plain LDAP on port 389 let our FortiGate bind to Active Directory without caring about the domain controller certificate. Once we switched to LDAPS on port 636, the LDAP query structure stayed familiar, but the transport stopped being forgiving. The FortiGate had to validate the server certificate, verify the issuer, and complete TLS negotiation before the bind could matter.

We did not change the user search filter, group membership lookup, or bind DN format. We changed the trust requirement underneath them. That distinction mattered because all the familiar LDAP settings still looked correct in the GUI, which made the outage feel more confusing than it really was.

  • LDAP server address still pointed at the domain controller FQDN.
  • Port changed from 389 to 636.
  • Secure connection mode changed to LDAPS.
  • Bind credentials stayed the same.
  • FortiGate needed the internal Microsoft CA certificate before it could trust the DC certificate.

What I didn’t expect was how cleanly the problem hid behind a normal authentication failure. I have debugged failed binds, expired passwords, nested group issues, and bad search bases for years, but this one sat below the LDAP layer and made the higher-level symptoms misleading.

The cert was the control plane.

My opinion: if we enable LDAPS without proving certificate chain trust from the firewall’s point of view, we have only completed half the migration.

Import the Internal CA Into FortiGate

Our domain controller certificate was valid inside Windows because our domain machines already trusted the internal Microsoft CA. The FortiGate was not a domain-joined Windows host, and it did not inherit that trust. I had to export the issuing CA certificate from our Microsoft CA, then import it into the FortiGate trusted CA certificate store.

In our environment, I exported the root CA as Base-64 encoded X.509, checked the certificate path from a Windows admin workstation, then uploaded the CA certificate under FortiGate certificate management. On FortiOS 7.4.3, the GUI path was straightforward, but I still verified from the CLI because I do not like assuming a certificate object is usable just because it appears in a dropdown.

config vpn certificate ca
    show
end

config user ldap
    edit "AD-LDAPS"
        set server "dc01.plant.example.com"
        set cnid "sAMAccountName"
        set dn "DC=plant,DC=example,DC=com"
        set type regular
        set username "CN=svc-fgt-ldap,OU=Service Accounts,DC=plant,DC=example,DC=com"
        set secure ldaps
        set port 636
        set ca-cert "Plant-Internal-Root-CA"
    next
end

I also checked name alignment. If FortiGate connects to an IP address but the DC certificate only contains the FQDN in the subject alternative name, validation can still fail depending on configuration and behavior. We standardized on FQDNs for LDAP server entries because certificates are identity documents, not decorations.

Small certificate shortcuts become long outages.

My opinion: internal PKI is still PKI, and treating it as “trusted because we made it” is how avoidable outages get manufactured.

Debug LDAP From the FortiGate CLI

Once the CA was imported, I wanted proof before telling the help desk to ask users to retry. On the FortiGate, I used diagnostic commands to watch the authentication path instead of staring at the GUI test button. The CLI gave me the sequence I needed: DNS resolution, TCP reachability, TLS behavior, bind attempt, and group matching.

My normal workflow now is to start narrow and increase detail only when the first pass does not show the fault. I run packet capture for port 636, then LDAP debug, then authentication debug. If the firewall never completes TLS, chasing bind filters is wasted time. If TLS completes and bind fails, then I look at credentials, DN, and account state.

diagnose sniffer packet any 'host dc01.plant.example.com and port 636' 4 0 a

diagnose debug reset
diagnose debug application fnbamd -1
diagnose debug enable

diagnose test authserver ldap "AD-LDAPS" my.test.user MyTemporaryPassword

diagnose debug disable
diagnose debug reset

On a separate Ubuntu 22.04 jump host, I used OpenSSL and a small Python 3.11 script to compare the certificate chain FortiGate should have been seeing. That was not required to fix the FortiGate, but it helped my team document the incident without relying on guesswork.

You may also find this useful: Check out our guide on Python Network Config Backup: Automating Multi-Vendor Device Snapshots for more practical tips.

The fastest debug is the one that separates transport from identity.

My opinion: FortiGate LDAP troubleshooting should start with TLS visibility when LDAPS is enabled, because authentication cannot succeed across a trust failure.

Use the GUI Test Without Trusting It Blindly

The FortiGate GUI test is useful, but I treat it as a confirmation tool, not the primary diagnostic source. After importing the CA certificate and selecting it in the LDAP server profile, I used the GUI test with a known VPN user and watched the CLI debug at the same time. That combination told me whether the GUI result matched the underlying authentication flow.

One trap in our environment was group membership. The LDAP server test could validate a user bind, while the VPN policy still failed if the firewall user group mapping did not match the expected AD group. That was not the root cause of this outage, but I checked it anyway because manufacturing VPN access often depends on tight group scoping for vendors, engineers, and maintenance teams.

I also had my team test from two FortiGate appliances because HA and certificate objects can make people overconfident. The primary unit had the imported CA, the secondary had synchronized correctly, and both returned successful LDAP tests. Only then did we close the incident and stop treating the help desk tickets as password problems.

Green buttons need witnesses.

My opinion: the GUI test is valuable only when paired with logs or diagnostics that prove why it passed.

Fix Secure LDAP Before the Next Change Window

I changed our LDAPS rollout checklist after this incident. Any FortiGate LDAP profile using LDAPS now gets a certificate-chain check, a CA-store check, an FQDN check, and a real authentication test before the change is considered complete. I also added a note to verify FortiOS 7.4.3 behavior after firmware upgrades because certificate handling is not something I want rediscovered during a production VPN outage.

We also stopped describing this as “the FortiGate broke LDAP.” That phrasing was wrong. The firewall did exactly what a TLS client should do when it does not trust the issuer. Our process failed because we migrated the domain controllers to secure LDAP without giving every dependent client the CA chain required to trust it.

That distinction matters in a plant. When remote engineers cannot reach historian systems, PLC support tools, or vendor jump boxes, nobody wants a lecture about X.509. They want access restored, and they want the next maintenance window to avoid repeating the same failure.

My opinion: secure LDAP is worth doing, but half-migrated certificate trust is worse than plain LDAP because it looks secure right up until people need it.

LDAPS did not fail because Active Directory rejected us; it failed because FortiGate had no reason to trust our domain controller.

External References