<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>self-hosting on Samirtech</title>
    <link>https://samirtech.org/tags/self-hosting/</link>
    <description>Recent content in self-hosting on Samirtech</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 16 Sep 2026 10:00:00 +0100</lastBuildDate><atom:link href="https://samirtech.org/tags/self-hosting/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Is My Homelab Actually Working? Glance, Gatus, Grafana and Useful Alerts</title>
      <link>https://samirtech.org/posts/how-i-monitor-and-protect-my-homelab/</link>
      <pubDate>Wed, 16 Sep 2026 10:00:00 +0100</pubDate>
      
      <guid>https://samirtech.org/posts/how-i-monitor-and-protect-my-homelab/</guid>
      <description>Installing an application is the easy part. Knowing whether it still works a week later is a different job.
Once I had several services running, opening each one to check it became tedious. A photo library might load its homepage but fail to access storage. A container might be running while its application is stuck. Several perfectly healthy apps might appear offline because the shared DNS server or reverse proxy has failed.</description>
      <content>&lt;p&gt;Installing an application is the easy part. Knowing whether it still works a week later is a different job.&lt;/p&gt;
&lt;p&gt;Once I had several services running, opening each one to check it became tedious. A photo library might load its homepage but fail to access storage. A container might be running while its application is stuck. Several perfectly healthy apps might appear offline because the shared DNS server or reverse proxy has failed.&lt;/p&gt;
&lt;p&gt;I wanted a simple way to answer three questions: &lt;strong&gt;what can I use, what has gone wrong, and does it need my attention now?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;My setup uses Glance, Gatus and Grafana for different parts of that job, with Gotify for local monitoring notifications and an external Healthchecks.io heartbeat that alerts me directly through Telegram. Security monitoring sits alongside availability monitoring, rather than being confused with it.&lt;/p&gt;
&lt;h2 id=&#34;the-short-version&#34;&gt;The short version&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What I use it for&lt;/th&gt;
&lt;th&gt;What it does not prove&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Glance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;My everyday starting page: app links and quick availability indicators&lt;/td&gt;
&lt;td&gt;That every feature behind a working page is healthy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gatus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Repeated endpoint checks, response times and a record of availability&lt;/td&gt;
&lt;td&gt;That a successful HTTP request means a complete application workflow works&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grafana&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Looking at metrics and logs to understand a problem or a trend&lt;/td&gt;
&lt;td&gt;That a graph without errors guarantees a secure system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gotify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bringing selected notifications to my attention&lt;/td&gt;
&lt;td&gt;That every failure has an alert rule, or that a delivered message was read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Healthchecks.io → Telegram&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Warning me when the server stops reporting to an independent service&lt;/td&gt;
&lt;td&gt;Which dependency failed, or whether individual applications still work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The distinction matters. A dashboard is somewhere I look. A notification is something that comes to me. Neither is useful unless the underlying checks mean something.&lt;/p&gt;
&lt;h2 id=&#34;glance-the-page-i-open-first&#34;&gt;Glance: the page I open first&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/glanceapp/glance&#34;&gt;Glance&lt;/a&gt; is the front door to the lab. It gives me a manageable set of bookmarks and monitor widgets instead of a collection of addresses and ports to remember.&lt;/p&gt;
&lt;p&gt;My configuration groups checks by the services on the Docker host and the NAS web interfaces. That makes it easier to see whether one app needs attention or whether several services on the same machine have stopped responding.&lt;/p&gt;
&lt;p&gt;It is particularly useful from a phone: open one page, find the service, then follow its link. I do not need a full metrics dashboard just to open the music library.&lt;/p&gt;
&lt;p&gt;But I treat a green indicator as a narrow statement: &lt;strong&gt;the configured check succeeded&lt;/strong&gt;. It is not a promise that login, playback, uploads, background jobs and storage access all work. A bookmark alone is not a health check either.&lt;/p&gt;
&lt;h2 id=&#34;gatus-checking-rather-than-guessing&#34;&gt;Gatus: checking rather than guessing&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/TwiN/gatus&#34;&gt;Gatus&lt;/a&gt; performs recurring checks and keeps the results. This is where I look when I want more than a quick current-state indicator.&lt;/p&gt;
&lt;p&gt;The checks in my setup include application web endpoints, the HTTPS route to Grafana, and DNS checks for both local names and upstream resolution. They test expected response conditions and response-time limits.&lt;/p&gt;
&lt;p&gt;That helps separate failures which otherwise look identical in a browser:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One application fails:&lt;/strong&gt; start with that service and its dependencies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Several friendly URLs fail together:&lt;/strong&gt; investigate shared DNS and proxy dependencies before restarting every app.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A direct application route works but its friendly URL does not:&lt;/strong&gt; the access path may be broken even though the application is running.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A page responds but is consistently slow:&lt;/strong&gt; there may be a developing problem rather than a complete outage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where the check runs matters too. A server-side check cannot prove that a phone using different DNS settings, another network or a VPN will get the same result. When a device has a problem, I still test from that device.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An important detail in my current setup:&lt;/strong&gt; Gatus is providing status checks, but its configuration does not currently define alert providers. A failed Gatus check therefore should not be described as automatically producing a Gotify notification. That is a separate integration to configure and test if I want it.&lt;/p&gt;
&lt;h2 id=&#34;grafana-understanding-why&#34;&gt;Grafana: understanding why&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://grafana.com/oss/grafana/&#34;&gt;Grafana&lt;/a&gt; is where I go for more detail. Glance is the starting page; Gatus records whether checks pass; Grafana helps me investigate what was happening around a failure.&lt;/p&gt;
&lt;p&gt;Behind Grafana, Prometheus collects metrics and Loki stores logs. Exporters and collectors make host, container and other infrastructure information available to those systems. Grafana displays the data; it does not create reliable measurements simply because a dashboard exists.&lt;/p&gt;
&lt;p&gt;My provisioned dashboards cover areas such as server health, the Docker fleet, storage, NAS applications, internet connection health and container logs.&lt;/p&gt;
&lt;p&gt;The useful questions are practical:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Was memory pressure building before the application stopped responding?&lt;/li&gt;
&lt;li&gt;Is the disk getting full, or is storage activity unusually high?&lt;/li&gt;
&lt;li&gt;Did the slowdown affect the whole host or just one container?&lt;/li&gt;
&lt;li&gt;Did errors appear around the time of an update?&lt;/li&gt;
&lt;li&gt;Is an internet problem affecting several otherwise unrelated services?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A graph can show a trend that a green status dot misses. A service can still respond while its remaining disk space is steadily disappearing.&lt;/p&gt;
&lt;p&gt;I also need to distinguish &lt;strong&gt;no data&lt;/strong&gt; from &lt;strong&gt;everything is fine&lt;/strong&gt;. If an exporter stops reporting, or a dashboard query no longer matches the available metrics, an empty panel is not evidence of good health. Check the data source and timestamps before trusting the picture.&lt;/p&gt;
&lt;h2 id=&#34;notifications-useful-enough-that-i-keep-reading-them&#34;&gt;Notifications: useful enough that I keep reading them&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://gotify.net/&#34;&gt;Gotify&lt;/a&gt; is the notification hub in this setup. It gives separate monitoring jobs somewhere to send a short, actionable message rather than requiring me to watch dashboards all day.&lt;/p&gt;
&lt;p&gt;The surrounding setup includes container-health notifications, image-update monitoring and a dedicated security-notification collector. These are separate paths; their coverage is not identical to the list of checks shown in Gatus.&lt;/p&gt;
&lt;p&gt;An image-update notice means a new image is available for review. It does not mean the running version is compromised, and it is not permission to update every stateful service automatically. I want to know what changed, check any migration requirements, and have a recovery path first.&lt;/p&gt;
&lt;p&gt;For security notifications, the design is deliberately low-noise: grouped CrowdSec decisions, repeated SSH failures rather than every individual failed attempt, and selected higher-severity Falco events. Filtering is a trade-off, not complete coverage. Lower-severity events may still deserve investigation even when they do not generate an immediate notification.&lt;/p&gt;
&lt;p&gt;A useful alert should tell me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Which service or security component needs attention.&lt;/li&gt;
&lt;li&gt;What was observed, without overstating what it proves.&lt;/li&gt;
&lt;li&gt;When it happened and where I can investigate.&lt;/li&gt;
&lt;li&gt;Whether the condition has recovered, when recovery reporting is supported.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Repeated copies of the same message do not make the system safer. Grouping, sensible thresholds and cooldowns help keep notifications readable. Failed delivery needs retry handling, and a labelled test message is how I check the delivery path—not an assumption that a configured URL must work.&lt;/p&gt;
&lt;h2 id=&#34;security-is-a-separate-layer&#34;&gt;Security is a separate layer&lt;/h2&gt;
&lt;p&gt;Availability monitoring asks, “Does this respond?” Security monitoring asks, “Is something happening that should not be?” A service can answer every health check while being misconfigured or compromised.&lt;/p&gt;
&lt;p&gt;My broader approach combines restricted access, SSH keys, protected credentials, deliberate updates and recoverable backups with monitoring. The local HTTPS arrangement is explained in &lt;a href=&#34;https://samirtech.org/posts/local-https-without-exposing-my-homelab/&#34;&gt;Local HTTPS Without Exposing My Homelab to the Internet&lt;/a&gt;. A certificate encrypts and authenticates a connection; it does not decide who is allowed to reach the service.&lt;/p&gt;
&lt;p&gt;Two security tools in the setup have distinct roles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://www.crowdsec.net/&#34;&gt;CrowdSec&lt;/a&gt;&lt;/strong&gt; analyses supported activity and can create decisions about suspicious sources. A decision is not proof that traffic was blocked: enforcement depends on a correctly configured and functioning remediation component, often called a bouncer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://falco.org/&#34;&gt;Falco&lt;/a&gt;&lt;/strong&gt; detects runtime behaviour against rules. An alert is a reason to investigate the event, application and timing—not automatic proof of malware, and not something to dismiss just because the container looks healthy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I do not want raw command arguments, tokens or sensitive log contents copied into phone notifications. The notification should identify the problem; detailed evidence can stay in the protected system where it belongs.&lt;/p&gt;
&lt;p&gt;Dashboards need protection too. They can reveal application names, infrastructure layout, activity and logs. A monitoring page is not harmless just because it does not contain an obvious “delete” button.&lt;/p&gt;
&lt;h2 id=&#34;what-i-do-when-something-looks-wrong&#34;&gt;What I do when something looks wrong&lt;/h2&gt;
&lt;p&gt;For a photo-library problem, my investigation would look like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start in Glance.&lt;/strong&gt; Is it one service or a wider group? Follow the link and confirm what actually fails.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check Gatus.&lt;/strong&gt; Did the endpoint fail, become slow, or remain reachable? Is DNS affected too?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Grafana.&lt;/strong&gt; Compare the relevant time window with resource metrics, storage signals and logs. Check that the data is fresh.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read related notifications.&lt;/strong&gt; Look for a matching health event, security event or recent maintenance notice. Timing is evidence to investigate, not proof of a cause.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make the smallest justified change.&lt;/strong&gt; Avoid restarting the entire stack when the evidence points to one dependency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify the actual user task.&lt;/strong&gt; Load a photo, play a file or complete the action that originally failed. A recovered status dot alone is not enough.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is an example troubleshooting method, not a claim that a particular outage occurred or that every application has a synthetic end-to-end test.&lt;/p&gt;
&lt;h2 id=&#34;the-monitoring-system-can-fail-as-well&#34;&gt;The monitoring system can fail as well&lt;/h2&gt;
&lt;p&gt;There is an obvious limitation to running much of this on the same always-on host: if that host goes down, its dashboards and notification jobs may go down with it.&lt;/p&gt;
&lt;p&gt;No message does not necessarily mean no problem. It can mean the collector stopped, the network failed, or Gotify became unreachable.&lt;/p&gt;
&lt;h3 id=&#34;what-i-added-an-external-heartbeat&#34;&gt;What I added: an external heartbeat&lt;/h3&gt;
&lt;p&gt;I have now added a heartbeat to the hosted &lt;a href=&#34;https://healthchecks.io/&#34;&gt;Healthchecks.io&lt;/a&gt; service. A scheduled job on the always-on server sends an outbound HTTPS request every five minutes. If those requests stop arriving, Healthchecks can alert me directly through Telegram, without relying on the server or its Gotify instance to send the message.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Home server → outbound heartbeat → Healthchecks.io
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                       │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                              Missing or failed heartbeat
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                       │
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                       v
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                    Telegram
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is sometimes called a &lt;em&gt;dead man’s switch&lt;/em&gt;: instead of trusting silence, an independent system expects regular evidence that the job is still running. It requires no new inbound port, public dashboard or second local monitoring stack.&lt;/p&gt;
&lt;p&gt;The installation preserved the existing scheduled jobs. The ping URL is kept in an owner-only configuration file outside the website repository, with bounded connection and request timeouts and retries. A private success timestamp is updated only after an accepted ping. The URL is a secret: anyone who knows it could send false heartbeats, so it does not belong in an article, screenshot or public configuration example.&lt;/p&gt;
&lt;h3 id=&#34;timing-and-what-it-actually-tells-me&#34;&gt;Timing and what it actually tells me&lt;/h3&gt;
&lt;p&gt;The server&amp;rsquo;s verified sending interval is &lt;strong&gt;five minutes&lt;/strong&gt;. The timing I settled on for the external check is a &lt;strong&gt;five-minute period with a thirty-minute grace window&lt;/strong&gt;. With those settings saved in Healthchecks, an alert is due about &lt;strong&gt;thirty-five minutes after the last successful ping&lt;/strong&gt;. The grace window avoids alerts for short interruptions, at the cost of slower warning.&lt;/p&gt;
&lt;p&gt;Those provider-side settings still need confirmation in the account dashboard; the ping URL can report success or failure but cannot read or change the check&amp;rsquo;s schedule. The server sending every five minutes does not, by itself, establish the alert deadline.&lt;/p&gt;
&lt;p&gt;A missing heartbeat can mean the server is down, its scheduler has failed, or the home internet or power connection is unavailable. It does not distinguish those causes. Telegram delivery also depends on Healthchecks, Telegram and my device having connectivity; during a home internet outage, a phone may need mobile data.&lt;/p&gt;
&lt;p&gt;This is a &lt;strong&gt;host-reporting check, not an application-health check&lt;/strong&gt;. It can keep succeeding while Docker or an individual app is broken. A separate heartbeat that reports success only after selected local health checks pass would be a useful next layer, but it has not been installed.&lt;/p&gt;
&lt;h3 id=&#34;the-test-i-actually-completed&#34;&gt;The test I actually completed&lt;/h3&gt;
&lt;p&gt;On 16 September 2026, the first ping was accepted and a subsequent scheduled run was independently observed. I then tested the notification path by sending an explicit failure signal to Healthchecks, followed by a successful recovery heartbeat. Healthchecks acknowledged the failure request, accepted the recovery, and I received both the &lt;strong&gt;DOWN&lt;/strong&gt; and &lt;strong&gt;UP&lt;/strong&gt; messages in Telegram.&lt;/p&gt;
&lt;p&gt;No server or application was stopped, and the normal five-minute schedule remained in place. This confirms the explicit failure-and-recovery notification path. It does &lt;strong&gt;not&lt;/strong&gt; yet prove the missing-heartbeat timeout: that needs a separate controlled pause of only the heartbeat job, leaving the Healthchecks check enabled, followed by restoring the job and confirming recovery.&lt;/p&gt;
&lt;p&gt;The external heartbeat reduces the silent-host-failure gap; it does not make the local services redundant. The same applies to DNS: monitoring one resolver does not remove it as a single point of failure.&lt;/p&gt;
&lt;p&gt;Monitoring configuration, dashboard definitions and alert rules also belong in the backup plan. Recovery instructions should remain accessible without relying entirely on the system being recovered.&lt;/p&gt;
&lt;h2 id=&#34;what-was-checked-for-this-draft&#34;&gt;What was checked for this draft&lt;/h2&gt;
&lt;p&gt;A read-only check on 16 September 2026 found Glance, Gatus, Grafana, Prometheus, Loki and Gotify running. Gatus&amp;rsquo;s returned latest results were successful for its configured endpoints, and Grafana&amp;rsquo;s health endpoint reported its database as OK. The configured checks, Glance widgets and dashboard definitions were also inspected.&lt;/p&gt;
&lt;p&gt;CrowdSec and Falco reported active. The dedicated security collector&amp;rsquo;s timer was enabled, and its most recent recorded run had a successful exit status. Those are operational checks, not proof that every security event is collected or that every notification reaches a device.&lt;/p&gt;
&lt;p&gt;The later Healthchecks failure-and-recovery test confirmed Telegram delivery as described above. It did not test the Gotify or security-alert delivery paths. This drafting work did not trigger a security event, verify CrowdSec enforcement, test every Grafana panel or audit the complete network boundary. Privileged SSH-policy inspection was unavailable, so this is not a fresh certification of the host&amp;rsquo;s effective SSH settings. The article describes the setup and its limits, not a security guarantee.&lt;/p&gt;
&lt;h2 id=&#34;why-i-think-this-matters&#34;&gt;Why I think this matters&lt;/h2&gt;
&lt;p&gt;The point is not to build an enterprise monitoring department at home. It is to stop discovering problems only when someone wants to use an application.&lt;/p&gt;
&lt;p&gt;Good monitoring gives me an earlier warning, a clearer place to start and less temptation to change things blindly. Useful notifications let me step away without pretending that I am watching every graph. Security checks help flag behaviour that an uptime check cannot see.&lt;/p&gt;
&lt;p&gt;Backups complete the picture, but they answer a different question: &lt;strong&gt;can I recover?&lt;/strong&gt; A green dashboard cannot answer that. That takes &lt;a href=&#34;https://samirtech.org/posts/testing-a-restore-before-disaster-strikes/&#34;&gt;a restore test&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My rule is simple: &lt;strong&gt;Glance to get there, Gatus to check availability, Grafana to investigate, and notifications for the things worth interrupting me about.&lt;/strong&gt; Keep the checks honest, understand the gaps, and test the user-facing result.&lt;/p&gt;
</content>
    </item>
    
    <item>
      <title>Local HTTPS Without Exposing My Homelab to the Internet</title>
      <link>https://samirtech.org/posts/local-https-without-exposing-my-homelab/</link>
      <pubDate>Wed, 16 Sep 2026 03:18:48 +0100</pubDate>
      
      <guid>https://samirtech.org/posts/local-https-without-exposing-my-homelab/</guid>
      <description>Remembering an address and a different port for every service gets old quickly. I wanted names I could bookmark, HTTPS that worked without certificate warnings, and no accidental public access to private applications.
