Homelab Network School
Everything you need to understand your network โ IPs, DNS, ports, VLANs, HTTPS, and Wireguard.
Built around your actual stack. Pick a topic below.
IP Addresses
Octets, private vs public ranges, subnet masks, CIDR notation. Why your server is 192.168.x.x and what every number means.
DNS
How name resolution works, record types (A, CNAME, TXT, PTR), Pi-hole as your network resolver, and local DNS for your services.
Ports
Well-known ports, every port your homelab services use, port forwarding in pfSense, TCP vs UDP. The apartment-number analogy.
VLANs & DHCP
Network segmentation, DHCP leases, static IPs, your four VLANs (Private / Guest / IoT / CCTV) and firewall rules between them.
HTTPS & Nginx
HTTP vs HTTPS, TLS handshake, how Nginx Proxy Manager reverse-proxies all your services, and free Let's Encrypt certs via DuckDNS.
Wireguard VPN
How Wireguard works, tunnels vs port forwarding, OPNsense integration, client setup, and reaching your homelab from anywhere securely.
IP Addresses
An IP address is a 32-bit number that identifies every device on a network.
Written as four numbers (octets) separated by dots. Every number is 0โ255.
1 # subnet โ your network number inside 192.168.x.x
42 # host โ this specific device. .1 is almost always your router
168 = 10101000
1 = 00000001
42 = 00101010
192.168.1.50 # your gaming PC
192.168.1.1 # your router (always .1 by convention)
192.168.1.255 # broadcast โ sent to every device on this subnet
192.168.1.0 # network address โ "name" of this subnet, not assignable
192.168.10.0/24 # your planned Private LAN VLAN
192.168.20.0/24 # your planned Guest VLAN
192.168.30.0/24 # your planned IoT VLAN
192.168.40.0/24 # your planned CCTV VLAN
172.16.0.0/12 # Class B โ Docker bridge network lives here
127.0.0.1 # loopback โ "myself." A service listening here is local-only
/24 = 255.255.255.0 # 254 hosts โ your VLANs (192.168.10.0/24)
/16 = 255.255.0.0 # 65,534 hosts โ Docker internal networks
/8 = 255.0.0.0 # 16 million โ entire 10.x.x.x private space
/30 = 255.255.255.252 # 2 hosts โ point-to-point links
/24 โ 2^8 - 2 = 254 hosts
/25 โ 2^7 - 2 = 126 hosts
/16 โ 2^16 - 2 = 65,534 hosts
ip addr show eth0 # just eth0
ip route show # routing table โ shows your gateway
ip route | grep default # just the default gateway line
ping -c 4 192.168.1.1 # test if gateway is reachable
ping -c 4 8.8.8.8 # test internet (skips DNS)
ping -c 4 google.com # test internet + DNS
DNS โ Domain Name System
DNS translates hostnames into IP addresses. Every time you type a URL,
DNS runs before a single byte of content loads. Your Pi-hole controls this for your whole network.
sudo resolvectl flush-caches # Linux: clear DNS cache
sudo dscacheutil -flushcache # macOS: clear DNS cache
nslookup jellyfin.home # test resolving a local name
dig jellyfin.home @192.168.10.5 # query Pi-hole directly
1.0.0.1 # Cloudflare secondary
8.8.8.8 # Google โ fast, but Google logs queries
9.9.9.9 # Quad9 โ blocks malware domains too
r720.home โ 192.168.10.100 # another name for the same IP
pihole.home โ 192.168.10.5 # Pi-hole's own dashboard
jellyfin.home โ server.home # CNAME
nextcloud.home โ server.home # CNAME
navidrome.home โ server.home # CNAME
dig google.com @1.1.1.1 # ask Cloudflare directly
nslookup jellyfin.home 192.168.10.5 # older but works everywhere
dig +short google.com # just the IP, no extra output
dig jellyfin.home +trace # full resolution chain
Ports
An IP address gets you to the right building. A port number gets you to the right apartment.
Your server has one IP โ ports tell traffic which of the dozens of services to talk to.
UDP # connectionless. Faster, no guarantees. Game servers, DNS (port 53), Wireguard.
ssh -p 2222 user@192.168.10.100 # custom port
ssh-keygen -t ed25519 # generate key pair
ssh-copy-id user@192.168.10.100 # push public key (passwordless login)
- "53:53/tcp"
- "53:53/udp" # both required in docker-compose
https://jellyfin.yourdomain.com # external via NPM (preferred)
# apps like Symfonium use the Subsonic API on the same port
http://192.168.10.100:8080 # internal (NPM admin panel)
rtsp://192.168.10.100:8554/camera_name # RTSP stream
http://192.168.10.100:11434 # Ollama API (internal only)
http://192.168.10.5:8082/admin # if NPM owns port 80
Friends connect to: yourpublicip:25565
# or dahamsterserver.duckdns.org:25565
Zomboid: 16261 UDP (game), 16262 UDP (direct)
Pavlov: 7777 UDP
# all forwarded WAN โ 192.168.10.100:port
ss -tulpn | grep :8096 # is Jellyfin listening?
netstat -tulpn # older alternative
nmap -p 1-65535 192.168.10.100 # scan all ports on server from another machine
curl -I http://localhost:8096 # test if service responds
VLANs & DHCP
VLANs are virtual separate networks on the same physical switch. Your managed switch tags traffic
by VLAN ID so different device groups are completely isolated from each other.
โ Access to server rack โ all services
โ Access to Proxmox UI (8006)
โ Can reach all other VLANs via explicit rules
192.168.10.5 # Pi-hole (DNS)
192.168.10.10 # managed switch (management IP)
192.168.10.100 # R720xd server (Proxmox)
192.168.10.50 # gaming PC (DHCP reservation)
192.168.10.50+ # everything else dynamic DHCP
โ No access to server rack
โ No access to any other VLAN
โ Can reach Frigate only (optional, explicit rule)
โ No access to Proxmox, server management
โ No access to Guest or CCTV VLANs
โ No access to any other VLAN
โ No DNS needed
Offer # pfSense replies "here's 192.168.10.45, available for 24h"
Request # device says "yes I'll take 192.168.10.45"
Acknowledge # pfSense confirms + sends: subnet mask, gateway, DNS server
โ DHCP Static Mappings โ Add
โ enter MAC address + desired IP + hostname
ip link show # Linux: find your MAC address
ip addr show eth0 # MAC is the "link/ether" line
VLAN 20: 192.168.20.50 โ 192.168.20.200
VLAN 30: 192.168.30.50 โ 192.168.30.200
VLAN 40: 192.168.40.50 โ 192.168.40.100 # fewer cameras needed
2. Pi-hole returns 192.168.10.100 (local A record)
3. Phone connects TCP to 192.168.10.100:8096
4. Jellyfin responds, streams media
# total: under 20ms inside your LAN
2. DuckDNS resolves to your current public IP
3. TCP hits pfSense on WAN port 25565
4. pfSense NAT forwards to 192.168.10.100:25565
5. Minecraft container accepts connection
6. Friend is in your world
2. Gets IP 192.168.20.x via DHCP, DNS = 1.1.1.1
3. pfSense firewall blocks all traffic to VLAN 10
4. Guest can browse internet
5. 192.168.10.x doesn't exist from their perspective
6. Your R720xd is completely invisible to them
2. pfSense allows VLAN 40 โ 192.168.10.100:5000 only
3. Frigate AI detects a person in the frame
4. Clip is saved to R720xd storage (TrueNAS dataset)
5. Notification sent (Home Assistant webhook)
6. Camera never touches the internet โ not once
HTTPS & Nginx Proxy Manager
Every service you expose externally needs HTTPS. Nginx Proxy Manager sits at the edge of your server,
handles TLS, and routes traffic to the right container by subdomain.
โ Session cookies protected from hijacking
โ File uploads/downloads encrypted
โ API keys and tokens protected
โ ISP cannot see what you're doing (only that you're talking to your server)
2. Server hello # server: picks algorithm, sends TLS certificate
3. Cert verify # browser: is cert signed by a trusted CA? domain match? expired?
4. Key exchange # both sides derive a shared session key (private to this session)
5. Encrypted data # all subsequent traffic encrypted. Handshake done.
nextcloud.yourdomain.com โ localhost:5000 # SSL on
music.yourdomain.com โ localhost:4533 # SSL on
ai.yourdomain.com โ localhost:3000 # SSL on
books.yourdomain.com โ localhost:8083 # SSL on
frigate.yourdomain.com โ localhost:5000 # LAN-only (no external access)
npm:
image: jc21/nginx-proxy-manager:latest
ports:
- "80:80" # HTTP (redirects to 443)
- "443:443" # HTTPS (all proxied services)
- "81:81" # admin panel
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
Domain: *.yourname.duckdns.org
Challenge: DNS / DuckDNS
API Token: [your DuckDNS token]
# one cert covers all subdomains โ jellyfin, nextcloud, music, etc.
โ Auto-renews every 90 days โ zero maintenance
โ DuckDNS API token handles the DNS-01 challenge automatically
services:
duckdns:
image: lscr.io/linuxserver/duckdns:latest
environment:
- SUBDOMAINS=dahamsterserver
- TOKEN=your-duckdns-token
- TZ=America/New_York
restart: unless-stopped
Wireguard VPN
Wireguard is the VPN built into OPNsense. Connect from anywhere and your phone/laptop behaves
as if it's sitting on your home LAN โ reaching Proxmox, Jellyfin, Pi-hole, everything.
โ OPNsense (Wireguard server, 10.0.0.1)
โ routes to 192.168.10.x (your LAN)
โ 192.168.10.100 (R720xd) responds
โ back through tunnel โ your phone
โ Proxmox web UI
โ Frigate camera feeds
โ Pi-hole admin
โ Portainer
โ Anything admin or sensitive
Your website (Nginx) โ public-facing
Jellyfin/Nextcloud (via NPM) โ family access without VPN setup
cat privatekey # your private key โ never share this
cat publickey # give this to OPNsense
Listen Port: 51820
Tunnel Address: 10.0.0.1/24 # VPN subnet, not your LAN
DNS Server: 192.168.10.5 # Pi-hole โ VPN clients get ad blocking too
Disable Routes: unchecked
Name: my-phone
Public Key: [your phone's public key]
Allowed IPs: 10.0.0.2/32 # VPN IP assigned to this client
โ Add rule:
Action: Pass
Interface: WireGuard
Source: WireGuard net (10.0.0.0/24)
Destination: LAN net (192.168.10.0/24)
Protocol: any
# this lets VPN clients reach your entire VLAN 10
PrivateKey = [your device private key]
Address = 10.0.0.2/24 # VPN IP for this device
DNS = 192.168.10.5 # Pi-hole โ use it even while remote
[Peer]
PublicKey = [OPNsense server public key]
Endpoint = dahamsterserver.duckdns.org:51820
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 10.0.0.0/24
# routes only your home subnets through tunnel
# (split tunnel โ other internet goes direct)
PersistentKeepalive = 25 # keep NAT hole punched open
AllowedIPs = 192.168.10.0/24 # split tunnel โ only home LAN through VPN (faster)
wg show wg0 # specific interface
wg-quick up wg0 # bring tunnel up (Linux client)
wg-quick down wg0 # bring tunnel down
systemctl enable wg-quick@wg0 # auto-connect on boot
โ iOS shortcut: "when not on home WiFi, enable Wireguard"
โ Works on LTE, 5G, hotel WiFi โ anything with internet
โ Check port 51820 UDP is forwarded in OPNsense WAN rules
โ Check OPNsense Wireguard service is running
โ Check DuckDNS resolved to your current public IP (curl ifconfig.me)
# Connected but can't reach LAN
โ Check AllowedIPs in client config includes 192.168.10.0/24
โ Check OPNsense firewall rule: WireGuard โ LAN is Pass
โ Check your server's subnet routes
# DNS not working over VPN
โ Check DNS = 192.168.10.5 in [Interface] section of config
โ Check Pi-hole is allowing queries from 10.0.0.0/24