Export and import Sophos Firewall via the Central API
With SFOS 22.0 MR2 or later, firewall configurations can be exported and imported through the Sophos Central REST API in Sophos Fusion (formerly Sophos Central). 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.
For direct firewall access, local REST API and API keys in SFOS 23.0 describes a different path. Those local administrator keys do not replace this article’s cloud service principal, token, tenant or regional API host.
For a single manual change directly in local WebAdmin, use selectively export and import configuration instead. The article also explains Entities.xml, the SSMK, object dependencies, and import merge behavior.
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 Fusion;
- 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,md5ormd5sum;- the tenant ID, regional API host, and firewall ID;
- a maintenance window and a tested recovery path for imports.
Credential creation, roles, token and whoami contracts, region discovery, secret protection, validation, and shared troubleshooting are centralized in Manage Sophos Central API credentials securely. Complete that process first; this article then consumes SOPHOS_ACCESS_TOKEN, SOPHOS_TENANT_ID, and SOPHOS_API_HOST.
API credentials are created in Sophos Fusion 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. For every tenant-specific request, use the regional apiHost that whoami returns for that tenant.
Map the three values validated by the owner process to the variable names used here:
JWT="$SOPHOS_ACCESS_TOKEN"
TENANT_ID="$SOPHOS_TENANT_ID"
API_HOST="$SOPHOS_API_HOST"
Do not request the JWT again; JWT is the short-lived access token copied above.
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.
The tenant ID and regional host also originate from the owner process:
TENANT_ID and API_HOST are already validated. For partner or Enterprise credentials, they must originate from the fully paginated tenant list in the owner process.
Determine the firewall ID
The firewall list shows the name, hostname, serial number, firmware, and UUID:
FIREWALLS_FILE=$(mktemp); printf '[]\n' >"$FIREWALLS_FILE"
page=1; max_pages=1000; expected_pages=
while (( page <= max_pages )); do
PAGE_RESPONSE=$(curl --fail-with-body --silent --show-error --get \
--header "Authorization: Bearer $JWT" --header "X-Tenant-ID: $TENANT_ID" \
--data-urlencode "page=${page}" --data-urlencode 'pageSize=100' \
--data-urlencode 'pageTotal=true' "$API_HOST/firewall/v1/firewalls")
page_total=$(jq -er 'select((.items|type)=="array")|.pages.total|select(type=="number" and floor==. and .>=1 and .<=1000)' <<<"$PAGE_RESPONSE")
if [[ -z "$expected_pages" ]]; then expected_pages=$page_total; fi
[[ "$page_total" == "$expected_pages" ]] || exit 1
jq -e --argjson page "$PAGE_RESPONSE" '.+$page.items' "$FIREWALLS_FILE" >"${FIREWALLS_FILE}.new"
mv "${FIREWALLS_FILE}.new" "$FIREWALLS_FILE"
(( page >= expected_pages )) && break; ((page++))
done
(( page == expected_pages )) || exit 1
jq -r '.[] | [.name,.hostname,.serialNumber,.firmwareVersion,.id] | @tsv' "$FIREWALLS_FILE"
Use the UUID of the correct firewall and do not select it solely by a similar display name:
read -r -p 'Firewall UUID from the approved inventory: ' REQUESTED_FIREWALL_ID
UUID_RE='^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$'
FIREWALL_ID=$(jq -er --arg id "$REQUESTED_FIREWALL_ID" --arg re "$UUID_RE" '[.[]|select((.id|type)=="string" and (.id|test($re)) and (.id|ascii_downcase)==($id|ascii_downcase))]|select(length==1)|.[0].id' "$FIREWALLS_FILE")
rm -f "$FIREWALLS_FILE"; unset PAGE_RESPONSE REQUESTED_FIREWALL_ID page page_total expected_pages max_pages
If the firewall is missing, first check the tenant, region, Sophos Fusion connection, and management approval. The registration process is explained in Connect Sophos Firewall to Sophos Fusion.
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"
Entity names are exact and case-sensitive; the example uses FirewallRule and NATRule. For a selective export, includeDependency defaults to false; set the option deliberately and still inspect the resulting package.
Package scope and limitations
Before an API import, account for these SFOS rules that apply to the TAR file:
- An import updates existing settings. A setting absent from the package remains unchanged on the target; when it exists in both configurations, the imported value takes precedence.
- With external authentication, the export contains only users created manually on the firewall.
OTPTokensincludes only issued tokens for those manually created users. REDDevicedoes not automatically include the required DHCP server, even withincludeDependency. ExportDHCPServeras well, or recreate the DHCP server after the import.- An imported URL list can contain no more than 128 domains.
- XGS 88w, 108w, 118w, and 128w have additional conditions for wireless configurations, including frequency bands, Security mode, Encryption, bridges, and the number of unique SSIDs.
A TAR listing that opens without errors therefore proves only that the archive is readable. It does not prove that every required object is present or that it will take effect on the target hardware.
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:
- Are exactly the expected rules, objects, and settings present?
- Are users, passwords, certificates, or dependent objects missing because the SSMK was missing or incorrect?
- Do interfaces, zones, gateways, firmware, pattern version, and hardware model match the package?
- Do routing, NAT, VPN, authentication, and management access work with defined test cases?
- Do the Audit Trail and configuration logs show unexpected changes?
- 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.
Roll back a faulty import
If local validation fails, stop the rollout, retain the transaction response, and do not mask the result with further imports. For recovery in local WebAdmin, go to Backup and firmware > Backup and restore. Under Restore configuration, use Choose file to select the backup created beforehand, enter its Encryption password, and select Upload and restore.
Restoring replaces the current configuration, deletes the backup stored on the firewall, and restarts the firewall. The WebAdmin IP from the restored configuration then becomes active. Make sure that IP and the encryption password are available before importing. After the restart, repeat the same functional tests and verify with another export; changes made after the backup are lost during the restore.
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 Sophos Fusion 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 Sophos Fusion 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.
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