Last updated: 2026-05-31
NPM has two distinct roles depending on whether the request is internal or external.
Internal (on-network) access:
Pi-hole resolves all *.pittsfamily.me subdomains to NPM at 10.0.2.14. The browser connects directly to NPM via HTTPS, and NPM proxies to the backend. This path does not touch Cloudflare.
External (internet) access:
Cloudflare Tunnel routes external traffic directly to each backend IP:port, bypassing NPM entirely. This is intentional — NPM has Force SSL enabled on all hosts, which causes HTTP→HTTPS redirect loops if traffic were proxied through NPM externally (the tunnel delivers plain HTTP to backends). See the Cloudflare Tunnel page.
On-network:
Browser → Pi-hole DNS → 10.0.2.14 (NPM) → backend
External:
Browser → Cloudflare edge → cloudflared tunnel → backend:port (direct)
| LXC | 101 on proxmox2 |
| IPv4 | 10.0.2.14 |
| Admin UI | npm.pittsfamily.me → 10.0.2.14:81 |
| Credentials | Vaultwarden → Nginx Proxy Manager — Account — Claude |
| Co-located with | cloudflared container (same Docker Compose, same LXC) |
All hosts use HTTPS with a valid Let's Encrypt cert. Internal clients reach backends via these NPM proxy entries; external clients use Cloudflare Tunnel routes instead (same backends, different path).
| Hostname | Backend | Public via Tunnel? |
|---|---|---|
| ai.pittsfamily.me | 10.0.2.19:8080 |
No |
| calibre.pittsfamily.me | 10.0.2.21:8083 |
Yes |
| cloud.pittsfamily.me | 10.0.2.10:30125 |
Yes |
| dedup.pittsfamily.me | 10.0.2.10:8086 |
No |
| help.pittsfamily.me | 10.0.2.23:3000 |
Yes |
| homeassistant.pittsfamily.me | 10.0.2.12:8123 |
Yes |
| lidarr.pittsfamily.me | 10.0.2.21:8686 |
No |
| mcp.pittsfamily.me | 10.0.2.18:3100 |
Yes |
| nas.pittsfamily.me | 10.0.2.10:443 |
No |
| navidrome.pittsfamily.me | 10.0.2.21:4533 |
Yes |
| npm.pittsfamily.me | 10.0.2.14:81 |
No |
| paperless.pittsfamily.me | 10.0.2.10:30070 |
Yes |
| paperless-ai.pittsfamily.me | 10.0.2.10:8086 |
No |
| photos.pittsfamily.me | 10.0.2.10:2283 |
Yes |
| pihole.pittsfamily.me | 10.0.2.15:80 |
No |
| prowlarr.pittsfamily.me | 10.0.2.21:9696 |
No |
| proxmox1.pittsfamily.me | 10.0.2.11:8006 |
No |
| proxmox2.pittsfamily.me | 10.0.2.13:8006 |
No |
| router.pittsfamily.me | 10.0.2.1:443 |
No |
| torrent.pittsfamily.me | 10.0.50.10:8080 |
No |
| uptime.pittsfamily.me | 10.0.2.21:3001 |
No |
| vault.pittsfamily.me | 10.0.2.17:80 |
Yes |
All 14 Let's Encrypt certificates use DNS challenge via the Cloudflare API (not HTTP challenge). This works even though there are no port-forwards from the internet.
| Challenge method | DNS-01 via Cloudflare API |
| API token | Vaultwarden → Cloudflare — API Token — NPM DNS |
| Renewed | 2026-08-26 (all 14 certs batch-renewed) |
| Renewal period | 90 days; NPM auto-renews when <30 days remaining |
Why DNS challenge? Port 80/443 are not forwarded from the internet (all public traffic goes through Cloudflare Tunnel). DNS challenge lets NPM prove domain ownership via the Cloudflare API without needing inbound ports.
Add a new internal-only service:
new-service.pittsfamily.me → 10.0.2.14.pct exec 102 -- pihole-FTL --config dns.hosts and add both A and AAAA entries for the new hostname.Add a new public service:
Do the above, then also add a Cloudflare Tunnel public hostname — see Cloudflare Tunnel.
If a subdomain resolves to Cloudflare IPs on-network:
The Pi-hole AAAA or HTTPS override is missing. See Pi-hole → DNS Architecture section.
If external access gives a 502 or SSL error:
Check the Cloudflare Tunnel page. The tunnel bypasses NPM — NPM being down does not affect external access.
Check NPM health:
ssh proxmox2 'pct exec 101 -- docker ps | grep npm'
ssh proxmox2 'pct exec 101 -- docker logs npm-app-1 --tail 20'