Field Note

Tor Guard Relay v2.2.0: Safer Updates, Clearer Health, Encrypted Recovery

I built Tor Guard Relay to make running privacy infrastructure more approachable. This update focuses on the work that makes it dependable: knowing which source produced an image, understanding what a health result proves, and recovering the same identity after a failure.

Tor Guard Relay v2.2.0 cover: a purple and green onion, retro relay computers and gold circuit roots
The familiar onion and circuit roots stay at the centre of the project. This release strengthens the work around them: updates, observation and recovery.

Released 10 October 2026: v2.2.0 is published , with retained release evidence . The screenshots below come from a deployed exit relay and a successful encrypted-backup verification. The article also covers maintenance fixes merged after the release tag; those belong to the current rebuild pipeline, rather than changing the tagged source.

ImprovementWhat it gives an operator
🛡️ Tor security floorTor 0.4.9.14 or newer in both variants
🩺 Current-run diagnosticsSeparate process, config, freshness and readiness signals
🔐 Encrypted recoveryA complete recovery set with full archive verification
📦 Exact image promotionPublication of the candidate that passed validation
🔄 Reviewed main rebuildsPost-release fixes can reach rebuilt images without moving the source tag
🗑️ Protected retentionA migration window and explicit safeguards for required image graphs

Tor 0.4.9.14 needs prompt attention

Tor released 0.4.9.14 on 7 October 2026. The Tor Project’s announcement identifies high-severity issues affecting relays, clients, onion services and directory authorities, and recommends updating as soon as possible.

The tagged changelog is the reference for the individual upstream fixes. The container update carries the required Tor version into images that are validated before publication.

Both stable and edge builds now require Tor 0.4.9.14 or newer. Stable uses Alpine 3.24.2 with a pinned base digest. The Go builder and Lyrebird source revision are pinned, and the reviewed dependency graph is checked in, including Pion STUN 3.1.7.

An image refresh only helps after the running container is recreated from it. Reloading torrc does not replace Tor.

Deployed relay output showing Tor 0.4.9.14 and successful configuration validation
The running relay reports Tor 0.4.9.14, followed by a successful validation of its active configuration. This capture records the deployed build, rather than a version promised by a tag.

A release should carry its evidence forward

The release workflow now builds four candidates: stable and edge, each for AMD64 and ARM64. It loads and checks each candidate before publication, including behavior, actual component versions, vulnerability results and SBOMs.

Promotion loads the validated image archives, checks their image identity, and assembles version and alias manifests from the pushed candidate digests. There is no second build between validation and promotion.

Post-release publishing fixes restored the intended public tags and replaced visible validated-* staging tags with digest-only promotion. Untagged architecture manifests still exist because they are part of multi-platform images; they are not automatically clutter.

RegistryStable tagsEdge tags
Docker Hub: r3bo0tbx1/onion-relay2.2.0, latestedge
GHCR: ghcr.io/r3bo0tbx1/onion-relay2.2.0, latest2.2.0-edge, edge

Version tags are rebuilt under the current publication policy, so they can point to newer image digests over time. Record the digest as well as the version when identifying a deployment; a source tag and a container tag serve different purposes.

Security updates have independent paths: Renovate can propose a new Lyrebird source pin, while a compatible vulnerable Go dependency can be patched in our lock before upstream changes its own graph. Go reachability checks analyze the source and dependency graph that produced the candidate transport, with a byte-for-byte match. All HIGH/CRITICAL image findings and known reachable Go vulnerabilities block publication, including issues without a fix.

The 🔒🧅 workflow checks current source on relevant main changes. Every six hours, on manual dispatch and after a successful publication, it also audits the exact published architecture digests in both registries. Candidate security gates remain part of publication. Monitoring can reveal a newly disclosed issue in an unchanged image; it does not patch a running relay. Applicable fixes should receive expedited validation and a new patch release, followed by container recreation. Unfixed issues need assessment and mitigation, and scheduling and advisory ingestion can be delayed.

The first source scan also surfaced a module-level advisory for klauspost/compress 1.18.0 without a reachable call. We updated the lock to the available fixed 1.18.7 for GO-2026-5841 , independently of Lyrebird’s source commit. Subsequent reviewed dependency updates have moved the current lock forward again; the example records the initial repair, not the current lock version. The full report preserves the distinction between a dependency finding and demonstrated code reachability.

Rebuilds now follow reviewed main

The original release automation rebuilt the tagged source. That preserved the release snapshot but also kept post-release fixes out of routine image refreshes. The corrected 🚀✨ workflow selects an immutable commit from main for scheduled rebuilds and manual runs with an empty source_tag input.

