Support Forum
The Forums are a place to find answers on a range of Fortinet products from peers and product experts.
New Contributor

FortiADC - upload PFX certificate with PowerShell


I am working on a PowerShell script for the Forti-ADC to upload PFX-certificates. The API is working fine and I can GET information with API-calls from the Forti-ADC with PowerShell. The only thing I cannot get to work is uploading a certificate

I found a way to upload certificates via Bash, as this is written in the manual ( This is working fine, but I could not succeed in doing the same with PowerShell.


The request looks like this (session details not added).

$ClearTextPfxPassword = "password"
$URI = ""

$Body = @{}
$Body.add("vdom", "root")
$Body.add("mkey", "ScriptName")
$Body.add("type", "PKCS12")
$Body.add("passwd", $ClearTextPfxPassword)
$Body.add("cert", "certificateFilename.pfx")
$ResultUploadCertificate = Invoke-webrequest -Uri $URI -Method Post -Headers $Headers -form $Body -WebSession $session -SkipCertificateCheck -verbose

The response looks like this:
PS Microsoft.PowerShell.Core\FileSystem::> $ResultUploadCertificate
StatusCode : 200
StatusDescription : OK
Content : {"payload":-2001}
RawContent : HTTP/1.1 200 OK
Date: Tue, 30 Feb 2022 07:42:01 GMT
Connection: keep-alive
Set-Cookie: last_access_time=1644444444; Path=/; SameSite=strict; HttpOnly; Secure
X-XSS-Protection: 1; mode=block
Headers : {[Date, System.String[]], [Connection, System.String[]], [Set-Cookie, System.String[]], [X-XSS-Protection, System.String[]]…}
Images : {}
InputFields : {}
Links : {}
RawContentLength : 17
RelationLink : {}

The bash variant looks like this and is working:
curl -v -F 'mkey=ScriptTestName' -F 'vdom=root' -F 'type=PKCS12' -F 'passwd=password' -F 'cert=@certificateFilename.pfx' -H "Authorization: Bearer xxxxxxxxxxxxxxxxxxxxx" -H "Cookie: last_access_time=164444444" --insecure


I tried several things, like:

  • using absolute path (for now PFX-certificates is in same folder to keep it simple), but not sure if I should use the '@' which is used in the bash-script.
  • different file notations in body (relative/absolute path, with '@', etc...)

Is someone able to inform what is wrong with the PS-script and how I can get it to work?

Not applicable

Hello @BJBee ,

Thank you for posting to Fortinet Community Forums. We would be having someone answer this query on the post. Thank you for your patience.

Not applicable

Hello @BJBee,

Can you confirm if the WAF is enabled on the device?



Hi BJBee. When using Postman to upload certificate files, I've had to add this HTTP header. Maybe the bash script does it automatically and the PowerShell does not?
Content-Type: "multipart/form-data; boundary=--------------------------"

The reason is that the POST consists of both an HTML form with the parameters, and then the binary part being the upload of the actual PFX file.

If you enable HTTP management on ADC, and disable redirect to HTTPS, you can make a packet capture to see what the HTTP request actually looks like, and see if this may be a hint.

Systems Engineer, CSP, Nordics
New Contributor

I have been struggling to get this to work, but finally did. Below is the script I am using to login and upload the PFX via the API in Powershell. The important things to note is the FormDataTemplate section that manually crafts the multipart/form-data body. Also, the encoding for the file is important, I use ISO-8859-1 because it is encoded using a single byte unlike UTF-8 and others. I tried all sorts of ones until I found ISO-8859-1. 


$PFXFilePath = 'C:\temp\cert.pfx'
$PFXFileName = 'cert.pfx'
$PFXPassword = "yourpfxpassword"

$ADCCertName = "thenameofthecertintheADC"
$ADCUsername = "YourADCUser"
$ADCPassword = "yourADCUSerPassword"
$ADCHostname = "youradchostname"

$loginheaders = @{
'Content-Type' = 'application/json'
'Accept' = 'application/json'
$loginbody = @{

$LoginRequest = Invoke-WebRequest -Uri "https://$ADCHostname/api/user/login" -Headers $loginheaders -Method Post -Body ($loginbody|ConvertTo-Json) -SessionVariable session

$token = (ConvertFrom-Json $([String]::new($LoginRequest.Content))).token

$boundary = "Thisistheuniqueboundarystring"
$UploadHeaders = @{
'Content-Type' = "multipart/form-data; boundary=$boundary"
'Accept' = '*/*'
'Authorization'='Bearer ' + $token

$PFXFileBytes = [System.IO.File]::ReadAllBytes($PFXFilePath)
$enc = [System.Text.Encoding]::GetEncoding("ISO-8859-1")
$PFXFileEncoded = $enc.GetString($PFXFileBytes)

#creating the formdata manually
$FormDataTemplate = "
Content-Disposition: form-data; name=""mkey""

Content-Disposition: form-data; name=""vdom""

Content-Disposition: form-data; name=""type""

Content-Disposition: form-data; name=""passwd""

Content-Disposition: form-data; name=""cert""; filename=""$PFXFileName""
Content-Type: application/octet-stream


$UploadRequest = Invoke-webrequest -Uri "https://$ADCHostname/api/upload/certificate_local" -Headers $UploadHeaders -Body $FormDataTemplate -Method Post -WebSession $session


You shouldn't have any problems with the above and it should all work, this was built for Powershell v5 but tested and worked in v7 as well. The spacing between the boundaries is important and probably should not be changed. If you are getting a payload -2001 or payload multipart upload fail, then the formatting of the multipart body is wrong and has an extra newline, space, etc. If you are getting a payload -167 then that means it can't decrypt and use the PFX, so that means the encoding on the file is wrong or the password is incorrect. At least this is what I found those errors to mean. I don't know what the official meaning is, it would be nice if the documentation was a little more robust for things like that.

New Contributor

Has the uri for the certificate upload changed in recent firmware?  I have a FortiADC on v7.4 and api/upload/certificate_local returns a 404.  I don't have access to the Fortinet developer portal to see any recent API documentation.

New Contributor

Finally back around at working on this, and not sure why I was getting a 404 -  but kudos to securityOTC for this script, it works perfectly and I'll be using it to upload ACME issued wildcard certificates.


A hint I'll leave for the next person who finds this thread.  If you can't find the API method to use, or the parameters its expecting, open your browser Dev Tools and capture the actions through the webui.  It will capture the URIs and the parameters / JSON that you need to pass.

Top Kudoed Authors