Concluded·30 Aug 2026 · 5 min read

What a gateway backup actually restores

Ignition 8.3.8 (b2026071409) · Docker on an unprivileged Proxmox LXC · TimescaleDB 2.x on PostgreSQL 16 · gwcmd.sh backup, container -r restore

The question here is narrow and testable: when you restore a gateway backup, what comes back?

Not "does the restore succeed" — it does. I wanted the file-by-file answer, and what the restored machine does in the first two minutes of its life.

The rig

Originaledge-1, Ignition 8.3.8, file-based config, an edge gateway for the Rotterdam site
Estategateway-network peers central-a-master and central-a-backup
HistorianTimescaleDB on PostgreSQL, reached over the plant segment on :5544
Restore targeta clean container from the same image, no shared volumes
Backup methodgwcmd.sh -b, the gateway's own supported path

Taking the backup

$ time gwcmd.sh -b /tmp/v.gwbk -y
Gateway backup saved to /tmp/v.gwbk
2.312s

12,564,181 bytes on disk, 22.7 MB uncompressed, 2,711 entries.

1,008
config/
941
projects/
752
user-lib/
10
certs, xml
0rows of tag history. The only matches for “history” are store-and-forward metric tag definitions.
The archive by area, drawn in proportion. Three areas account for 2,701 of the 2,711 entries; the fourth is certificates and a handful of xml.

Searching the archive for history data returns matches, which is misleading until you read them: they are the tag definitions for the store-and-forward metrics — config/resources/core/ignition/tag-definition/System/Gateway/StoreAndForward/tsdb/…. The definitions of the counters, not the data. There are no history rows in the file at all.

Two failed restores before one worked

Both failures are behaviour worth recording, because each cost a cycle.

1
-h 9088 -s 9043 -n restored-gwexit 1
Gateway Public HTTP/HTTPS/Address must be specified together — Address not specified
2
-a 192.168.x.x -h 9088 -s 9043exit 1
The port(s) 8088, 8060 are unavailable — bound the backup's ports, not the command line's, and collided with the original on host networking
3
bridge networking, -p 9088:8088healthy in 22 s
Gateway backup restored → Gateway started in 19 seconds
Three attempts. The second is the one worth remembering: the restore reinstates the backup's own network settings over the ones you passed in.

The first is a straightforward argument-validation rule: -h and -s cannot be supplied without -a. Fine.

The second is the interesting one. With all three supplied, the gateway still came up trying to bind 8088 and 8060 — the ports from the backup, not the ports on my command line — and collided with the original on the same host network:

java.lang.Exception: The port(s) 8088, 8060 are unavailable
  at CommissioningContext.maybeCheckEnvPorts(CommissioningContext.java:555)

The restore overwrites the freshly-commissioned settings with the backup's. That is correct for a real recovery, where the original is gone. For a test restore it means you cannot place the copy beside the original on a shared network stack. Bridge networking with a published port worked immediately.

The restore itself

07:50:07  container start
07:50:08  Gateway restore file staged: Gateway backup restored
07:50:10  Starting Ignition 8.3.8 (b2026071409)
07:50:29  Gateway started in 19 seconds

Twenty-two seconds cold start to running gateway, of which the restore is roughly one. The 19 seconds is ordinary software startup and would be the same on a fresh install.

File-by-file against the original

Both gateways, find . -type f under data/config/resources, sorted under a fixed collation:

files
original edge-11,451
restored1,457
only in original6
only in restored12

The six are one real file — migration-log-202608070924.md, the husk left by an earlier config.idb migration and the same husk the drift measurements tripped over — plus five orphaned content-addressed blobs under .resources/. The twelve are five commissioning artifacts the fresh boot wrote before the restore landed (identity-provider/temp/, user-source/temp/) plus seven blobs.

Category by category the picture is cleaner still:

=alarm-journal
=audit-profile
=cobranding
=database-connection
=database-driver
=database-translator
=edge-system-properties
=gateway-network-incoming
=gateway-network-proxy-rules
=gateway-network-queue-settings
=gateway-network-settings
=general-alarm-settings
identity-provider
=images
=keyboard_layout
=opc-connection
=quickstart
=schedule
=security-levels
=security-properties
=store-and-forward-engine
=system-properties
=tag-group
=tag-provider
=translations
user-source
24 identical2 differ by commissioning artifacts1,451 files vs 1,4571 genuinely absent: migration-log-202608070924.md
Resource counts, original against restored, category by category. The two marked differ do so only by the temp/ files the fresh boot wrote before the restore landed.

Twenty-six resource categories with identical counts, except the two carrying those temp/ artifacts. Database connections, gateway-network settings, tag providers, tag groups, store-and-forward engines, security levels, the 369 images — all present, all matching.

