Skip to content
Avanet

Export and import Sophos Firewall via the Central API

Since Sophos Central Firewall Management 2026.29, configurations from firewalls running SFOS 22.0 MR2 or later can be exported and imported via the REST API. The process consists of a few steps: authenticate API access, determine the firewall ID, start the export or upload, and check the returned transaction until completion.

An import changes the target firewall; it is not the same as restoring a backup. First create a current firewall backup, run the process on a test firewall, and then validate the result locally.

Prepare prerequisites and API variables

The following are required:

  • a firewall running SFOS 22.0 MR2 or later that is connected to and managed by Sophos Central;
  • an active paid firewall subscription other than the Base License or an active support contract;
  • a dedicated API credential with the required permissions;
  • curl, jq, and, for an import, md5 or md5sum;
  • the tenant ID, regional API host, and firewall ID;
  • a maintenance window and a tested recovery path for imports.

API credentials are created in Sophos Central under Global Settings > Access Control > API Credentials. For a tenant, Sophos recommends the Service Principal Firewall role. In the Partner Dashboard, the function is located under Global Settings > APIs & Integrations > API Credentials Management; different roles are available there, and the least privileged role with access to the target tenant should be used. The Client Secret, JWT, Secure Storage Master Key, and any download or upload URLs issued later must not be included in tickets, chats, or screenshots.

The examples run in Bash and assume a tenant credential. Partner and Enterprise credentials must first resolve the target tenant and its regional API host. Sophos explains this difference under How Our APIs Work.

Enter the Client ID and Client Secret without writing the secret to the shell history:

read -r -p "Client ID: " CLIENT_ID
read -r -s -p "Client Secret: " CLIENT_SECRET
printf '\n'

Then request a time-limited JWT:

JWT=$(
  printf '%s' "$CLIENT_SECRET" |
    curl --fail-with-body --silent --show-error \
      --request POST \
      --header 'Content-Type: application/x-www-form-urlencoded' \
      --data-urlencode 'grant_type=client_credentials' \
      --data-urlencode "client_id=$CLIENT_ID" \
      --data-urlencode 'client_secret@-' \
      --data-urlencode 'scope=token' \
      https://id.sophos.com/api/v2/oauth2/token |
    jq -er '.access_token'
)
unset CLIENT_SECRET

The simple curl examples pass the short-lived JWT as a header argument. They should therefore be run on a trusted admin workstation where no unauthorized users can read local process arguments.

With a tenant credential, whoami returns the tenant ID and regional API host:

WHOAMI=$(
  curl --fail-with-body --silent --show-error \
    --header "Authorization: Bearer $JWT" \
    https://api.central.sophos.com/whoami/v1
)

TENANT_ID=$(jq -er 'select(.idType == "tenant") | .id' <<<"$WHOAMI")
API_HOST=$(jq -er '.apiHosts.dataRegion' <<<"$WHOAMI")
printf 'Tenant: %s\nAPI host: %s\n' "$TENANT_ID" "$API_HOST"

If jq aborts at this point, the credential is probably assigned to a partner or Enterprise organization. Do not continue using that organization’s ID as the X-Tenant-ID; instead, determine the managed tenant and its apiHost.

Determine the firewall ID

The firewall list shows the name, hostname, serial number, firmware, and UUID:

curl --fail-with-body --silent --show-error \
  --header "Authorization: Bearer $JWT" \
  --header "X-Tenant-ID: $TENANT_ID" \
  "$API_HOST/firewall/v1/firewalls?pageSize=1000" |
  jq -r '.items[] |
    [.name, .hostname, .serialNumber, .firmwareVersion, .id] |
    @tsv'

Use the UUID of the correct firewall and do not select it solely by a similar display name:

FIREWALL_ID="<firewall-uuid>"

If the firewall is missing, first check the tenant, region, Central connection, and management approval. The registration process is explained in Connect Sophos Firewall to Sophos Central.

Export the configuration

Start a complete export

The export runs asynchronously. The first request therefore returns only a transaction ID:

EXPORT_RESPONSE=$(
  curl --fail-with-body --silent --show-error \
    --request POST \
    --header "Authorization: Bearer $JWT" \
    --header "X-Tenant-ID: $TENANT_ID" \
    --header 'Content-Type: application/json' \
    --data '{"fullExport":true}' \
    "$API_HOST/firewall/v1/firewall-config/firewalls/$FIREWALL_ID/export"
)

