Valkyrian Labs logo

Configuration files

The forti home ~/.forti and its layout, overrides, migrating a pre-0.2 setup, file modes, config.json keys and environment variables.

Configuration files

Where forti keeps profiles, secrets, certificates, plans, approvals, backups and the audit log, and the keys of config.json. Everything lives in one directory, the forti home. Nothing depends on the working directory.

The forti home

The forti home is $FORTI_HOME, else ~/.forti (mode 0700, macOS and Linux alike). forti-mcp uses the same home, so both see the same profiles.

1~/.forti/                        0700   the forti home ($FORTI_HOME)2├── config.json                  0600   profiles and the default profile; no secrets3├── credentials/                 0700   the file secret store4│   └── <profile>.api-key        0600   (and <profile>.cert-password)5├── certs/<profile>/             0700   forti cert create: ca.crt, ca/ca.key, client.crt, client.p12, client.pass6├── state/                       0700   plans, approvals, backups, the audit log7│   ├── plans/  approvals/  backups/8│   └── audit.jsonl9└── cache/                              reserved

After forti install mcp --scope workspace, the home is also the MCP workspace: it then holds AGENTS.md, CLAUDE.md, .mcp.json, .claude/, .codex/, .agents/ and .forti-workspace.json as well.

forti home shows every location in use, the secret store, and any override:

1$ forti home2forti home: ~/.forti3  config       ~/.forti/config.json4  credentials  ~/.forti/credentials/  file store5  state        ~/.forti/state/  plans, approvals, audit log, backups6  cache        ~/.forti/cache/7  certs        ~/.forti/certs/  forti cert create8  secrets      macOS Keychain (service "forti")

With --json, data holds the same (home, config_dir, config_file, credentials, state, cache, certs, secret_store, legacy, overrides).

Overrides

LocationResolved as
home$FORTI_HOME, else ~/.forti
configuration (config.json, credentials/)$FORTI_CONFIG_DIR, else the pre-0.2 location while one is in use, else the home
state$FORTI_STATE_DIR, else the pre-0.2 location while one is in use, else <home>/state
cache$FORTI_CACHE_DIR, else the pre-0.2 location while one is in use, else <home>/cache
certificatesalways <home>/certs

FORTI_HOME moves everything at once; the FORTI_*_DIR variables move one location each. All of them must be absolute paths (a relative one is a config error).

Pre-0.2 setups

Releases before 0.2 used the XDG locations: ~/.config/forti (configuration and credentials), ~/.local/state/forti and ~/.cache/forti, or $XDG_CONFIG_HOME/forti, $XDG_STATE_HOME/forti and $XDG_CACHE_HOME/forti when those are set to absolute paths. Such a setup keeps working: forti uses it while all of these hold:

  • FORTI_HOME and FORTI_CONFIG_DIR are not set;
  • ~/.forti/config.json does not exist;
  • the old config.json does.

forti home then adds a legacy: line, and the home check of forti doctor warns. New certificates go to ~/.forti/certs either way. Move the setup with one command:

1forti home migrate
  • It moves credentials/, the state directory and the cache into the home, then config.json last: until config.json is in the home, forti keeps using the old locations, so an interrupted migration leaves a working setup, and running the command again finishes it.
  • Items are renamed (or, across file systems, copied, renamed into place and then deleted), so their contents and modes are kept.
  • It never overwrites anything. When ~/.forti/config.json, or another destination that is not an empty directory, already exists, it moves nothing and lists the conflicts. Move the old setup or the conflicting files aside and run it again.
  • It refuses while FORTI_CONFIG_DIR, FORTI_STATE_DIR or FORTI_CACHE_DIR is set, because forti then uses that directory, not the home. Unset the variable (and move that directory yourself if you want it in the home).
  • Other files in ~/.config/forti (a CA bundle a profile refers to, for example) stay where they are and are listed; profiles keep pointing at them. The old directory is removed once it is empty.
  • Keychain items need no change: their account names stay the same.
  • Running it again is harmless: "already migrated: nothing to move".

Then run forti home and forti doctor.

Files

