Files
notes/docs/home-server/networking.md
T
2026-10-06 16:33:29 +01:00

8.2 KiB

schema_version, id, type, scope, sensitivity, last_reviewed, sources, related, update_triggers, unknowns
schema_version id type scope sensitivity last_reviewed sources related update_triggers unknowns
1 home-server.networking reference
jupiter
mercury
dns
ingress
wireguard
cloudflare
private-infrastructure 2026-10-06
kind reference
owner-report Network topology, Mercury nginx/iptables/fail2ban output, LAN DNS, 2026-10-06
kind reference
owner-report Cloudflare ingress YAML and client SSH configurations, 2026-10-06
kind reference
owner-report CGNAT rationale, Docker network and container inspection, 2026-10-06
kind repository revision paths
repository jupiter-stacks 610e325ef6a98850511ce7a089b0b9a77bfecf15
stacks/traefik/docker-compose.yml
stacks/cloudflared/docker-compose.yml
stacks/gitea/docker-compose.yml
stacks/jellyfin/docker-compose.yml
stacks/audiobookshelf/docker-compose.yml
home-server.reference
home-server.host
home-server.mercury
home-server.seedbox
home-server.authentication
home-server.operations
dns-change
proxy-change
tunnel-change
firewall-change
certificate-change
public-dns-record-types-and-cloudflare-proxy-status
cloudflare-access-applications-and-login-policies
wireguard-allowedips-and-keepalive
jupiter-firewall-policy
traefik-forwarded-header-trust-configuration

Networking

HTTPS ingress

---
config:
  themeCSS: |
    .node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; }
    .cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; }
    .label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; }
    .edgeLabel, .edgeLabel p { background-color: var(--infra-zone) !important; color: var(--infra-text) !important; }
    .flowchart-link { stroke: var(--infra-line) !important; }
    marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; }
    .infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; }
    .infraEdge rect { fill: var(--infra-edge-fill) !important; stroke: var(--infra-edge-stroke) !important; }
    .infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; }
  flowchart:
    curve: basis
    nodeSpacing: 35
    rankSpacing: 55
---
flowchart TB
  Internet["<b>Public clients</b>"]

  subgraph VPS["MERCURY / PUBLIC EDGE"]
    Nginx["<b>nginx</b><br/>185.230.217.66<br/>WireGuard 10.0.0.1"]
    Uptime["<b>Uptime service</b><br/>127.0.0.1:3001"]
    Nginx -->|"uptime.umbra.mom / HTTP"| Uptime
  end

  subgraph Home["JUPITER / HOME SERVER"]
    LAN["<b>LAN clients</b><br/>Jellyfin / Bookshelf local DNS"]
    Traefik["<b>Traefik</b><br/>192.168.1.56<br/>WireGuard 10.0.0.2"]
    Apps["<b>Application containers</b><br/>Docker network: traefik"]
    LAN -->|"HTTPS :443"| Traefik
    Traefik -->|"Application routing"| Apps
  end

  Internet -->|"HTTPS :443"| Nginx
  Nginx -->|"HTTPS :443 over WireGuard"| Traefik

  class Internet,LAN infraClient
  class Nginx,Traefik infraEdge
  class Apps,Uptime infraService

Cloudflare Tunnel

---
config:
  themeCSS: |
    .node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; }
    .cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; }
    .label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; }
    .edgeLabel, .edgeLabel p { background-color: var(--infra-zone) !important; color: var(--infra-text) !important; }
    .flowchart-link { stroke: var(--infra-line) !important; }
    marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; }
    .infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; }
    .infraCloud rect { fill: var(--infra-cloud-fill) !important; stroke: var(--infra-cloud-stroke) !important; }
    .infraEdge rect { fill: var(--infra-edge-fill) !important; stroke: var(--infra-edge-stroke) !important; }
    .infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; }
  flowchart:
    curve: basis
    nodeSpacing: 35
    rankSpacing: 45
