Post 015


The Problem

My portfolio at tima.dev, hosted on Netlify, a fully public site started showing a Chrome prompt: "Access other devices on your local network." When I clicked Block, every Ghost blog feature image broke. The Holocron Logs section turned into a wall of broken image icons.

This is a Chrome security feature called Private Network Access (PNA), and it catches an edge case that anyone running a homelab alongside a public portfolio will eventually hit.


What Chrome Private Network Access Does

Chrome enforces a boundary between public and private networks. If a page loaded from a public origin (like Netlify) tries to fetch resources from a private/local IP address, Chrome intercepts the request and asks for permission.

The logic:

Public site (tima.dev on Netlify)
    → Fetches Ghost API for blog posts
    → Ghost returns feature_image URLs pointing to holocron-labs.tima.dev
    → holocron-labs.tima.dev resolves to... what?

If the DNS resolution for holocron-labs.tima.dev returns a private IP, either because Cloudflare isn't proxying, or because your local DNS override catches it first. Then, Chrome flags it as a public-to-private network request and blocks it.


The Root Cause

The issue was in Cloudflare's DNS settings for the holocron-labs subdomain.

DNS-Only Mode (Gray Cloud) - Broken

holocron-labs.tima.dev → CNAME → holocron-labs.ghost.io (DNS only)

In DNS-only mode, Cloudflare returns the actual IP address of Ghost's servers. But here's the problem: when you're on your home network, AdGuard's wildcard rewrite catches *.tima.dev first and returns your NPM IP (192.168.1.101). Chrome sees a public Netlify page trying to load images from 192.168.1.101 - a private IP - and blocks it.

Proxied Mode (Orange Cloud) - Fixed

holocron-labs.tima.dev → CNAME → holocron-labs.ghost.io (Proxied)

With Cloudflare proxying enabled, DNS queries return Cloudflare's public IPs (e.g., 104.x.x.x), not your homelab IP. Chrome sees public-to-public and allows the request.

The fix: Enable the orange cloud (Proxied) on the holocron-labs CNAME record in Cloudflare.


Why This Is Subtle

The portfolio works perfectly on mobile data, from the office, from any network that isn't your home. It only breaks at home because that's where the AdGuard wildcard rewrite exists. If you only test from home, you'd think it's a code problem. If you only test from outside, you'd never see it.

The debugging path:

  1. ✗ "Is my Ghost API key wrong?" No, the API returns data fine
  2. ✗ "Is Netlify blocking cross-origin images?" No, CORS headers are fine
  3. ✗ "Is my JavaScript broken?" No, it works on mobile
  4. ✓ "Why does Chrome only block this at home?" Because local DNS resolves to a private IP

The Fix in Detail

Cloudflare DNS Panel

Before:

Type: CNAME
Name: holocron-labs
Target: holocron-labs.ghost.io
Proxy: DNS only (gray cloud)

After:

Type: CNAME
Name: holocron-labs
Target: holocron-labs.ghost.io
Proxy: Proxied (orange cloud)

NPM Proxy Host (for Home Access)

Even with Cloudflare proxying, you still need NPM to handle holocron-labs.tima.dev when you're home and going through the AdGuard wildcard. Add a proxy host:

  • Domain: holocron-labs.tima.dev
  • Scheme: https
  • Forward Hostname: holocron-labs.ghost.io
  • Forward Port: 443
  • SSL: Let's Encrypt

This ensures the chain works identically whether you're home or remote.


Who This Affects

Anyone who:

  1. Hosts a public portfolio/website (Netlify, Vercel, GitHub Pages)
  2. Fetches content from a self-hosted service (Ghost, API, etc.)
  3. Uses local DNS overrides (AdGuard, Pi-hole) with wildcard rules
  4. Tests their site from their home network

If your public site loads any resource from a domain that resolves to a private IP on your local network, Chrome will flag it. The symptom is intermittent - works everywhere except home - which makes it incredibly frustrating to debug.


Prevention

For anyone running a homelab with a public-facing portfolio:

  1. Proxy external services through Cloudflare - any subdomain that serves content to your public site should return Cloudflare IPs, not your homelab IP
  2. Add specific NPM proxy hosts for external services - don't rely solely on the AdGuard wildcard; make sure NPM knows how to route traffic for services that live outside your network
  3. Test from both home and external networks - the PNA issue only manifests when local DNS is involved

Related: Post 013 - NPM Rebuild and Cloudflare DNS Migration covers the Cloudflare and NPM wildcard setup.