Fortinet ACME certificates: FortiGate, FortiMail and FortiWeb
FortiOS 7.6.3 and later ships a built-in ACME client with External Account Binding, so FortiGate can fetch and renew certificates from FairSSL on its own. Older FortiGate versions use a proxy host running Lego or simple-acme that pushes the certificate in over SSH. Both approaches are covered here.
Which approach should you use?
Native ACME on FortiGate
FortiGate runs the ACME client itself. You register a local certificate object with EAB keys and FortiOS handles issuance and automatic renewal. No extra host required.
Go to native setup →Proxy via Lego or simple-acme
A small Linux or Windows host issues the certificate and pushes it to FortiGate via SSH or REST API. The same pattern also works for FortiMail and older FortiWeb, neither of which has native ACME.
Go to proxy setup →Native ACME (FortiOS 7.6.3+)
FortiGate\'s built-in ACME client supports External Account Binding, which is the requirement for fetching certificates from a commercial CA like FairSSL. Everything happens in the CLI.
Step 1: Verify firmware
Log in to FortiGate via SSH or the GUI console and check the version. EAB support requires 7.6.3 or later.
get system status Step 2: Configure FairSSL AutoDNS
Log in to your FairSSL control panel, go to AutoDNS and create a CNAME for
_dnsauth.<your-domain> pointing at the FairSSL DNS server. AutoDNS answers the
ACME challenge itself, so FortiGate does not need port 80 open to the internet. See the full
AutoDNS guide.
Step 3: Create the local ACME certificate object
Grab the EAB KID and HMAC key from the ACME tab in your FairSSL control panel. Replace the placeholders below and run the commands in the FortiGate CLI.
config vpn certificate local
edit "fairssl-vpn-cert"
set enrollment acme
set acme-ca-url https://fairssl.dk/acme
set acme-eab-key-id YOUR_EAB_KID
set acme-eab-key-hmac YOUR_EAB_HMAC
set acme-email acme-client@fairssl.dk
set acme-domain vpn.example.com
set acme-auth-url "http://vpn.example.com/.well-known/acme-challenge"
set auto-regenerate-days 30
set auto-regenerate enable
next
end auto-regenerate-days 30 means FortiGate will try to renew 30 days before expiry, which is
safe for both 90-day and 47-day certificates. Set acme-domain to the exact FQDN the
certificate is for. FortiGate\'s built-in ACME flow is intended for a single FQDN; use the proxy approach
for wildcards or multi-SAN.
Step 4: Issue the certificate
execute vpn certificate local generate "fairssl-vpn-cert" FortiGate contacts the FairSSL ACME server, validates ownership via AutoDNS and fetches the certificate. The whole flow normally finishes in under 30 seconds.
Step 5: Verify status
get vpn certificate local | grep -A 3 "fairssl-vpn-cert"
Expect Status: OK and a validity period matching the certificate lifetime (typically 47-90 days).
Step 6: Bind the certificate to services
Use the local certificate object for SSL VPN, admin HTTPS or other services. You can bind the same object to multiple services. All bindings get the renewed certificate automatically.
# SSL VPN
config vpn ssl settings
set servercert "fairssl-vpn-cert"
end
# Admin HTTPS (optional)
config system global
set admin-server-cert "fairssl-vpn-cert"
end Verify with FairSSL\'s SSL scanner that the right certificate is being served on the public FQDN.
Proxy setup (FortiGate < 7.6.3, FortiMail, older FortiWeb)
When the appliance cannot run ACME itself, we issue the certificate on a separate Linux or Windows host and push it over via SSH or REST API. The Linux + Lego variant is the most flexible and is also used for FortiMail and older FortiWeb.
Linux host with Lego and SSH
A small Linux server (Debian, Ubuntu or RHEL) issues the certificate via Lego v5.0.4+ and pushes the PEM
files to FortiGate over SSH using sshpass. FairSSL\'s commands require Lego v5.0.4 or newer:
early v5 versions had registration and hook problems, and older v4 commands do not match these examples.
# 1. Install Lego and sshpass
arch=$(dpkg --print-architecture)
case "$arch" in amd64|arm64) ;; *) echo "Adjust arch"; exit 1 ;; esac
version=$(curl -fsSL https://api.github.com/repos/go-acme/lego/releases/latest \
| sed -n 's/.*"tag_name": "\(v[^"]*\)".*/\1/p' | head -1)
curl -fL "https://github.com/go-acme/lego/releases/download/${version}/lego_${version}_linux_${arch}.tar.gz" \
| sudo tar -xz -C /usr/local/bin lego
sudo apt-get install -y sshpass
sudo install -d -m 0700 /etc/ssl/fortigate # 2. Create deploy script (save as /usr/local/bin/fortigate-deploy.sh)
#!/bin/bash
set -euo pipefail
FGT_IP="10.0.0.1"
FGT_USER="admin"
FGT_PASS="YOUR_SSH_PASSWORD"
CERT_NAME="fairssl-vpn-cert"
CERT=$(cat "$LEGO_HOOK_CERT_PATH")
KEY=$(cat "$LEGO_HOOK_CERT_KEY_PATH")
SSH="sshpass -p $FGT_PASS ssh -T -o StrictHostKeyChecking=no $FGT_USER@$FGT_IP"
$SSH <<EOF
config vpn certificate local
edit "$CERT_NAME"
set private-key "$KEY"
set certificate "$CERT"
next
end
config vpn ssl settings
set servercert "$CERT_NAME"
end
EOF # 3. Issue the certificate (EAB keys only needed on first run)
sudo /usr/local/bin/lego run \
--domains "vpn.example.com" \
--server https://fairssl.dk/acme \
--eab --eab.kid YOUR_EAB_KID --eab.hmac YOUR_EAB_HMAC \
--accept-tos --email acme-client@fairssl.dk \
--http --http.webroot "/var/www/html" \
--path "/etc/ssl/fortigate" \
--deploy-hook "/usr/local/bin/fortigate-deploy.sh" # 4. Cron renewal, run daily at a random time
# /etc/cron.d/lego-fortigate (example)
17 04 * * * root /usr/local/bin/lego run \
--domains "vpn.example.com" \
--server https://fairssl.dk/acme \
--accept-tos --email acme-client@fairssl.dk \
--http --http.webroot "/var/www/html" \
--path "/etc/ssl/fortigate" \
--renew-days 7 \
--deploy-hook "/usr/local/bin/fortigate-deploy.sh"
Lego v5+ runs run daily and uses ARI to decide when to renew. If the ACME server is
unreachable or ARI does not flag a renewal, it falls back to renewing when 7 days remain. The deploy hook
only fires when an actual renewal happens.
Windows host with simple-acme
If you do not have a Linux server, run the setup on Windows with simple-acme and Posh-SSH. Install
FairSSL\'s simple-acme build
(pre-configured with the right settings and ships Deploy-FortiGate.ps1 in the Scripts folder).
# PowerShell, Run as Administrator
Install-Module Posh-SSH, CredentialManager -Force -Scope CurrentUser
New-StoredCredential -Target "FortiGate-acme-user" `
-UserName "admin" -Password "YOUR_SSH_PASSWORD" -Persist LocalMachine
cd C:\simple-acme
wacs.exe --verbose --baseuri "https://fairssl.dk/acme" `
--eab-key-identifier YOUR_EAB_KID --eab-key YOUR_EAB_HMAC --accepttos `
--source manual --host "vpn.example.com" --validation none `
--store pemfiles --pemfilespath "C:\simple-acme\Certificates" `
--pemfilesname "vpn-example-com" --friendlyname "fairssl-vpn-cert" `
--installation script --script ".\Scripts\Deploy-FortiGate.ps1" `
--scriptparameters "-StorePath 'C:\simple-acme\Certificates' -FilePrefix 'vpn-example-com' -FirewallHost '10.0.0.1' -CertLabel 'fairssl-vpn-cert' -CredentialTarget 'FortiGate-acme-user'"
Add -UpdateAdmin inside --scriptparameters if admin HTTPS should use the
certificate as well. simple-acme creates a scheduled task that runs daily and renews when ARI says so or
when fewer than 7 days remain.
FortiMail and FortiWeb
FortiMail and older FortiWeb use the same pattern as FortiGate proxy: an external host issues the certificate and a deploy hook pushes it in via the REST API. The ACME Easy Guides section of the FairSSL control panel ships finished scripts for FortiMail (Linux + Windows) and FortiWeb (Windows). The logic and structure mirror the FortiGate examples above; only the concrete API calls and the install endpoint differ.
Troubleshooting
Status stays Pending
Check that _dnsauth.<domain> is set up as a CNAME pointing to FairSSL\'s DNS server. Verify with dig _dnsauth.your-domain.com CNAME. Retry with execute vpn certificate local renew <name>.
Lego returns account does not exist
Most likely an old Lego version. Upgrade to v5.0.4+ and re-run the issuance command with the same --path. If the error persists, delete /etc/ssl/fortigate/accounts and re-run step 3 with the EAB keys.
SSH deploy fails with Host key verification failed
First time round you have to accept FortiGate\'s SSH host key. Run ssh admin@<fortigate-ip> manually from the deploy host and confirm the fingerprint. Or add the host key to ~/.ssh/known_hosts ahead of time.
SSL VPN still shows the old certificate after deploy
SSL VPN caches the certificate in memory. FairSSL\'s full deploy script in the control panel handles this by setting servercert to self-sign first and then back to the new certificate. If you use a simpler script of your own, the same swap has to be in there too.
Frequently asked questions
Find answers to the most common questions about SSL certificates and FairSSL.
acme-auth-url field still has to be set because the FortiOS CLI requires it, but the HTTP-01 flow is never actually used as long as the domain is set to AutoDNS validation in your FairSSL control panel.get vpn certificate local in the CLI. The status should read Status: OK with a valid expiry date. If it stays Pending, check that the domain is set up correctly under AutoDNS in your FairSSL control panel, then run execute vpn certificate local renew <name>.auto-regenerate-days to 30, FortiGate renews the certificate 30 days before expiry, which is safe for both 90-day and 47-day certificates. For full ARI awareness the proxy approach via Lego or simple-acme is still more flexible.--path. Keep the EAB keys in a safe place as a backup anyway, in case you ever need to rebuild the setup.config vpn ssl settings, set servercert "name", end. Admin HTTPS: config system global, set admin-server-cert "name", end. When the certificate is renewed, both bindings get the new one automatically.Ready to automate FortiGate certificates?
Create a free account and issue your first certificate in under 10 minutes.