One thing that looked like a finding and was not. An earlier pass over the archive listing suggested external/config-mode.json was missing from the backup. It is present on both gateways after the restore — but dated 13 May on both, which is the image build date. It ships with the image. The restore lands on top of a fresh install, so a file absent from the backup can still be present afterwards, and an archive listing cannot tell you which. That is the argument for diffing the restored machine rather than reading the manifest.

The credential came back working

The backup carries the database password as an encrypted object:

"password": {
  "data": {
    "ciphertext": "g6gOxrruY_FE92CuyA",
    "encrypted_key": "sQWDrUomA-5rPVxiLpcAQu82Sisbp4SuwWBel9wM0pgAOfkefX9cGg",
    "iv": "-boqHvUL1dPdY2I-",
    "protected": "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwiaWF0IjoxNzg2MTE2NTM5LCJ6aXAiOiJERUYifQ",
    "tag": "k06PkvdS5tu7_FtVxHJB4A"
  },
  "type": "Embedded"
}
ciphertextg6gOxrruY_FE92CuyAthe password itself
encrypted_keysQWDrUomA-5rPVxi…the key that unwraps it — in the same file
protectedeyJhbGciOiJBMjU2S1ci…A256KW + A256GCM, issued 2026-05-13
typeEmbeddedthe operative word
The database password as it travels inside the backup. Encrypted at rest and self-contained — the wrapping key rides along, which is what makes the restored gateway able to authenticate without anybody typing anything.

type: Embedded is the operative word: the wrapping key travels with the backup. Ninety seconds into the restored gateway's life:

[s.f.s.TagHistoryDatasourceSink] Tag history sink initialized
  successfully against datasource 'tsdb'

A sink cannot initialise against a datasource that will not authenticate. The credential survived the round trip and worked against production.

Proving the writes rather than trusting the log line

Counting rows in the live sqlt_data_1_2026_08 table over two 90-second windows, changing nothing between them except whether the restored copy was running:

copy running31,73432,004+270 rows / 90 s
copy stopped32,02832,028+0 rows / 90 s
no ticks — nothing arrived
Two ninety-second windows on the live sqlt_data_1_2026_08 table. Each tick is ten rows. The only variable changed between them was whether the restored copy was running.

The caveat that matters. The original gateway was running throughout, but its SQL Historian access level changed: Demo_Enabled -> Demo_Expired at 07:34, so it was writing nothing. Every one of those 270 rows came from the restored copy — a clean attribution, but it also means I did not observe two gateways writing concurrently. The claim this test supports is "a restored copy writes to production unprompted", not "a restored copy duplicates the original's rows". I checked for duplicate (tagid, t_stamp) pairs in the window and found none, which is consistent with only one writer.

Estate trust arrived pre-approved

// gateway-network-incoming/central-a-master_3007b9a9…/config.json
{ "connectionId": "…-central-a-master|3007b9a9-…", "securityStatus": "Approved" }
// gateway-network-incoming/central-a-backup_2829c77e…/config.json
{ "connectionId": "…-central-a-backup|2829c77e-…", "securityStatus": "Approved" }
a new gateway
  1. 1. announces itself to the central pair
  2. 2. sits pending
  3. 3. a person looks at it
  4. 4. a person approves it
the restored copy
central-a-masterApproved
central-a-backupApproved
restored from file, before first boot completed. No step 3.
How a gateway normally earns a place in the estate, against what the restored one had before it finished booting.

Both incoming connections came back approved. A gateway joining an estate for the first time has to be approved by a person; a restored one has that approval already, because approval is a resource and resources are what a backup restores.

What the backup does not contain

The historian database is 366 MB, and it is tempting to put that number beside the 12.5 MB backup and call it an argument. It is not a fair comparison — that database is shared across several rigs:

oss_tag_history284 MB
estate_tag_history62 MB
tag_history5.2 MB
sqlt_data (this gateway)3.4 MB
354.6 MB across these four tables · 366 MB database including indexes · backup 12.5 MB · rows of history in the backup: 0
The database is shared across several rigs. This gateway's own Ignition tag history is the dark slice on the right — about 32,000 rows. None of any of it is in the backup.

Ignition's own tag history on this estate is the 3.4 MB slice, about 32,000 rows. The honest statement is not about the ratio. It is that the backup contains zero rows of process history at any size, and restoring the gateway recovers none of it. Historian recovery is a separate mechanism with a separate failure mode, and it wants its own rehearsal.

Verdict

Configuration restore: complete, fast, and better than I expected — one leftover log file's difference across 1,451 files.

The rest of it is the finding. The restored gateway is not an inspection copy. It holds a working production credential, it holds pre-granted estate trust, it ignores your command line in favour of the backup's own network settings, and it starts using all three within twenty-two seconds of launch. Test restores belong on a network with nowhere to go.

The argument for a general audience is in a restore is not a rehearsal.

The same gateway killed outright and timed past this finish line, to the first row landing in the historian, is in timing a recovery to the first good row.

Newsletter

New essays, by email.

SCADA, cloud, AI, and the plant floor — a short email when something new is published. No noise, unsubscribe anytime.