Those are three separate problems. DNS supplies the address, TLS verifies and encrypts the connection, and routing and firewall rules determine who can reach the service. Solving one does not automatically solve the others.
The layout I use My always-on Docker host runs Caddy as the reverse proxy and CoreDNS for private service-name resolution.</description>
      <content>&lt;p&gt;Remembering an address and a different port for every service gets old quickly. I wanted names I could bookmark, HTTPS that worked without certificate warnings, and no accidental public access to private applications.&lt;/p&gt;
&lt;p&gt;Those are three separate problems. DNS supplies the address, TLS verifies and encrypts the connection, and routing and firewall rules determine who can reach the service. Solving one does not automatically solve the others.&lt;/p&gt;
&lt;h2 id=&#34;the-layout-i-use&#34;&gt;The layout I use&lt;/h2&gt;
&lt;p&gt;My always-on Docker host runs Caddy as the reverse proxy and CoreDNS for private service-name resolution. The applications keep their existing direct ports while the proxy provides the friendlier route.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Phone or computer
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ├─ DNS query → local resolver → private proxy address
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  └─ HTTPS request → Caddy → application on its existing port
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Certificate renewal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  └─ Caddy → DNS-provider API → temporary public DNS challenge
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example, &lt;code&gt;photos.example.com&lt;/code&gt; could resolve to the proxy&amp;rsquo;s private address on the home network. The browser connects to Caddy, which forwards the request to the photo application. The example names here are placeholders, not my private configuration.&lt;/p&gt;
&lt;h2 id=&#34;a-public-certificate-does-not-require-a-public-application&#34;&gt;A public certificate does not require a public application&lt;/h2&gt;
&lt;p&gt;ACME DNS-01 validation proves control of a domain using a DNS TXT record. The certificate authority checks that record; it does not need to reach the private photo application.&lt;/p&gt;
&lt;p&gt;Caddy needs a build with the appropriate DNS-provider module. In my setup that is the Cloudflare module, with a restricted API token supplied through a private environment file. The token must not appear in Git, examples, screenshots or command output.&lt;/p&gt;
&lt;p&gt;This lets the proxy obtain a publicly trusted wildcard certificate without opening an inbound web port solely for certificate validation. It still needs outbound access to the certificate authority and DNS provider. Working renewal also depends on the token remaining valid and the proxy&amp;rsquo;s state surviving container replacement.&lt;/p&gt;
&lt;p&gt;The certificate does not enforce privacy. Router port forwards, firewall rules, tunnels and application authentication must still be checked separately. Some services may have deliberately configured remote access; adding a local certificate should not create or change that access accidentally.&lt;/p&gt;
&lt;p&gt;Publicly trusted certificates are recorded in certificate-transparency logs. A wildcard can avoid listing each individual service hostname, but the certificate&amp;rsquo;s domain name is not secret.&lt;/p&gt;
&lt;h2 id=&#34;why-the-client-dns-setting-matters&#34;&gt;Why the client DNS setting matters&lt;/h2&gt;
&lt;p&gt;A correct CoreDNS record is useful only if the device actually asks a resolver that knows about it.&lt;/p&gt;
&lt;p&gt;My workstation routes the relevant domain to the local resolver. Other clients need an equivalent arrangement, normally through the network&amp;rsquo;s DNS policy. A phone using a different resolver, an encrypted-DNS setting or a VPN may follow another path.&lt;/p&gt;
&lt;p&gt;That explains a common failure: the service works by its direct address, but its friendly name does not. Before changing the application, check which resolver the client is using.&lt;/p&gt;
&lt;p&gt;Do not rely on a public secondary DNS server as a fallback for private records. Clients do not necessarily consult servers in the order expected, and an authoritative negative answer is not a request to try somewhere else.&lt;/p&gt;
&lt;p&gt;When extending private resolution for a domain that also hosts a public website or email, preserve those public records. Only the intended service names should receive private answers; other queries need the correct forwarding path.&lt;/p&gt;
&lt;h2 id=&#34;why-not-just-use-a-private-certificate-authority&#34;&gt;Why not just use a private certificate authority?&lt;/h2&gt;
&lt;p&gt;That is a valid alternative. Names under &lt;code&gt;home.arpa&lt;/code&gt; can use a private CA, but each client must trust that CA. Trust stores may differ between browsers and operating systems, and some devices make importing a CA inconvenient.&lt;/p&gt;
&lt;p&gt;For my public-domain service names, DNS-01 certificates avoid that extra trust installation. Clients still need the right DNS and network access. My older private-CA aliases are a separate naming and trust arrangement, not interchangeable certificates.&lt;/p&gt;
&lt;p&gt;Avoid inventing &lt;code&gt;.local&lt;/code&gt; names for ordinary unicast DNS: &lt;code&gt;.local&lt;/code&gt; is reserved for multicast DNS.&lt;/p&gt;
&lt;h2 id=&#34;keep-the-original-access-route-during-the-change&#34;&gt;Keep the original access route during the change&lt;/h2&gt;
&lt;p&gt;I kept direct application ports available while introducing the proxy. That gave me a way to distinguish a proxy problem from an application problem without rebuilding the application.&lt;/p&gt;
&lt;p&gt;This is a migration safeguard, not a reason to expose every port widely. Direct access and proxied access each need an intentional firewall and authentication policy.&lt;/p&gt;
&lt;p&gt;The proxy should route only explicitly configured hostnames. Adding a wildcard certificate must not silently turn every management interface into a reachable website.&lt;/p&gt;
&lt;h2 id=&#34;checks-that-matter&#34;&gt;Checks that matter&lt;/h2&gt;
&lt;p&gt;For a setup like this, I would verify each layer separately:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;DNS:&lt;/strong&gt; the client&amp;rsquo;s normal resolver returns the intended private address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS:&lt;/strong&gt; the browser or command-line client accepts the certificate without bypassing verification.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Routing:&lt;/strong&gt; Caddy sends the request to the correct application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application behavior:&lt;/strong&gt; login, redirects, uploads and any application-specific connections still work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Continuity:&lt;/strong&gt; existing direct routes and unrelated services still behave as before.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exposure:&lt;/strong&gt; router, firewall and tunnel configuration match the intended access policy.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These example commands are checks, not installation instructions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Replace the placeholder hostname with your own service.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;getent ahosts photos.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl --head --show-error https://photos.example.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Do not add &lt;code&gt;--insecure&lt;/code&gt; to make a certificate error disappear. A redirect or authentication response may be expected; a successful HTTP response alone does not test every application feature. Some applications do not support HEAD requests, so use a normal browser request when appropriate.&lt;/p&gt;
&lt;p&gt;This article describes the deployed design, not a fresh security audit of every service or a claim that every household device has been configured.&lt;/p&gt;
&lt;h2 id=&#34;the-trade-off&#34;&gt;The trade-off&lt;/h2&gt;
&lt;p&gt;A shared proxy and resolver make access simpler, but they become shared dependencies. If either fails, several friendly URLs can stop working even while the applications remain healthy.&lt;/p&gt;
&lt;p&gt;Their configuration, certificate state and protected credentials belong in the recovery plan. Keep a private record of the direct access routes as well. The useful result is fewer addresses to remember—not a system that can only be repaired through the very proxy that has failed.&lt;/p&gt;
&lt;h3 id=&#34;official-references&#34;&gt;Official references&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://caddyserver.com/docs/automatic-https&#34;&gt;Caddy automatic HTTPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/caddy-dns/cloudflare&#34;&gt;Caddy Cloudflare DNS module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://letsencrypt.org/docs/challenge-types/&#34;&gt;Let&amp;rsquo;s Encrypt challenge types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://coredns.io/plugins/hosts/&#34;&gt;CoreDNS hosts plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My main lesson is simple: a trusted certificate proves a name, not a network boundary. Keep DNS, certificate trust and access control separate, then test them together from the device that will actually use the service.&lt;/p&gt;
</content>
    </item>
    
  </channel>
</rss>