---
flowchart TB
  Shell["<b>SSH client</b><br/>Host jupiter / user jay"]
  Git["<b>Git over SSH</b><br/>Host git.umbra.mom / user git"]
  Cloud["<b>Cloudflare Tunnel edge</b><br/>Access policy unconfirmed"]

  subgraph Home["JUPITER / HOME SERVER"]
    Connector["<b>cloudflared</b><br/>Locally configured ingress"]
    SSH["<b>Host sshd</b><br/>192.168.1.56:22"]
    Gitea["<b>Gitea SSH</b><br/>192.168.1.56:2222"]
    Connector -->|"ssh.umbra.mom / SSH :22"| SSH
    Connector -->|"git.umbra.mom / SSH :2222"| Gitea
  end

  Shell -->|"cloudflared access ssh / ssh.umbra.mom"| Cloud
  Git -->|"cloudflared access ssh / git.umbra.mom"| Cloud
  Cloud <-->|"Tunnel traffic / initiated outbound by Jupiter"| Connector

  class Shell,Git infraClient
  class Cloud infraCloud
  class Connector infraEdge
  class SSH,Gitea infraService

Client cloudflared transports SSH through Cloudflare; Jupiter's connector opens the outbound tunnel. Origins still perform SSH authentication. No Mercury, WireGuard, or Traefik hop on this path; no home-router port forwarding.

Ingress hostname Origin Client SSH user
ssh.umbra.mom ssh://192.168.1.56:22 jay
git.umbra.mom ssh://192.168.1.56:2222 git
Unmatched http_status:404 N/A

Both client configurations use IdentityFile ~/.ssh/id_ed25519.

Host jupiter
    User jay
    IdentityFile ~/.ssh/id_ed25519
    ProxyCommand cloudflared access ssh --hostname ssh.umbra.mom

Host git.umbra.mom
    User git
    IdentityFile ~/.ssh/id_ed25519
    ProxyCommand cloudflared access ssh --hostname %h

Local tunnel configuration is separate from Cloudflare Access applications. Access login/provider/allow rules remain unconfirmed; ProxyCommand alone does not establish enforcement.

Ingress source: host /data/cloudflared/config.yaml, mounted at /etc/cloudflared/config.yaml.

DNS / routes

DNS / route Destination
Public service ingress Mercury 185.230.217.66
jellyfin.local.umbra.mom Jupiter 192.168.1.56
bookshelf.local.umbra.mom Jupiter 192.168.1.56
ssh.umbra.mom, git.umbra.mom Cloudflare Tunnel
uptime.umbra.mom Mercury nginx -> http://127.0.0.1:3001

Mercury

Mercury VPS: native nginx; WireGuard server 10.0.0.1/24; Jupiter client 10.0.0.2/24. Hosting, TLS, firewall and fail2ban details are maintained on that page.

Home ISP uses CGNAT. Mercury supplies a static public IPv4 and forwards over WireGuard; owner preference is to avoid public ingress via the residential IP.

Observed Docker networks

Snapshot from supplied inspection, not fixed-address configuration. Container IPs may change; use Docker service names.

Network IPv4 subnet Internal Observed role
traefik 172.18.0.0/16 No Reverse proxy and application ingress
gitea_network 172.23.0.0/16 No Gitea, runner, notes builder, Havox sync
cloudflared_cloudflare 172.26.0.0/16 No Tunnel connector
gitea_data 192.168.96.0/20 Yes Gitea and PostgreSQL
paperless_data 192.168.112.0/20 Yes Paperless, PostgreSQL, Redis
dozzle_dozzle 172.27.0.0/16 Yes Dozzle and socket proxy

Cloudflared's observed 172.26.0.2 matches the supplied proxied SSH login source. An internal network alone does not isolate a multi-network container from egress through its other networks. Empty network entries do not establish whether they are obsolete or safe to remove.

Jupiter: repository configuration

  • Traefik publishes TCP 80,443; HTTP redirects to HTTPS.
  • Docker provider: opt-in traefik.enable=true; external network traefik.
  • File provider: /data/traefik/dynamic.
  • ACME: Let's Encrypt; Cloudflare DNS-01; /data/traefik/acme/acme.json; apex + *.umbra.mom certificate requested.
  • HTTPS fallback: priority 1; unmatched non-apex hosts redirect to https://umbra.mom.
  • cloudflared: /data/cloudflared -> /etc/cloudflared; stack-local bridge cloudflare; no published ports.