To rebuild both variants through GitHub, open Actions → 🚀✨ → Run workflow, select main, leave source_tag empty and enable publish. The workflow builds stable and edge for AMD64 and ARM64, validates each candidate and promotes only successful results. An explicit source_tag selects that tagged source instead.

A main rebuild still uses the latest released version namespace. Its source must descend from that reviewed tag, and the README version must match. A version bump needs a new release tag. Source and security-policy commit identities are recorded separately, and the original GitHub release evidence remains attached to the release.

Health needs a current observation

My earlier health-check article argued for a narrow contract. v2.2.0 adds process liveness to the Docker check and makes the other signals explicit in JSON.

QuestionEvidence
Is Tor running?One exact Tor process
Can Tor validate the active config?Quiet verification of the active torrc
Has this run bootstrapped?Current-run bootstrap notices
Does this observation belong to this run?PID, start time, log inode and byte offset
Can users reach it publicly?Separate network and consensus checks

Old successful bootstrap lines cannot make a restarted relay ready. A missing or rotated log is reported as missing or stale evidence.

Tor Guard Relay 2.2.0 status on a deployed exit: running process, current readiness, 100 percent bootstrap and valid mounted configuration
The exit relay reports a live process, current-run readiness, 100% bootstrap and zero current-run errors. The ORPort line is Tor’s self-test result; consensus membership and independent public reachability still need separate observations.

Inspect these signals with the operator tools:

docker exec tor-relay status
docker exec tor-relay health
docker exec tor-relay doctor --json
docker exec tor-relay config validate

Replace tor-relay with your container name. These diagnostic commands do not restart the relay or change its configuration.

Doctor JSON showing Tor 0.4.9.14, live and ready state, valid configuration, fresh observation and a reminder to check external reachability separately
The diagnostic capture omits identity fields and shows the booleans separately. A ready reason describes the local observation; the next action still points to external reachability and consensus checks.

Doctor supplies a reason and next action. Bridge-line generation now checks local transport state and accepts an explicit public address; it no longer prescribes a fixed 24–48 hour wait. Local bridge credentials still do not prove external connectivity.

For multiple Docker-managed relays, the host-side fleet inventory collects local image identities and health observations without opening a metrics listener. Run it on each actual relay host; a local Docker engine cannot describe containers on an unrelated server.

Configuration has an owner

Mounted torrc files remain authoritative. Generated ENV configuration is validated before atomic replacement and regenerated at startup.

The new config command can validate a candidate, show a directive-only diff with every value redacted, and apply it atomically to a generated target. Invalid candidates retain the active file. A value-only change will not appear in the redacted diff, so the candidate still needs private review.

Reload validates the active file, signals the exact Tor process and confirms that the process survives. Tor decides which directives can reload. Lasting generated changes belong in deployment ENV; other changes may require recreation.

Accounting and IPv6 ENV mappings cover common operator settings without making the generator a substitute for every advanced torrc option.

Encrypted recovery belongs beside deployment

Copying keys alone leaves configuration, family material and transport state behind. The new host command captures the active torrc and supported includes, the complete DataDirectory and an encrypted integrity manifest.

Tor Guard Relay v2.2.0: four recovery stages, from stopping writers and encrypting through full verification and offline validation before deliberate activation
The archive remains encrypted on disk. Plaintext appears only in a deliberately created restore staging directory.

The backup command runs on the host, using the scripts in the repository. It is not installed inside the relay image. The host needs Python 3.10+, age and access to the correct Docker engine. Run the commands from the repository directory so the relative script path exists.

If the tooling is not already on the host, the tagged release contains it:

git clone --depth 1 --branch v2.2.0 \
  https://github.com/r3bo0tbx1/tor-guard-relay.git \
  "$HOME/tor-guard-relay"
cd "$HOME/tor-guard-relay"

Use an existing checkout instead of cloning over it. Prepare a public recipient file and keep a protected copy of its private recovery identity separately; the backup guide covers setup, include limits and staged recovery.

Creation with --stop briefly stops a running source and restarts it after the snapshot. Preview coverage with --dry-run first and stop any other host writers. With the recipient prepared:

sh scripts/utilities/relay-backup.sh create \
  --container tor-relay --stop \
  --recipients "$HOME/.config/relay-backup/recipients.txt" \
  --output-dir "$HOME/relay-backups"

sh scripts/utilities/relay-backup.sh verify \
  "$HOME/relay-backups/ACTUAL_BACKUP_FILENAME.tar.gz.age" \
  --identity "$HOME/.config/relay-backup/identity.txt"

Use the actual archive filename printed by creation, replacing the placeholder above. Verification does not stop the relay or extract its keys to disk.

Encrypted backup verification returning verified true, 282 files and archive format 1
This real archive passed full verification with 282 files. The result establishes archive integrity and successful decryption; it does not by itself establish a successful staged restore or complete disaster-recovery rehearsal.

