{"id":1833,"date":"2026-01-22T11:17:20","date_gmt":"2026-01-22T11:17:20","guid":{"rendered":"https:\/\/paoloronco.it\/?p=1833"},"modified":"2026-01-22T11:18:05","modified_gmt":"2026-01-22T11:18:05","slug":"over-engineering-un-homelab-logging-backup-e-business-continuity-con-ubuntu-server","status":"publish","type":"post","link":"https:\/\/paoloronco.it\/en\/over-engineering-un-homelab-logging-backup-e-business-continuity-con-ubuntu-server\/","title":{"rendered":"Over\u2011Engineering a Homelab: Logging, Backup and Business Continuity with Ubuntu Server"},"content":{"rendered":"<p class=\"wp-block-paragraph\">This article stems from a very real problem: knowing if your backups are actually working, without having to manually check scattered logs, opaque cron scripts, and unreliable notifications. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In my homelab (which still remains <em>a house<\/em>, not an enterprise data center) I decided to apply <strong>Business Continuity and Disaster Recovery principles<\/strong> typical of professional environments:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>deterministic and verifiable logging<\/li>\n\n\n\n<li>clear separation between <em>execution<\/em> And <em>check<\/em><\/li>\n\n\n\n<li>principle of <em>fail-closed<\/em> (Better one more alert than a lost backup)<\/li>\n\n\n\n<li>external and immutable audit trail<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The result is an architecture that revolves around <strong>Ubuntu Server<\/strong>, redundant backups (Oracle Cloud + Hetzner), <strong>Google Cloud for logging<\/strong> and a workflow <strong>n8n<\/strong> which validates every day that everything went well.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article explains <strong>the architecture<\/strong>, the <strong>technical choices<\/strong> and the <strong>Why<\/strong>. The n8n workflow is a paid product: I describe it transparently, but without releasing sensitive or one-to-one replicable information.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The real problem (first)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before this architecture, the flow was the classic:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>cron job \u2192 rsync \/ rclone<\/li>\n\n\n\n<li>local logs<\/li>\n\n\n\n<li>manual control \u201cwhen it happens\u201d<\/li>\n\n\n\n<li>alert only in case of obvious errors<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This approach has three structural flaws:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Silence \u2260 success<\/strong><br>If a job doesn&#039;t get off the ground or dies halfway, you often don&#039;t find out right away.<\/li>\n\n\n\n<li><strong>Local logs = single point of failure<\/strong><br>If you lose the VM, you also lose proof that the backup was made. <em>era<\/em> match.<\/li>\n\n\n\n<li><strong>Wasted human time<\/strong><br>Reading raw logs is expensive and error-prone.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Hence the decision to <em>consciously over-engineer<\/em>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Project objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The goals were not to \u201cdo cool things\u201d, but to be <strong>operationally calm<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>know <strong>everyday<\/strong> if all jobs have run<\/li>\n\n\n\n<li>know <strong>which job<\/strong> he failed and <strong>Why<\/strong><\/li>\n\n\n\n<li>have logs <strong>immutable<\/strong> off the server<\/li>\n\n\n\n<li>be able to demonstrate after the fact what happened (audit)<\/li>\n\n\n\n<li>zero reverse dependencies: control must never touch the server<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"597\" src=\"https:\/\/paoloronco.it\/wp-content\/uploads\/2026\/01\/image-1024x597.png\" alt=\"\" class=\"wp-image-1834\" srcset=\"https:\/\/paoloronco.it\/wp-content\/uploads\/2026\/01\/image-1024x597.png 1024w, https:\/\/paoloronco.it\/wp-content\/uploads\/2026\/01\/image-300x175.png 300w, https:\/\/paoloronco.it\/wp-content\/uploads\/2026\/01\/image-768x448.png 768w, https:\/\/paoloronco.it\/wp-content\/uploads\/2026\/01\/image-1200x700.png 1200w, https:\/\/paoloronco.it\/wp-content\/uploads\/2026\/01\/image.png 1248w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">High-level architecture<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>Ubuntu Server (on\u2011prem \/ Proxmox) \u251c\u2500 cron \u251c\u2500 rsync \/ rclone \u251c\u2500 structured logs \u2502 \u251c\u2500 Google Cloud Logging (streaming) \u2514\u2500 Google Cloud Storage (snapshot log) \u2502 \u25bc n8n (monitoring &amp; validation) \u2502 \u251c\u2500 check log presence \u251c\u2500 deterministic parsing \u251c\u2500 content analysis \u2514\u2500 alert \/ ticket\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Key separation:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Ubuntu Server runs<\/strong><\/li>\n\n\n\n<li><strong>n8n observes<\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">No SSH, no API to the server. Zero trust in the data producer.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Ubuntu Server as a foundation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ubuntu Server is the starting point:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>VM on Proxmox<\/li>\n\n\n\n<li>local storage + dedicated disks<\/li>\n\n\n\n<li>versioned scripts (Git)<\/li>\n\n\n\n<li>cron <em>simple<\/em>, but observable<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Structured logs, not \u201crandom text\u201d<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Each backup job produces logs with:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>marker of <code>START<\/code>, <code>RSYNC_END<\/code>, <code>SUMMARY<\/code>, <code>END<\/code><\/li>\n\n\n\n<li><code>run_id<\/code> unique<\/li>\n\n\n\n<li><code>rc<\/code>, duration, warnings, errors<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This allows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>deterministic parsing<\/li>\n\n\n\n<li>detect incomplete runs<\/li>\n\n\n\n<li>distinguish warning from failure<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It&#039;s not logging &quot;for humans&quot;, it&#039;s logging <strong>for the machines<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Redundant backups \u2260 Business Continuity<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Making multiple copies is not enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There <strong>Business Continuity<\/strong> requires:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>know that <em>all<\/em> copies have been updated<\/li>\n\n\n\n<li>know <em>When<\/em> a copy is not<\/li>\n\n\n\n<li>react proportionately<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Because of this:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Oracle Cloud and Hetzner are just <em>target<\/em><\/li>\n\n\n\n<li>the truth is in the logs<\/li>\n\n\n\n<li>continuity is in the automatic verification<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">External logging: Google Cloud<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Logging is divided into two complementary channels.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Google Cloud Logging (streaming)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>near-real-time ingestion of rsync logs<\/li>\n\n\n\n<li>quick queries<\/li>\n\n\n\n<li>immediate troubleshooting<\/li>\n\n\n\n<li>very low costs (often zero)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It is used for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>analyses<\/li>\n\n\n\n<li>debug<\/li>\n\n\n\n<li>operational visibility<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Google Cloud Storage (immutable snapshots)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>daily log upload<\/li>\n\n\n\n<li>deterministic path <code>YYYY\/MM\/DD<\/code><\/li>\n\n\n\n<li>versioning and retention<\/li>\n\n\n\n<li>service account with minimal permissions<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">It is used for:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>audit<\/li>\n\n\n\n<li>independent verification<\/li>\n\n\n\n<li>n8n workflow input<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This bucket is the <strong>source of truth<\/strong> for monitoring.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The role of n8n (monitoring, not backup)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The n8n workflow <strong>does not perform backups<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It only does three things, in order:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Check attendance<\/strong><br>Do all the logs expected for today exist?<\/li>\n\n\n\n<li><strong>Check integrity<\/strong><br>Does each log have a consistent START\/END\/SUMMARY?<\/li>\n\n\n\n<li><strong>Semantic verification<\/strong><br>Errors? Warnings? Abnormal patterns?<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">If something is missing or not correct \u2192 alert.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why n8n?<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>readable workflows<\/li>\n\n\n\n<li>versionable<\/li>\n\n\n\n<li>extendable<\/li>\n\n\n\n<li>decoupled from the server<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The value is not \u201cautomation\u201d, but <strong>the validation<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Where to buy it<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Official shop<\/strong>: <a href=\"https:\/\/shop.paoloronco.it\/23-backup-sync-execution-validation-log-driven.html\">https:\/\/shop.paoloronco.it<\/a><\/li>\n\n\n\n<li><strong>Gumroad<\/strong>: <a href=\"https:\/\/paoloronco.gumroad.com\/l\/ReliableBackup-SyncExecutionValidation\">https:\/\/paoloronco.gumroad.com<\/a><\/li>\n\n\n\n<li><strong>n8n Creators Hub \/ Templates<\/strong>: coming soon<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Because it&#039;s a paid workflow<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This workflow:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>encapsulates months of real-world debugging<\/li>\n\n\n\n<li>handles edge cases (incomplete logs, duplicates, partial runs)<\/li>\n\n\n\n<li>it is meant to be <strong>extended<\/strong>, not only used<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">He doesn&#039;t sell magic.<br>Sells <strong>time saved<\/strong> And <strong>mistakes avoided<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where to buy it<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>shop.paoloronco.it<\/li>\n\n\n\n<li>Gumroad<\/li>\n\n\n\n<li>n8n Creators Hub (template)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The sensitive code remains mine.<br>The architecture and principles are shared.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Safety and key principles<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Single-purpose Service Account<\/li>\n\n\n\n<li>minimum permits<\/li>\n\n\n\n<li>no credentials in the workflow<\/li>\n\n\n\n<li>no direct access to the server<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If n8n were to be compromised:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>can only <em>read public logs for him<\/em><\/li>\n\n\n\n<li>cannot touch backups or servers<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is <strong>zero trust applied to a homelab<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">What did this architecture take away from me?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>daily manual check<\/li>\n\n\n\n<li>anxiety about \u201chas he left?\u201d<\/li>\n\n\n\n<li>night debugging<\/li>\n\n\n\n<li>useless logs<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">In exchange I got:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>measurable trust<\/li>\n\n\n\n<li>significant alerts<\/li>\n\n\n\n<li>audit trail<\/li>\n\n\n\n<li>operational serenity<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This project was not born to demonstrate that <em>it can be done<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It was born because <strong>I didn&#039;t want to waste time doubting backups anymore<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Even in a homelab, data matters.<br>And when data matters, <strong>Logging is part of backup<\/strong>, not a detail.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Over-engineering? Perhaps.<br>But it is over-engineering that sleeps peacefully.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>","protected":false},"excerpt":{"rendered":"<p>Questo articolo nasce da un problema molto concreto: sapere davvero se i backup funzionano, senza dover controllare manualmente log sparsi, script cron opachi e notifiche poco affidabili. Nel mio homelab (che resta pur sempre una casa, non un data center enterprise) ho deciso di applicare principi da Business Continuity e Disaster Recovery tipici di ambienti &hellip; <a href=\"https:\/\/paoloronco.it\/en\/over-engineering-un-homelab-logging-backup-e-business-continuity-con-ubuntu-server\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Over\u2011Engineering un Homelab: Logging, Backup e Business Continuity con Ubuntu Server&#8221;<\/span><\/a><\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1833","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/posts\/1833","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/comments?post=1833"}],"version-history":[{"count":2,"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/posts\/1833\/revisions"}],"predecessor-version":[{"id":1836,"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/posts\/1833\/revisions\/1836"}],"wp:attachment":[{"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/media?parent=1833"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/categories?post=1833"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/paoloronco.it\/en\/wp-json\/wp\/v2\/tags?post=1833"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}