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.

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.
| Improvement | What it gives an operator |
|---|---|
| 🛡️ Tor security floor | Tor 0.4.9.14 or newer in both variants |
| 🩺 Current-run diagnostics | Separate process, config, freshness and readiness signals |
| 🔐 Encrypted recovery | A complete recovery set with full archive verification |
| 📦 Exact image promotion | Publication of the candidate that passed validation |
| 🔄 Reviewed main rebuilds | Post-release fixes can reach rebuilt images without moving the source tag |
| 🗑️ Protected retention | A 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.

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.
| Registry | Stable tags | Edge tags |
|---|---|---|
Docker Hub: r3bo0tbx1/onion-relay | 2.2.0, latest | edge |
GHCR: ghcr.io/r3bo0tbx1/onion-relay | 2.2.0, latest | 2.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.
| Question | Evidence |
|---|---|
| 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.

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.

ready reason describes the local observation; the next action still points to external reachability and consensus checks.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.

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.

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.
| Preserved | Required before removal |
|---|---|
| Every current public tag and its complete AMD64/ARM64 graph | Exact known manifests outside the protected set |
| At least two recent published and audited builds per variant | Image-age grace period and successful publication/audit evidence |
| Original current-release images and reviewed deployed image identities | Complete deployment review refreshed within seven days |
| Shared protected manifests, unknown objects and GitHub release assets | An 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:
- Record the current fingerprint, image digest and deployment configuration.
- Create and verify an encrypted backup, and rehearse recovery in a new staging directory.
- Review generated ENV or mounted torrc ownership before recreating the container.
- Confirm identity continuity, the running Tor version and fresh bootstrap/readiness.
- 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.





