Profiles and credentials
One profile per FortiGate - create the first, add, edit, rename and copy more, choose where each API key comes from, and check them with forti doctor.
Profiles and credentials
forti keeps one profile per FortiGate. A profile holds how to reach the device (host, port, VDOM,
TLS files), its operating mode and a reference to its API key, never the key itself. All profiles
live in one file, ~/.forti/config.json in the forti home,
so managing ten firewalls is the same as managing one.
Your first profile
1forti configure
forti asks for a profile name (Enter takes default), the host, the admin HTTPS port, an optional
VDOM, CA bundle, connect-to address and client certificate, where the API key comes from (it is typed
with echo off), and the operating mode.
Before saving, it checks the profile with one read of monitor/system/status. If the check fails,
nothing is written; when it fails at the terminal for a network reason, forti offers to save the
profile anyway, so a typed key is not lost. The first profile becomes the default.
FortiOS locks an API user out after a few failed attempts, so forti configure makes one attempt and
stops on a rejected key. Check the key, the API user's trusted hosts and its admin profile first.
Non-interactively (scripts, provisioning), pass --profile and the settings. Pick where the API key
comes from:
The key comes from standard input and is kept in the secret store.
1op read op://ops/fw01/api-key | forti configure --profile fw01 --host fw01.example.net \2 --ca-bundle ~/certs/fw01-ca.pem --api-key-stdin --defaultThe key stays in an environment variable; only the variable's name is saved.
1forti configure --profile lab --host 192.0.2.10 --port 8443 --api-key-env FORTI_LAB_KEYThe key is printed by a command (run without a shell; quotes group words).
1forti configure --profile edge --host edge.example.net --api-key-command "pass show fortigate/edge"--skip-check saves without contacting the device (for example while it is offline).
Several FortiGates
Every command works on one profile: the one named by --profile/-p, else the default profile, else
the only one.
1forti devices # every profile; the default is marked *2forti -p fw02 get monitor/system/status3forti get firewall/policy --profile branch-ber --fields policyid,name,action4forti profile default fw02 # change which one commands use by default
Add another device
Most FortiGates in a fleet share settings: port, VDOM, CA bundle, timeouts, mode. Start the new profile from an existing one, change what differs, then give it its own key:
1forti profile copy fw01 fw02 # settings only: no API key, no client certificate2forti profile set fw02 host=fw02.example.net3forti configure --profile fw02 # prompts, prefilled from fw02; type fw02's key
profile copy never copies the API key or the client certificate and its password: they belong to the
source device. It does copy everything else, including resolve_ip and ca_bundle, so check that
those fit the new device (forti profile show fw02). To give the new device a client certificate, run
forti cert create --profile fw02 (Client certificates).
Change settings
forti profile set changes settings without prompts and without contacting the device:
1forti profile set fw02 host=fw02.example.net port=84432forti profile set lab vdom=root timeout_ms=600003forti profile set lab vdom= # an empty value restores the default
| Key | Value |
|---|---|
host | host name or address, without scheme or port |
port | admin HTTPS port, 1 to 65535 |
vdom | VDOM for every request |
ca_bundle | PEM CA bundle (checked for readability, stored as an absolute path) |
client_cert | PKCS#12 client certificate; clearing it (client_cert=) also clears its password |
resolve_ip | connect to this IPv4 or IPv6 address while verifying TLS for host |
connect_timeout_ms, timeout_ms | 1 to 3600000 |
backup_before_critical | true or false |
mode | read-only, guarded or autonomous |
Values are checked exactly as config.json is when it loads. Secrets are not settings: an API key or a
password goes through forti configure. Changing host or port re-binds the profile's stored
secrets to the new address (only secrets bound to the old address; see
Stored keys are bound to their device).
To change settings and check them against the device, or to change where the key comes from, run
forti configure --profile NAME. On an existing profile, every prompt starts from the current value
(including an API key command), and Enter keeps it. With options instead of prompts, only the
options you give change:
1forti configure --profile fw01 --port 8443 # checks the device, keeps everything else
Rename a profile
1forti profile rename lab fw-lab
The rename takes everything along: stored secrets (Keychain or file store) move to the new name and
stay bound to the same device, ~/.forti/certs/lab/ moves to certs/fw-lab/ with every profile path
that pointed into it, and the default follows. A certificate made by forti cert create keeps its CN
(forti-lab), so the FortiGate needs no change. Plans made before the rename name the old profile:
plan them again.
Remove a profile
1forti profile remove fw-lab
remove deletes the profile's stored secrets first; if the secret store refuses (a locked keychain),
the profile stays so you can remove it later. When the default profile is removed and exactly one
profile remains, that one becomes the default. Removing a profile never contacts the device, and it
does not delete certs/<profile>/ (use forti cert remove first).
Several devices and AI agents
forti-mcp reads the same profiles. An agent lists them with forti_discover {"what": "devices"} and
names one in each call, so one registration covers every FortiGate, each under its own mode. To pin
forti-mcp to one profile, register it with forti install mcp --profile NAME; see
AI agent integration.
Profile commands
1forti profile list # also: forti profile, forti devices2forti profile show [NAME] # settings and where the key comes from (never the key)3forti profile set NAME key=value...4forti profile rename OLD NEW5forti profile copy SOURCE NEW6forti profile default NAME7forti profile mode [NAME] guarded # read-only | guarded | autonomous8forti profile remove NAME
profile showreports the key's source and, for the secret store, whether a key is stored and bound to this host and port (api_key_stored,api_key_bound); for an environment source, whether the variable is set (api_key_set).profile modewithout a name changes the selected profile (--profile, else the default).- No profile command contacts the device.
configure options
| Option | Meaning |
|---|---|
--profile NAME | profile name: 1 to 64 letters, digits, - and _ (required with other options) |
--host HOST | host name or address. No scheme and no port: fw.example.net, 192.0.2.1, [2001:db8::1] |
--port N | admin HTTPS port, 1 to 65535 (default 443) |
--vdom NAME | VDOM for every request (default: the device's default VDOM) |
--ca-bundle PATH | PEM file of CA certificates that verify the device's certificate |
--resolve-ip IP | connect to this IPv4 or IPv6 address, while still verifying the certificate for --host |
--client-cert PATH | PKCS#12 client certificate, when the device requires one |
--api-key-stdin / --api-key-env VAR / --api-key-command CMD | where the API key comes from (exactly one) |
--cert-password-stdin / --cert-password-env VAR / --cert-password-command CMD / --cert-password-file PATH | the client certificate's password (needs a client certificate) |
--mode MODE | read-only (default), guarded or autonomous |
--default | make this the default profile |
--skip-check | save without the verification read |
A new profile needs --host and an API key source. Paths are checked for readability and stored as
absolute paths, so later commands do not depend on the working directory. Standard input carries only
one secret: with --api-key-stdin, take the certificate password from another source.
Choosing autonomous prints a notice of what that mode allows. Read
Choosing autonomous mode first.
Credential sources
An API key is never accepted as an argument value, because argument lists are visible to other users and end up in shell history. It comes from one of these sources:
| Source | Stored in config.json | Where the secret lives |
|---|---|---|
secret store (--api-key-stdin, or typed at the prompt) | {"source": "store"} | macOS: the login Keychain, service forti, account <profile>/api-key. Elsewhere: ~/.forti/credentials/<profile>.api-key, mode 0600 |
environment (--api-key-env VAR) | {"source": "env", "env": "VAR"} | the variable, read on every use |
command (--api-key-command CMD) | {"source": "command", "command": [argv...]} | whatever the command reads; the first line of its output is the key |
file (--cert-password-file PATH) | {"source": "file", "path": "/abs/path"} | a file only you can read; its first line is the secret |
A client certificate password uses the same sources, under the account <profile>/cert-password. The
file source has a forti configure option for the certificate password only (forti cert create
sets it up); in config.json it works for the API key as well. A file source must be a regular file,
not a symbolic link, owned by you, with no access for group or others (chmod 600).
A credential command runs on every use, without a shell (found on PATH), with standard input from
/dev/null. Its standard error reaches you, so a password manager can ask to be unlocked on the
terminal. It must exit with status 0.
Stored keys are bound to their device
A key in the secret store is bound to the profile's host:port. forti releases it only to a profile
that still points there, so another config.json that reuses the profile name cannot send your key to
a different host. forti configure writes the binding, and both forti configure and
forti profile set re-bind a kept key when you change the host or port. Keys from the environment, a
command or a file are not stored, so they are not bound. See
Security notes.
FORTI_SECRET_STORE=file selects the 0600 file store on macOS too, for example on a Mac reached over
SSH where the login keychain is locked. FORTI_SECRET_STORE=keychain asks for the Keychain
explicitly. Use the same setting for configuring and for running commands. On Linux the file store is
the default.
macOS Keychain notes
- The item belongs to the application that created it. Another binary, such as
forti-mcp, orfortiafter an upgrade, triggers a one-time macOS prompt. Choose Always Allow. - Without a GUI session (SSH, launchd jobs) macOS cannot show that prompt, and a locked keychain
cannot be unlocked interactively. forti reports this as a configuration error with a hint. Unlock
with
security unlock-keychain, or use--api-key-env,--api-key-commandorFORTI_SECRET_STORE=file. - Inspect or delete items in Keychain Access by searching for
forti.
TLS
Certificate verification is always on, and no option, environment variable or config key disables it.
A FortiGate with a self-signed or private-CA certificate needs one of:
--ca-bundle PATH: export the device's CA certificate (or its self-signed certificate) as PEM and point the profile at it.--resolve-ip IP: when the certificate names a host (fw01.example.net) that does not resolve from where you are, keep that name as--hostand connect to the address with--resolve-ip. The certificate is still checked against the name.
forti configure and forti doctor tell a DNS failure, a refused connection, a timeout, an untrusted
certificate and a failed handshake apart, and suggest the matching fix.
For a FortiGate that should also require a client certificate from forti, see Client certificates.
Checking a profile
1forti doctor [--profile NAME]
Ordered checks, each reported as ok, warn, fail, skip or info:
| Check | What it verifies |
|---|---|
home | the forti home; a pre-0.2 setup is a warning, with forti home migrate as the fix (Configuration files) |
config | the configuration directory and config.json exist; the files are private (credential files readable by others fail, loose directory or config modes warn) |
profile | the selected profile loads and is valid |
secrets | the API key (and certificate password) resolve. Values are never shown |
tls_files | the CA bundle and client certificate exist and are readable; the bundle contains a PEM certificate |
connection | DNS, TCP and TLS, through one read of monitor/system/status |
auth | the device accepts the key (one attempt, never retried) |
version | the device's FortiOS version against the SDK schema version (a mismatch is a warning: reads work, attributes added or removed between versions may be missing or rejected in plans) |
mode | the operating mode |
privileges | autonomous profiles only: whether the account looks super_admin-class (read from system/api-user) |
mcp | whether forti-mcp is on PATH (information only) |
Later checks are skipped when an earlier one they depend on failed. The exit status is 0 when
nothing failed, otherwise that of the first failure (3 for configuration, 4 for connection or
authentication). With --json, a failing run returns ok: false with the checks in
error.details.checks. Run it once per device: forti doctor -p fw02.
Where profiles, secrets, certificates and plans are stored: Configuration files.