Creation streams tar through gzip into age. Verification authenticates the full stream and checks every member against the manifest. Restore requires a new directory, compares the fingerprint and validates configuration in a network-disabled helper. It never activates the copied identity automatically.

External or offline Tor master keys need separate custody. Unsupported include layouts and links fail closed. Docker can detect another container writing shared storage, but operators must also stop any host writer.

Registry cleanup protects what operators still need

Frequent rebuilds can leave older image indexes and architecture manifests behind. Docker Hub’s storage total includes more than the visible tag list. Deleting every untagged object would also delete children used by current multi-platform images, including ARM64 images needed by supported Raspberry Pi deployments.

The merged 🗑️🧹 policy keeps the weekly inventory and preview and adds a daily automatic-eligibility check. Deletion is enabled in policy, but it waits for a maintainer-published retirement notice linked through the repository’s REGISTRY_RETIREMENT_NOTICE_URL variable. The first cleanup requires 14 full days after that notice; merging alone does not start the clock. Later automatic cleanup can run at most once every 14 days, subject to the same checks.

PreservedRequired before removal
Every current public tag and its complete AMD64/ARM64 graphExact known manifests outside the protected set
At least two recent published and audited builds per variantImage-age grace period and successful publication/audit evidence
Original current-release images and reviewed deployed image identitiesComplete deployment review refreshed within seven days
Shared protected manifests, unknown objects and GitHub release assetsAn unchanged preview, durable cleanup intent and live rechecks

Retirement planning covers both registries before mutation. Publication and cleanup share a lock, and retained image graphs, layers and release assets are checked before and after removal. A failed or interrupted cleanup blocks another automatic attempt until its results are reconciled. Missing notice information or an expired review produces a waiting result rather than authorizing deletion.

The retention guide explains notice publication, deployment inventory and the exact reviewed legacy list. This article does not announce that the migration window has started or that the proposed automatic retirement has completed. Registry storage accounting can also lag successful manifest removal.

What the checks and screenshots establish

Candidate acceptance is tested with synthetic guard, exit and bridge identities and networking disabled. Acceptance checks startup, configuration, reload, restart freshness, transport output and shutdown. Recovery rehearsals check the archive and restored identity without joining the Tor network.

Failure tests cover wrong decryption identities, truncated ciphertext, unsafe paths, duplicate members, links, mismatched hashes, interruption, producer failure, simulated disk exhaustion and restart failure. The docs and template checks catch local links, stale Alpine examples and malformed deployment templates.

The full module scan retains an unfixed Go OpenPGP advisory . Those deprecated packages are absent from Lyrebird’s package dependency graph. That assessment accompanies the scan result; it does not become a claim that every dependency is vulnerability-free.

The deployed screenshots now add evidence of Tor 0.4.9.14, valid active configuration, fresh local bootstrap/readiness and one verified encrypted archive. The published release supplies separate publication evidence. Neither replaces independent consensus/reachability checks, a fleet-wide migration assessment or a staged recovery rehearsal.

A clearer place to start

The README keeps the project’s badges, logo, screenshots and emoji-led sections alongside the mode matrix and operator workflows. Detailed guides retain their examples, tables and troubleshooting context. The architecture guide restores its Mermaid diagrams and adds encrypted recovery and security-update flows. Curated release notes lead with the security action and compatibility changes, while historical migration records explain older problems without presenting retired emergency commands as today’s workflow.

The later maintenance work also grouped compatible Lyrebird Go updates to reduce dependency-PR churn, corrected the security-watch event flow and cleaned up skipped-job presentation. CodeQL findings in registry test routing were fixed with an exact parsed authority/repository allowlist rather than loose prefix checks. The refreshed Code of Conduct adds practical Tor privacy guidance, private reporting and fair moderation without treating technical disagreement as misconduct.

Legacy plaintext-backup and live-volume replacement helpers are retired. Recovery now needs the host tooling and a new staging directory; it never activates a copied relay identity automatically. Older source tags that lack the current validation contract fail closed under the updated pipeline. Existing bridge ENV aliases, Happy Family settings and health fields remain supported; reachable remains a string.

Before upgrading:

  1. Record the current fingerprint, image digest and deployment configuration.
  2. Create and verify an encrypted backup, and rehearse recovery in a new staging directory.
  3. Review generated ENV or mounted torrc ownership before recreating the container.
  4. Confirm identity continuity, the running Tor version and fresh bootstrap/readiness.
  5. Check public reachability and consensus separately, and retain a rollback image and deployment.

The release , operator guides and current workflows carry the details. For me, this update is about making the operational evidence easier to inspect: what is running, what passed validation, what can be recovered and what must remain pullable.