--- schema_version: 1 id: home-server.networking type: reference scope: [jupiter, mercury, dns, ingress, wireguard, cloudflare] sensitivity: private-infrastructure last_reviewed: "2026-10-06" sources: - kind: owner-report reference: "Network topology, Mercury nginx/iptables/fail2ban output, LAN DNS, 2026-10-06" - kind: owner-report reference: "Cloudflare ingress YAML and client SSH configurations, 2026-10-06" - kind: owner-report reference: "CGNAT rationale, Docker network and container inspection, 2026-10-06" - kind: repository repository: jupiter-stacks revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 paths: - 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 related: [home-server.reference, home-server.host, home-server.mercury, home-server.seedbox, home-server.authentication, home-server.operations] update_triggers: [dns-change, proxy-change, tunnel-change, firewall-change, certificate-change] unknowns: - 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 ```mermaid --- 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["Public clients"] subgraph VPS["MERCURY / PUBLIC EDGE"] Nginx["nginx
185.230.217.66
WireGuard 10.0.0.1"] Uptime["Uptime service
127.0.0.1:3001"] Nginx -->|"uptime.umbra.mom / HTTP"| Uptime end subgraph Home["JUPITER / HOME SERVER"] LAN["LAN clients
Jellyfin / Bookshelf local DNS"] Traefik["Traefik
192.168.1.56
WireGuard 10.0.0.2"] Apps["Application containers
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 ```mermaid --- 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["SSH client
Host jupiter / user jay"] Git["Git over SSH
Host git.umbra.mom / user git"] Cloud["Cloudflare Tunnel edge
Access policy unconfirmed"] subgraph Home["JUPITER / HOME SERVER"] Connector["cloudflared
Locally configured ingress"] SSH["Host sshd
192.168.1.56:22"] Gitea["Gitea SSH
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`. ```sshconfig 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](mercury.md): 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.