FileModeContents
~/.forti/config.json0600, directory 0700profiles and the default profile; no secrets
~/.forti/credentials/<profile>.api-key, <profile>.cert-password0600, directory 0700the file secret store: a binding host:port line, then the secret
macOS login Keychain, service fortiKeychain ACLthe macOS secret store: accounts <profile>/api-key and <profile>/cert-password
~/.forti/certs/<profile>/0700made by forti cert create: ca.crt and client.crt (0644), ca/ca.key, client.p12 and client.pass (0600). See Client certificates
~/.forti/state/plans/<id>.json0600, directory 0700stored plans, including rollback plans, with real values
~/.forti/state/plans/<id>.applied0600marks a plan as applied: a plan runs at most once
~/.forti/state/approvals/<id>.json0600, directory 0700a guarded-mode approval: plan id, digest, approved_at, expires_at, approved_by
~/.forti/state/approvals/<id>.used0600an approval that an apply has consumed
~/.forti/state/backups/<plan id>.conf0600, directory 0700configuration backups taken before critical plans (the 20 most recent are kept)
~/.forti/state/audit.jsonl0600one JSON line per apply attempt and per approval; attribute names, never values
No automatic cleanup

Plans, approvals and backups are never cleaned up automatically, apart from the backup limit. Deleting old plan files is safe once you no longer need them.

The cache directory is resolved but not written in this release: forti discover --live reads the device's schema each time.

Permissions forti enforces

  • config.json is refused when another user owns it, when group or others can write it, or when its directory is writable by group or others (without the sticky bit).
  • A credential file, and a file named by a file credential source, is refused when it is a symbolic link, is not a regular file, is owned by another user, or is accessible by group or others. Fix it with chmod 600.
  • forti doctor reports loose modes on the configuration directory, config.json and credentials/ with the chmod that fixes them.

config.json

1{2  "version": 1,3  "default_profile": "fw01",4  "profiles": {5    "fw01": {6      "host": "fw01.example.net",7      "port": 443,8      "vdom": "root",9      "ca_bundle": "/Users/me/certs/fw01-ca.pem",10      "mode": "read-only",11      "api_key": {"source": "store"},12      "client_cert": "/Users/me/.forti/certs/fw01/client.p12",13      "cert_password": {"source": "file", "path": "/Users/me/.forti/certs/fw01/client.pass"}14    },15    "lab": {16      "host": "192.0.2.10",17      "port": 8443,18      "mode": "guarded",19      "api_key": {"source": "env", "env": "FORTI_LAB_KEY"}20    }21  }22}

forti configure, forti profile and forti cert write this file atomically. You can edit it by hand; keep it 0600.

Unknown keys are dropped

Keys other than those below are dropped the next time forti writes the file. A profile that contains a secret value (token, password, api_key_value) is rejected.

KeyDefaultMeaning
hostrequiredhost name or address, without scheme or port
port443admin HTTPS port
vdomthe device's defaultVDOM for every request
ca_bundlethe system trust storeabsolute path of a PEM CA bundle
client_certnoneabsolute path of a PKCS#12 client certificate
resolve_ipnoneconnect to this address while verifying TLS for host
moderead-onlyread-only, guarded or autonomous
api_key{"source": "store"}{"source": "store"}, {"source": "env", "env": "VAR"}, {"source": "command", "command": ["argv", "..."]} or {"source": "file", "path": "/absolute/path"}
cert_passwordnonethe client certificate's password, same forms as api_key
backup_before_criticaltruetake a configuration backup before a critical-risk plan runs (and refuse the plan if it cannot be taken)
connect_timeout_ms5000connection timeout
timeout_ms30000whole-request timeout

backup_before_critical, connect_timeout_ms and timeout_ms have no forti configure option; set them with forti profile set NAME key=value or here.

Environment

VariableEffect
FORTI_HOMEthe forti home (default ~/.forti; an absolute path)
FORTI_CONFIG_DIR, FORTI_STATE_DIR, FORTI_CACHE_DIRoverride one location inside it (Overrides; absolute paths)
XDG_CONFIG_HOME, XDG_STATE_HOME, XDG_CACHE_HOMEused only to find a pre-0.2 setup
FORTI_SECRET_STOREfile or keychain. Default: the Keychain on macOS, the file store elsewhere
FORTI_OUTPUT=jsonalways print the JSON envelope (same as --json)
NO_COLORno ANSI color (color is used only when the output is a terminal)
HOMEthe base of the default home, ~/.forti
USERrecorded as cli:$USER in approvals