FortiGate can request and renew its own TLS certificate over ACME, the protocol defined in RFC 8555. This guide covers the case most firewall administrators actually face: issuing that certificate from a commercial certificate authority rather than a free one, which means the FortiGate has to authenticate to the CA with External Account Binding credentials, a key ID and an HMAC key that your CA gives you.
Automating this is no longer a convenience. Under the CA/Browser Forum ballot SC-081v3, publicly trusted TLS certificates have been capped at 200 days since March 15, 2026, and the cap drops to 100 days on March 15, 2027 and to 47 days on March 15, 2029. A certificate you replace by hand twice a year becomes one you replace by hand eight times a year, then more. ACME removes that work.
Before you start
Check every item below before you touch the CLI. Most failed ACME enrollments on FortiGate come from one of these, not from the commands themselves.
- FortiOS 7.6.3 or later if your CA requires External Account Binding. The two fields that carry EAB credentials, acme-eab-key-id and acme-eab-key-hmac, were added in 7.6.3. They are absent from the 7.6.2 CLI reference, so 7.6.0, 7.6.1 and 7.6.2 will reject them even though those builds do support ACME itself.
- Administrator access to the FortiGate CLI, through the console, SSH, or the CLI console in the GUI. Fortinet documents the EAB fields only in the CLI, so this part is not a GUI job.
- Your CA’s ACME directory URL, EAB key ID and EAB HMAC key. You generate these in your CA’s account portal. Copy the HMAC key exactly as the CA presents it: FortiOS expects the base64url form (“URL-encoded base64” in Fortinet’s own wording), so re-encoding it will break the registration.
- A public IP address on the FortiGate and a hostname in DNS that resolves to it. The name you put in acme-domain has to point at this firewall.
- TCP/443 or TCP/80 free on the ACME interface. FortiOS prefers the TLS-ALPN-01 challenge on port 443 and falls back to HTTP-01 on port 80 when the HTTPS daemon already holds 443. If another service holds both, the ACME process fails.
- No virtual IP on those ports on that interface. Fortinet is explicit: if a VIP is in use on port 80 or 443, the incoming ACME challenge is processed by the VIP rather than by the ACME daemon.
- Loopback interfaces are not supported for the ACME listener.
Which of the two methods you need
The built-in client is simpler, but it is deliberately narrow. FortiOS fills the Subject Alternative Name field with the FortiGate’s own DNS hostname, and that field cannot be edited, cannot hold a wildcard, and cannot hold more than one name. So the choice is not a matter of taste.
- Use the native client (Method 1) when you need one certificate for one hostname, that hostname resolves to the firewall, and port 80 or 443 on the internet-facing interface is free.
- Issue externally with acme.sh (Method 2) when you need a wildcard, several names on one certificate, a custom management port with the web traffic redirected, or when the firewall runs a FortiOS build older than 7.6.3. The maintainers of acme.sh’s FortiGate hook name the same limits in the hook itself: native ACME “does not support wildcard certificates or domain validation, and is not supported if you have a custom management web port”.
One point of orientation before the steps, because most ACME material assumes a free CA. FortiGate’s ACME client is not tied to any particular certificate authority: the acme-ca-url field simply defaults to the Let’s Encrypt directory, and the two EAB fields are the part a commercial CA adds. Let’s Encrypt does not currently support EAB at all, so if that is your CA you use the same configuration with the two EAB lines omitted. Everything else on this page applies either way.
Method 1: Native FortiGate ACME with EAB
Step 1: Confirm the firmware supports EAB
Open the CLI and read the running version:
get system status
Look at the Version line. Anything from 7.6.3 upward, including the 8.0 releases, carries the EAB fields. On an older build the CLI rejects the acme-eab-key-id line in Step 4, so upgrade first or go to Method 2, which has no firmware requirement.
Step 2: Choose the interface that will answer the challenge
The ACME daemon does not listen anywhere until you nominate an interface. This step is missing from a lot of FortiGate ACME write-ups and it is the single most common reason validation never completes.
config system acme
set interface "port1"
end
Replace port1 with the interface that faces the internet on your unit, commonly wan1. You can list more than one interface, separated by commas, when the firewall is reachable on several. If the FortiGate reaches the internet through a specific source address, or sits in an HA cluster using ha-direct, the same block accepts source-ip, source-ip6 and use-ha-direct.
Step 3: Confirm the FortiGate can reach your CA
Validation runs in both directions. The CA has to reach your firewall, and your firewall has to reach the CA’s ACME endpoint. Test the outbound half by name, which also proves DNS resolution works on the device:
execute ping acme.yourca.example
Use the hostname from your CA’s directory URL. If the ping fails to resolve, fix DNS on the FortiGate before going further. If it resolves but gets no reply, check whether outbound ICMP is filtered upstream, then confirm HTTPS to the same host is permitted, since that is what the ACME client actually uses.
Step 4: Create the ACME certificate object
This is the configuration that ties everything together. Substitute your own certificate name, CA directory URL, contact address, EAB credentials and domain.
config vpn certificate local
edit "acme-fgt-cert"
set enroll-protocol acme2
set acme-ca-url "https://acme.yourca.example/v2/DV90"
set acme-email "[email protected]"
set acme-eab-key-id "YOUR_EAB_KEY_ID"
set acme-eab-key-hmac "YOUR_EAB_HMAC_KEY"
set acme-domain "vpn.example.com"
next
Do not paste the closing end statement with the rest of the block. When you enter next, FortiOS prints the CA’s terms of service and asks a yes or no question before it will accept the object:
By enabling this feature you declare that you agree to the Terms of Service at https://acme.yourca.example/v2/DV90
Do you want to continue? (y/n)y
Answer y, then type end. If you paste the whole block in one go, the word end is consumed as the answer to that prompt and the configuration is left half-applied.
What each field does:
- enroll-protocol acme2 selects ACME as the enrollment method. The other accepted values are none, scep, cmpv2 and est, which is worth knowing because a stray value here is a common copy-paste error.
- acme-ca-url is your CA’s ACME directory endpoint. It defaults to the Let’s Encrypt directory, so you must set it for any other CA.
- acme-email is the contact address the CA registers against the account.
- acme-eab-key-id and acme-eab-key-hmac are the External Account Binding credentials, described in RFC 8555 section 7.3.4. They link the ACME account to the account you already hold with the CA, which is how the CA knows the request is yours without a separate interactive verification.
- acme-domain is the name that goes into the certificate. It has to resolve to this FortiGate.
- acme-renew-window is optional and defaults to 30. It is the number of days before expiry at which renewal begins, and it accepts 1 to 100.
- acme-rsa-key-size is optional and defaults to 2048, with 4096 as the maximum.
Two fields you may have seen elsewhere do not belong here. There is no acme-auth-url field in any FortiOS release: the challenge path is not something you configure, because the ACME daemon serves it on the interface from Step 2. And auto-regenerate-days, which does exist in the same command, governs SCEP-style renewal rather than ACME. For ACME the renewal timer is acme-renew-window.
Step 5: Check that the certificate issued
Enrollment starts as soon as you accept the terms. Read the result with:
get vpn certificate local details acme-fgt-cert
A successful object shows the subject, the issuer, the validity dates and an ACME details block whose status line confirms the certificate has been issued or renewed and can be used. If the status is anything else, the fuller log is one command away:
diagnose sys acme status-full vpn.example.com
Note the argument: these diagnostics take the domain, not the name you gave the certificate object. The output is JSON, and the two fields worth reading first are errors and the detail string under last, which usually names the exact stage that failed. To see which domains the client is managing at all, run diagnose sys acme list-domains.
Step 6: Apply the certificate to a service
An issued certificate sits in the local certificate store doing nothing until you bind it. For the administrative web interface, which is the most common reason to want a publicly trusted certificate on a firewall, use the GUI at System > Settings and set HTTPS server certificate, or the CLI:
config system global
set admin-server-cert "acme-fgt-cert"
end
For remote access, be aware that the ground moved under this in the same release train that added EAB. SSL VPN tunnel mode was removed in FortiOS 7.6.3, from both the GUI and the CLI, and Fortinet’s replacement is IPsec VPN, which can be configured to listen on TCP port 443. Web mode survives under a new name, Agentless VPN, and it is what config vpn ssl settings now configures:
config vpn ssl settings
set servercert "acme-fgt-cert"
end
Agentless VPN is not available on every model either. Fortinet’s list is specific: FortiGate and FortiWiFi 40F and variants, 60F and 61F, the 2 GB versions of FGR-60F, and the 90G and 91G, where the feature is gone from both the GUI and the CLI, so on those units this command has nothing to bind to. Tunnel mode is a separate matter, removed from every model in 7.6.3 rather than from a list of them. Older guidance that sends you to a VPN > SSL-VPN Settings page for the server certificate predates these changes.
Step 7: Let renewal run, and test it once
Once the object is valid, FortiOS renews it without any scheduled task on your side, beginning acme-renew-window days before expiry. With the 200-day ceiling now in force that window matters more than it used to, because there is less slack between renewals. Prove the mechanism works rather than waiting to find out:
diagnose sys acme force-renew vpn.example.com
That is the supported way to trigger a renewal by hand. FortiOS has no execute command for renewing an ACME certificate: the execute vpn certificate local tree offers only export, generate with a key type, import tftp and verify, none of which drive an ACME order. Re-check the result afterwards with get vpn certificate local details, and remember that a renewed certificate keeps the same object name, so anything already bound to it keeps working.
Method 2: Issue with acme.sh and deploy through the FortiGate API
This path lifts every limit of the native client. You run the ACME client on a Linux, macOS or BSD host you already operate, and the certificate reaches the firewall over its REST API. That is the mechanism rather than a workaround: FortiOS offers no general-purpose shell to copy files into, and its only file-based import command expects a TFTP server, so copying a renewed certificate across by hand every time is not a workable plan.
Step 1: Install acme.sh on the host
curl https://get.acme.sh | sh -s [email protected]
source ~/.bashrc
The installer places the client in your home directory and adds a cron entry that checks daily for certificates due for renewal.
Step 2: Register the ACME account with EAB
acme.sh --register-account \
--server https://acme.yourca.example/v2/DV90 \
--eab-kid "YOUR_EAB_KEY_ID" \
--eab-hmac-key "YOUR_EAB_HMAC_KEY" \
-m [email protected]
Pass --server on every command. Since version 3.0 the default CA in acme.sh is ZeroSSL, so leaving it out sends the request to the wrong authority. If registration fails with a complaint that the HMAC key cannot be base64-decoded, the key was altered somewhere between the CA portal and your terminal, most often by a copy that dropped or added characters.
Step 3: Issue the certificate with DNS validation
Use DNS-01 here, not a webroot. The hostname resolves to the FortiGate, so a challenge file placed under a web root on a different machine is never reached: the CA asks the name, and the name answers from the firewall. DNS validation sidesteps that entirely and is also the only route to a wildcard.
With a DNS provider API configured, acme.sh publishes and removes the record itself. The example below uses Cloudflare; acme.sh ships integrations for most providers, each with its own variables.
export CF_Token="your_cloudflare_api_token"
acme.sh --issue --dns dns_cf \
-d vpn.example.com \
--server https://acme.yourca.example/v2/DV90
If your DNS is not scriptable, acme.sh has a manual mode that prints the TXT record for you to add by hand. Use it knowing that a manually validated certificate cannot renew unattended, which defeats the purpose of the exercise:
acme.sh --issue --dns \
-d vpn.example.com \
--server https://acme.yourca.example/v2/DV90 \
--yes-I-know-dns-manual-mode-enough-go-ahead-please
HTTP validation is still possible from an external host, but only if you deliberately forward it. Fortinet notes that a virtual IP on port 80 can be used on purpose to let a server behind the firewall take part in the ACME process. That is the legitimate use of the VIP below, and it is the opposite of putting one in front of the native client, which would break Method 1:
config firewall vip
edit "acme-http"
set extintf "wan1"
set extip 203.0.113.10
set mappedip "192.168.1.99"
set extport 80
set mappedport 80
set protocol tcp
next
end
Replace the external address with your own public IP and the mapped address with the host running acme.sh, and pair the VIP with a firewall policy that permits the traffic. Remove it once issuance is done unless you intend to keep validating this way.
Step 4: Create a REST API administrator on the FortiGate
Go to System > Administrators and choose Create New > REST API Admin. Only an account with the super_admin profile can create one. Give it an admin profile that allows writing certificates and system settings, and restrict Trusted Hosts to the address of the machine running acme.sh, since this token can change the firewall’s configuration.
The API token is displayed once, when you create it. Store it in a password manager at that moment; there is no way to read it back afterwards.
Step 5: Deploy the certificate to the firewall
export FGT_HOST="203.0.113.10"
export FGT_TOKEN="your_fortigate_api_token"
export FGT_PORT="443"
acme.sh --deploy -d vpn.example.com --deploy-hook fortigate
FGT_HOST and FGT_TOKEN are required; FGT_PORT is optional and defaults to 443, so set it only if the management interface listens elsewhere. On the very first run, while the firewall still presents its untrusted factory certificate, add --insecure so the deployment can complete. Drop the flag on later runs.
Because acme.sh stores the deploy hook against the domain, every future renewal re-runs it automatically. There is no cron job to write and no copy step to maintain.
What the hook does, and the one thing to watch
Knowing the sequence saves a lot of guessing when something looks wrong in the GUI afterwards. The hook uploads the certificate and private key, uploads the CA certificate as a separate call because FortiGate does not extract the intermediate from a full chain on its own, sets the new certificate as the administrative web interface certificate, and only then deletes the object it deployed the previous time.
The detail worth planning around is the naming. Each deployment creates a new certificate object whose name is the domain followed by a UTC timestamp, with an asterisk in a wildcard name replaced by WILDCARD_. So the object name changes at every renewal. The hook re-points the administrative interface for you, but any other binding you made by hand, to Agentless VPN or a load-balancing virtual server for example, still refers to the old object and will be left pointing at something that no longer exists. Either keep this certificate for the management interface only, or script the extra bindings alongside the deployment.
When enrollment fails
Work through these in order. The first three account for most cases.
- The challenge never arrives. Confirm an interface is set in config system acme, that the name in acme-domain resolves to the firewall’s public address from outside your network, and that no virtual IP occupies port 80 or 443 on that interface.
- The CA rejects the account. Re-copy the EAB key ID and HMAC key. Many CAs mint EAB credentials that are valid for a single account registration, so a second attempt after a failed one may need a fresh pair.
- The FortiGate cannot reach the CA. Re-run the ping from Step 3 and check outbound policy and DNS. In an HA cluster with ha-direct enabled, set use-ha-direct in config system acme so the client uses the management interface.
- A replay or rate-limit error. Wait a few minutes and retry. Nonce errors are transient by design, and repeated failed orders can trip a CA-side limit that clears on its own.
- You need the full picture. Turn on client logging, reproduce, then turn it off again.
diagnose sys acme debug enable level debug
diagnose sys acme status-full vpn.example.com
diagnose sys acme debug disable
If an order is stuck rather than failing, diagnose sys acme clear-staging clears the staged order for a domain, diagnose sys acme delete-cert removes the stored certificate for one, and diagnose sys acme restart restarts the client. The first two take the domain as their argument.
Once the certificate is live, confirm what the outside world sees with the SSL Checker, which reports the chain, the expiry date and whether the intermediate is being served. For error strings that come from the CA rather than from FortiOS, such as badNonce, unauthorized or a CAA refusal, see our guide to fixing ACME SSL certificate errors.
Frequently Asked Questions
FortiOS 7.6.3 or later. ACME itself has been in FortiOS since 7.0, but the External Account Binding fields that commercial authorities require, acme-eab-key-id and acme-eab-key-hmac, arrived in 7.6.3. On 7.6.2 and earlier the CLI does not recognise them, so the native client is limited to authorities that accept an unauthenticated account registration.
Not necessarily. FortiOS uses the TLS-ALPN-01 challenge on TCP/443 by default and only falls back to HTTP-01 on TCP/80 when the HTTPS daemon already holds 443. What matters is that one of the two ports is reachable from the internet on the ACME interface and is not being used by another service or intercepted by a virtual IP. The often-repeated claim that port 80 is required describes the fallback, not the default.
No. FortiOS fills the Subject Alternative Name field with the FortiGate’s own DNS hostname, and that field cannot be edited, cannot take a wildcard, and cannot hold additional names. A wildcard or a multi-name certificate has to be issued elsewhere and deployed to the firewall, which is what Method 2 on this page does.
No, and you do not need to. Let’s Encrypt does not currently support External Account Binding. Configure the certificate object exactly as shown but leave out the two EAB lines, and either set acme-ca-url to the Let’s Encrypt directory or omit it, since that directory is the field’s default value.
Look for a virtual IP added on port 80 or 443 on the ACME interface. When a VIP is in use on those ports the challenge is handled by the VIP instead of the ACME daemon, so validation fails even though nothing about the certificate configuration changed. The other common cause is a DNS change that moved the hostname away from the firewall’s public address.
That depends on the certificate authority. Some let one ACME account cover many devices, others issue credentials per order or per domain and treat a reused pair as an error. Check your CA’s policy before rolling the same key ID and HMAC key across a fleet, because hitting an account conflict on ten firewalls at once is considerably harder to unpick than on one.
It can, as long as the CA you buy from offers an ACME endpoint. The manual route is still the right one for certificates that ACME cannot issue on this platform, and for those you generate a request on the device and upload the signed files afterwards. See our guides to generating a CSR on FortiGate and installing an SSL certificate on FortiGate.
For more on the protocol itself and on which certificates can be automated this way, see what the ACME protocol is and our ACME SSL certificates page.
Save 10% on SSL Certificates when ordering from SSL Dragon today!
Fast issuance, strong encryption, 99.99% browser trust, dedicated support, and 25-day money-back guarantee. Coupon code: SAVE10

