<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Backups · XB Field Notes</title><link>https://hk.xbcatm.com/tags/backups/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://hk.xbcatm.com/tags/backups/index.xml" rel="self" type="application/rss+xml"/><item><title>Bucket versioning and lifecycles manage cost</title><link>https://hk.xbcatm.com/posts/bucket-versioning-and-lifecycles/</link><pubDate>Sat, 08 May 2027 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/posts/bucket-versioning-and-lifecycles/</guid><description>S3 versioning retains recoverable history while lifecycle rules trade retention against cost; delete markers, null versions, and permanent expiration have different effects.</description><content:encoded>&lt;p&gt;S3 Versioning retains multiple versions under one key, making many accidental overwrites and ordinary deletes recoverable while the required version still exists. It does not protect against every deletion or provide an independent backup by itself. Retention, permissions, encryption-key availability, and restoration procedures determine whether the saved history can actually recover the application.&lt;/p&gt;
&lt;h2 id="distinguish-versioning-states"&gt;Distinguish versioning states&lt;/h2&gt;
&lt;p&gt;In a versioning-enabled bucket, a PUT creates a new version ID and preserves earlier data versions. Each version stores the whole object, not merely its changed bytes. Repeatedly overwriting a large object therefore has a different storage profile from a block-level incremental snapshot, even when the visible key count stays constant.&lt;/p&gt;
&lt;p&gt;An object that predates enablement keeps its &lt;code&gt;null&lt;/code&gt; version ID. New versioned writes do not overwrite that null version. Once enabled, a bucket cannot return to the never-versioned state, although versioning can be suspended. Under suspension, subsequent writes use the null-version slot and can replace an existing null version. Specify which state applies before describing overwrite safety.&lt;/p&gt;
&lt;p&gt;Null is a real version identifier, not a guarantee of permanent retention. A null version that becomes noncurrent can be subject to noncurrent expiration. It can also be explicitly targeted for deletion when authorized. A listing containing null therefore does not prove that old data is exempt from lifecycle cleanup.&lt;/p&gt;
&lt;h2 id="a-delete-marker-changes-visibility"&gt;A delete marker changes visibility&lt;/h2&gt;
&lt;p&gt;An ordinary DELETE without a version ID in an enabled bucket creates a delete marker. When that marker is current, a versionless GET normally returns 404; explicitly requesting a delete-marker version returns 405. Earlier data remains available by its data-version ID, subject to access, storage-class, and encryption requirements.&lt;/p&gt;
&lt;p&gt;Delete markers have no object payload but are not free zero-byte storage entries: S3 charges for the key-name bytes they store. Removing the specific current marker exposes the preceding version, which could itself be another marker. Alternatively, copying an explicit earlier data version creates a new current data version while preserving history. A marker cannot be copied as if it were object data.&lt;/p&gt;
&lt;p&gt;Deleting a particular data version is a different, permanent operation. Versioning alone cannot restore that deleted version. MFA Delete, when configured, adds authentication for permanent version deletion and changes to versioning state; it is not automatically active whenever versioning is enabled. AWS does not support using MFA Delete with lifecycle configurations.&lt;/p&gt;
&lt;h2 id="lifecycle-expresses-a-retention-tradeoff"&gt;Lifecycle expresses a retention tradeoff&lt;/h2&gt;
&lt;p&gt;Current-version Expiration and NoncurrentVersionExpiration do different jobs. In an enabled bucket, expiring current data generally creates a marker and makes that data noncurrent. Noncurrent expiration permanently removes eligible older data versions. Eligibility uses time since becoming noncurrent, and optional retained-version counts add another condition. It is not simply the object&amp;rsquo;s original upload age.&lt;/p&gt;
&lt;p&gt;Lifecycle execution is asynchronous. A thirty-day rule is not proof that exactly thirty versions remain at every instant. Rule filters, write frequency, retained-count settings, replication status, and Object Lock can affect what qualifies. Transitions to colder storage retain data with different storage, retrieval, and minimum-duration costs; calculate those costs against the actual object sizes and recovery needs.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;Growing noncurrent bytes can raise costs while ordinary listings appear stable. Inspect version inventory and lifecycle scope rather than expecting a fixed version-to-key ratio to diagnose every case. A bucket does not need to be physically “full” for this to become expensive, and lifecycle is not a hard spending cap or the only possible retention control.&lt;/p&gt;
&lt;p&gt;Overaggressive expiration creates the opposite problem: the required recovery version is permanently removed. Validate retention against the longest plausible detection delay and restore requirements. HEAD can inspect metadata but does not download and validate object contents; a successful metadata request alone is not a completed restoration test.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-bucket-versioning-and-lifecycles.detail.en.svg" alt="Version-stack visibility and two restore paths are separated from permanent noncurrent expiration, with suspended null-slot and recovery limitations." width="560" height="2250"&gt;
&lt;figcaption&gt;Version visibility — mechanism diagram.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Run this Python 3 model with assertions enabled. Retained historical executions cover these exact model bytes; this source review did not rerun them. It models an enabled bucket&amp;rsquo;s version stack entirely in memory, including a preexisting null data version. No AWS account, credentials, or remote object is used. Marker storage billing, real timestamps, permissions, lifecycle scheduling, and concurrent writes are deliberately outside the model.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;null&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;old&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;v1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;new&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;d1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;new&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;remove current marker: previous data visible&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;d2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Copy an explicit data version to create a new current version.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;null&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;v2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;old&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;d2&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;copy explicit data version: new current version; history retained&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Noncurrent null versions are not inherently protected from deletion.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[:]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;null&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;old&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;remove old null version: current copy remains&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The three outputs distinguish exposing a previous version, copying explicit historical data, and removing a noncurrent null version. Stored payload bytes remain through marker creation; this does not count the marker&amp;rsquo;s billable key bytes. For real recovery, inspect the exact key across paginated data-version and delete-marker listings, select an explicit data version, retrieve it, and validate its contents before changing production state.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html"&gt;AWS S3 versioning&lt;/a&gt;, how versioning and the null version work, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/DeleteMarker.html"&gt;AWS S3 delete markers&lt;/a&gt;, what a delete marker does and how to undo it, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html"&gt;AWS S3 lifecycle management&lt;/a&gt;, current and noncurrent expiration and transition rules, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-expire-general-considerations.html"&gt;AWS expiring objects&lt;/a&gt;, eligibility, permanent noncurrent deletion and asynchronous execution, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html"&gt;AWS MFA Delete&lt;/a&gt;, optional protection and lifecycle constraints, checked 2026-10-15.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/RestoringPreviousVersions.html"&gt;AWS restoring previous versions&lt;/a&gt;, explicit-version copying and restoration, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Backups are only useful after a restore rehearsal</title><link>https://hk.xbcatm.com/notes/backups-are-only-useful-after-a-restore-rehearsal/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/notes/backups-are-only-useful-after-a-restore-rehearsal/</guid><description>A backup plan should prove that the important files can be found, verified, and restored.</description><content:encoded>&lt;p&gt;“We have backups” is not yet a recovery capability. A useful backup has a known scope, a known freshness, a verification step, and a restore procedure that someone has actually run.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/backup-restore.detail.en.svg" alt="The source repository supplies authoritative rebuild inputs; the published artifact supplies last-published restore bytes. Retrieve archive and checksum, verify before extraction, and restore a trusted archive as an unprivileged user into an empty isolated target. Record checksums, permissions, dependencies, recovery targets and elapsed time. A checksum is not authenticity, and two nonempty pages are not full acceptance." width="720" height="2400"&gt;&lt;figcaption&gt;A recovery rehearsal starts with identifiable inputs and a retrieved, verified stored copy. Restore into a disposable isolated target, then record files, permissions, dependencies and elapsed time. A checksum or existing archive alone does not demonstrate recoverability.&lt;/figcaption&gt;&lt;/figure&gt;
&lt;h2 id="define-the-recovery-unit"&gt;Define the recovery unit&lt;/h2&gt;
&lt;p&gt;For a static site, the source repository and the generated artifact have different purposes. The repository is the authoritative input; the artifact is the quickest way to restore the last published version. Keep both distinctions clear.&lt;/p&gt;
&lt;p&gt;At minimum, record:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the source commit or archive identifier;&lt;/li&gt;
&lt;li&gt;the generator and theme versions;&lt;/li&gt;
&lt;li&gt;the list of files included in the artifact;&lt;/li&gt;
&lt;li&gt;the checksum of important downloads;&lt;/li&gt;
&lt;li&gt;the retention period and storage location.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not include credentials, private keys, cookies, or access tokens in a website backup. Those belong in a separate secret-management process.&lt;/p&gt;
&lt;h2 id="verify-before-storing"&gt;Verify before storing&lt;/h2&gt;
&lt;p&gt;An archive that cannot be opened is not a backup. A minimal verification loop looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -czf site-2026-06-03.tar.gz public/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -tzf site-2026-06-03.tar.gz &amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sha256sum site-2026-06-03.tar.gz &amp;gt; site-2026-06-03.tar.gz.sha256
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Creating a checksum records a baseline; it does not read back the stored copy. Retrieve the archive and checksum from backup storage, then verify them before extraction. A matching checksum detects accidental change but does not authenticate an archive if an attacker can replace both files. Listing members also does not prove that the right files were backed up.&lt;/p&gt;
&lt;h2 id="restore-somewhere-disposable"&gt;Restore somewhere disposable&lt;/h2&gt;
&lt;p&gt;Use a trusted archive, extract as an unprivileged user into an empty temporary directory or isolated host, then check entry points and a representative resource. An untrusted archive needs stronger isolation because paths and links can affect extraction. Do not test by overwriting the only live copy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sha256sum -c site-2026-06-03.tar.gz.sha256
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;restore_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tar -xzf site-2026-06-03.tar.gz -C &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restore_dir&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restore_dir&lt;/span&gt;&lt;span class="s2"&gt;/public/index.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$restore_dir&lt;/span&gt;&lt;span class="s2"&gt;/public/404.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The command is intentionally boring. A rehearsal should reveal missing files, wrong permissions, or undocumented dependencies before an incident does.&lt;/p&gt;
&lt;h2 id="measure-freshness-honestly"&gt;Measure freshness honestly&lt;/h2&gt;
&lt;p&gt;A backup schedule is meaningful only relative to the amount of work that can be lost. If the source changes once a month, daily snapshots may be unnecessary. If a resource is updated every hour, a monthly archive is not enough.&lt;/p&gt;
&lt;p&gt;Write down the target recovery point and recovery time, then choose storage and checks that can meet those targets. Avoid claiming “continuous protection” when the last verified restore is old.&lt;/p&gt;
&lt;p&gt;Keep at least one recoverable copy outside the live host’s failure and deletion boundary. Record the elapsed restore time, restored checksums, permissions, and any manual dependencies; two nonempty HTML files are a smoke test, not a complete recovery acceptance test.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/tar/manual/tar.html"&gt;GNU tar manual&lt;/a&gt;, archive creation and verification behavior, checked 2026-06-03.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hk.xbcatm.com/resources/"&gt;Static-site release checklist&lt;/a&gt;, local companion resource.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/coreutils/manual/html_node/sha2-utilities.html"&gt;GNU Coreutils: SHA-2 utilities&lt;/a&gt;, checksum creation and &lt;code&gt;--check&lt;/code&gt; verification, checked 2026-10-15.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>