EXPORT_TX=$(jq -er '.transactionId' <<<"$EXPORT_RESPONSE")
printf 'Export transaction: %s\n' "$EXPORT_TX"

Retrieve the status again after a few seconds:

EXPORT_STATUS=$(
  curl --fail-with-body --silent --show-error \
    --header "Authorization: Bearer $JWT" \
    --header "X-Tenant-ID: $TENANT_ID" \
    "$API_HOST/firewall/v1/firewall-config/firewalls/transactions/$EXPORT_TX"
)

jq '{status, result, createdAt, finishedAt, expiryAt, response}' \
  <<<"$EXPORT_STATUS"

Repeat the request approximately every ten seconds until status reaches the final state finished. pending and started are not errors.

Only when status: "finished" and result: "success" are returned does response.url contain the time-limited download URL:

DOWNLOAD_URL=$(
  jq -er '
    select(.status == "finished" and .result == "success") |
    .response.url
  ' <<<"$EXPORT_STATUS"
)

OUTPUT="sophos-firewall-config-$(date +%F).tar"
curl --fail-with-body --location --output "$OUTPUT" "$DOWNLOAD_URL"
tar -tf "$OUTPUT"
unset DOWNLOAD_URL

The TAR file may contain sensitive network, user, VPN, and policy data. Store it securely or delete it after analysis. For a readable report or a before-and-after comparison, the included Entities.xml can be used in Sophos Firewall Config Studio.

Export only selected configurations

For a selective export, entity names must be specified exactly and with the correct capitalization. This example exports firewall and NAT rules with dependent objects:

curl --fail-with-body --silent --show-error \
  --request POST \
  --header "Authorization: Bearer $JWT" \
  --header "X-Tenant-ID: $TENANT_ID" \
  --header 'Content-Type: application/json' \
  --data '{
    "fullExport": false,
    "includeDependency": true,
    "exportEntities": ["FirewallRule", "NATRule"]
  }' \
  "$API_HOST/firewall/v1/firewall-config/firewalls/$FIREWALL_ID/export"

The valid entity names are listed in the current export endpoint of the Sophos Firewall API. For a selective export, includeDependency defaults to false; set the option deliberately and still verify the resulting package.

Import the configuration

The import consists of requesting an upload URL, uploading the TAR file, confirming the file metadata, and checking the transaction. The example uses only one test firewall and deliberately sets performPartialImport to false. The API default is true and permits a partial import for each firewall.

Check compatibility first: the target firewall requires at least the same firmware and pattern version. Sophos supports selective imports between different models only from a lower to a higher model; the target hardware must have at least the same number of Ethernet ports. If port names differ, Entities.xml must be adjusted accordingly before upload.

Prepare the import file and checksum

Prepare the target file, checksum, and file size:

FILE="sophos-firewall-config-2026-08-03.tar"
FILE_SIZE=$(wc -c <"$FILE" | tr -d ' ')

On macOS:

CHECKSUM_MD5=$(md5 -q "$FILE")

On Linux:

CHECKSUM_MD5=$(md5sum "$FILE" | awk '{print $1}')

Request an upload session and upload the file

Request an upload session and display only the non-secret fields:

IMPORT_SESSION=$(
  curl --fail-with-body --silent --show-error \
    --request POST \
    --header "Authorization: Bearer $JWT" \
    --header "X-Tenant-ID: $TENANT_ID" \
    "$API_HOST/firewall/v1/firewall-config/firewalls/import"
)

IMPORT_TX=$(jq -er '.transactionId' <<<"$IMPORT_SESSION")
UPLOAD_URL=$(jq -er '.url' <<<"$IMPORT_SESSION")
jq '{transactionId, method, expiresAt}' <<<"$IMPORT_SESSION"

Upload the file using the returned PUT method before the pre-signed URL expires. Do not send a JWT or tenant header to this URL:

curl --fail-with-body --silent --show-error \
  --request PUT \
  --upload-file "$FILE" \
  "$UPLOAD_URL"
unset UPLOAD_URL

Complete the import and check the status

If the package contains sensitive information and is imported onto a different or newly deployed firewall, the matching Secure Storage Master Key is required. Without it, SFOS does not import sensitive information or configurations that depend on it. The following prompt does not display the secret; press Enter to omit it:

