---
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.