read -r -s -p "Secure Storage Master Key, or press Enter: " SSMK
printf '\n'

Complete the upload and assign the package to the target firewall:

TARGET_FIREWALL_ID="$FIREWALL_ID"

COMPLETE_RESPONSE=$(
  printf '%s' "$SSMK" |
    jq -Rsc \
      --arg firewall "$TARGET_FIREWALL_ID" \
      --arg checksum "$CHECKSUM_MD5" \
      --argjson size "$FILE_SIZE" '
        . as $ssmk |
        {
          firewallIds: [$firewall],
          checksumMd5: $checksum,
          fileSizeBytes: $size,
          performPartialImport: false
        }
        + if ($ssmk | length) > 0
          then {secureMasterKey: $ssmk}
          else {}
          end
      ' |
    curl --fail-with-body --silent --show-error \
      --request POST \
      --header "Authorization: Bearer $JWT" \
      --header "X-Tenant-ID: $TENANT_ID" \
      --header 'Content-Type: application/json' \
      --data-binary @- \
      "$API_HOST/firewall/v1/firewall-config/firewalls/import/$IMPORT_TX/upload-complete"
)
unset SSMK
jq '{id, status, result}' <<<"$COMPLETE_RESPONSE"

One request accepts 1 to 25 unique firewall IDs. For additional firewalls, the package must be uploaded again; do not reuse the same pre-signed URL or transaction ID.

Check the import status through the same transaction endpoint:

IMPORT_STATUS=$(
  curl --fail-with-body --silent --show-error \
    --header "Authorization: Bearer $JWT" \
    --header "X-Tenant-ID: $TENANT_ID" \
    "$API_HOST/firewall/v1/firewall-config/firewalls/transactions/$IMPORT_TX"
)

jq '{status, result, createdAt, finishedAt, response}' <<<"$IMPORT_STATUS"

Repeat this request approximately every ten seconds until status: "finished" appears. Only then evaluate result and, for multiple targets, every item in response.items.

success confirms API processing, not the functional outcome.

Validate the import locally

After finished, check the following on every target firewall:

  1. Are exactly the expected rules, objects, and settings present?
  2. Are users, passwords, certificates, or dependent objects missing because the SSMK was missing or incorrect?
  3. Do interfaces, zones, gateways, firmware, pattern version, and hardware model match the package?
  4. Do routing, NAT, VPN, authentication, and management access work with defined test cases?
  5. Do the Audit Trail and configuration logs show unexpected changes?
  6. Does another export or Config Studio comparison show only the planned differences?

Import/export updates existing configuration and does not automatically remove everything that is missing from the package. The TAR file is therefore neither a complete desired state nor a replacement for a backup, rollback, and functional test.

Troubleshooting

Export remains without a download URL

The URL appears only after status: "finished" and result: "success". For error or partialSuccess, check the fields in response and do not continue with an empty or expired URL.

HTTP 401 or 403

For 401, the JWT has usually expired or is invalid. Authenticate again. For 403, check the Central role, tenant context, X-Tenant-ID, and regional API host. The local XML API permission under /webconsole/APIController does not apply here; it is covered separately under Secure Sophos Firewall XML API access.

Upload-complete returns 400 or 409

Check the transaction ID, upload URL expiration time, actual file size, hexadecimal MD5 checksum, and unique firewall IDs. Do not modify the uploaded file afterward. Use a new upload session for another attempt.

Import ends with error or partialSuccess

Save response and, for multiple target firewalls, response.items. If an API request returns a separate 4xx or 5xx error response, also document error, message, code, correlationId, and requestId. Then check the target version, pattern version, model, ports, dependencies, and SSMK locally. If the Central response is insufficient, check fwcm-api-executor.log on the firewall as described in Sophos Firewall services and log files.

The transaction endpoint is authoritative for the API status; do not assume that the job will appear in the Central Firewall Task Queue.

Very large import aborts

Sophos lists Known Issue NR-19066: an import with a very large number of objects may exceed the two-hour processing limit. Instead of repeating the complete import unchanged, create smaller selective packages, import them individually, and validate each step.

At the end of the session, remove sensitive variables:

unset JWT CLIENT_ID TENANT_ID API_HOST FIREWALL_ID TARGET_FIREWALL_ID
unset EXPORT_TX IMPORT_TX CHECKSUM_MD5 FILE_SIZE