mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-13 18:00:35 +08:00
feat: add homelab config skills (VLAN segmentation, Pi-hole DNS, WireGuard VPN) (#1838)
* feat: add homelab config skills (VLAN, Pi-hole, WireGuard) Adds three homelab configuration skills, extracted from the stale PR #1413 with the same safety treatment applied to the previously accepted batch: - homelab-vlan-segmentation: IoT/guest/trusted/server VLAN design for UniFi, pfSense/OPNsense, and MikroTik. All firewall rules add isolation, not remove protections. Added change-window guidance and AP trunk port clarification. - homelab-pihole-dns: Pi-hole install, blocklists, DNS-over-HTTPS, local DNS records, troubleshooting. Docker is now the lead install method; bare-metal uses inspect-first pattern before running the installer script. - homelab-wireguard-vpn: WireGuard server, peer config, split tunnel, DDNS. Replaced broad iptables FORWARD ACCEPT with scoped directional rules (wg0→eth0 forward + established return only). Credentials moved to env files with explicit notes against inline secrets and version control. Continues the contribution from PR #1413; the eight skills/agents from that PR are already in main via #1729 and #1731. * docs: harden homelab skill pack --------- Co-authored-by: Affaan Mustafa <affaan@dcube.ai>
This commit is contained in:
parent
7f3dfde6d7
commit
71ed7c58d4
@ -11,7 +11,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"source": "./",
|
||||
"description": "The most comprehensive Claude Code plugin — 60 agents, 225 skills, 75 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning",
|
||||
"description": "The most comprehensive Claude Code plugin — 60 agents, 228 skills, 75 legacy command shims, selective install profiles, and production-ready hooks for TDD, security scanning, code review, and continuous learning",
|
||||
"version": "2.0.0-rc.1",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ecc",
|
||||
"version": "2.0.0-rc.1",
|
||||
"description": "Battle-tested Claude Code plugin for engineering teams — 60 agents, 225 skills, 75 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use",
|
||||
"description": "Battle-tested Claude Code plugin for engineering teams — 60 agents, 228 skills, 75 legacy command shims, production-ready hooks, and selective install workflows evolved through continuous real-world use",
|
||||
"author": {
|
||||
"name": "Affaan Mustafa",
|
||||
"url": "https://x.com/affaanmustafa"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — Agent Instructions
|
||||
|
||||
This is a **production-ready AI coding plugin** providing 60 specialized agents, 225 skills, 75 commands, and automated hook workflows for software development.
|
||||
This is a **production-ready AI coding plugin** providing 60 specialized agents, 228 skills, 75 commands, and automated hook workflows for software development.
|
||||
|
||||
**Version:** 2.0.0-rc.1
|
||||
|
||||
@ -150,7 +150,7 @@ Troubleshoot failures: check test isolation → verify mocks → fix implementat
|
||||
|
||||
```
|
||||
agents/ — 60 specialized subagents
|
||||
skills/ — 225 workflow skills and domain knowledge
|
||||
skills/ — 228 workflow skills and domain knowledge
|
||||
commands/ — 75 slash commands
|
||||
hooks/ — Trigger-based automations
|
||||
rules/ — Always-follow guidelines (common + per-language)
|
||||
|
||||
@ -358,7 +358,7 @@ If you stacked methods, clean up in this order:
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**That's it!** You now have access to 60 agents, 225 skills, and 75 legacy command shims.
|
||||
**That's it!** You now have access to 60 agents, 228 skills, and 75 legacy command shims.
|
||||
|
||||
### Dashboard GUI
|
||||
|
||||
@ -1362,7 +1362,7 @@ The configuration is automatically detected from `.opencode/opencode.json`.
|
||||
|---------|-------------|----------|--------|
|
||||
| Agents | PASS: 60 agents | PASS: 12 agents | **Claude Code leads** |
|
||||
| Commands | PASS: 75 commands | PASS: 35 commands | **Claude Code leads** |
|
||||
| Skills | PASS: 225 skills | PASS: 37 skills | **Claude Code leads** |
|
||||
| Skills | PASS: 228 skills | PASS: 37 skills | **Claude Code leads** |
|
||||
| Hooks | PASS: 8 event types | PASS: 11 events | **OpenCode has more!** |
|
||||
| Rules | PASS: 29 rules | PASS: 13 instructions | **Claude Code leads** |
|
||||
| MCP Servers | PASS: 14 servers | PASS: Full | **Full parity** |
|
||||
@ -1467,7 +1467,7 @@ ECC is the **first plugin to maximize every major AI coding tool**. Here's how e
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **Agents** | 60 | Shared (AGENTS.md) | Shared (AGENTS.md) | 12 |
|
||||
| **Commands** | 75 | Shared | Instruction-based | 35 |
|
||||
| **Skills** | 225 | Shared | 10 (native format) | 37 |
|
||||
| **Skills** | 228 | Shared | 10 (native format) | 37 |
|
||||
| **Hook Events** | 8 types | 15 types | None yet | 11 types |
|
||||
| **Hook Scripts** | 20+ scripts | 16 scripts (DRY adapter) | N/A | Plugin hooks |
|
||||
| **Rules** | 34 (common + lang) | 34 (YAML frontmatter) | Instruction-based | 13 instructions |
|
||||
|
||||
@ -160,7 +160,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**完成!** 你现在可以使用 60 个代理、225 个技能和 75 个命令。
|
||||
**完成!** 你现在可以使用 60 个代理、228 个技能和 75 个命令。
|
||||
|
||||
### multi-* 命令需要额外配置
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Everything Claude Code (ECC) — 智能体指令
|
||||
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 60 个专业代理、225 项技能、75 条命令以及自动化钩子工作流,用于软件开发。
|
||||
这是一个**生产就绪的 AI 编码插件**,提供 60 个专业代理、228 项技能、75 条命令以及自动化钩子工作流,用于软件开发。
|
||||
|
||||
**版本:** 2.0.0-rc.1
|
||||
|
||||
@ -147,7 +147,7 @@
|
||||
|
||||
```
|
||||
agents/ — 60 个专业子代理
|
||||
skills/ — 225 个工作流技能和领域知识
|
||||
skills/ — 228 个工作流技能和领域知识
|
||||
commands/ — 75 个斜杠命令
|
||||
hooks/ — 基于触发的自动化
|
||||
rules/ — 始终遵循的指导方针(通用 + 每种语言)
|
||||
|
||||
@ -224,7 +224,7 @@ Copy-Item -Recurse rules/typescript "$HOME/.claude/rules/"
|
||||
/plugin list ecc@ecc
|
||||
```
|
||||
|
||||
**搞定!** 你现在可以使用 60 个智能体、225 项技能和 75 个命令了。
|
||||
**搞定!** 你现在可以使用 60 个智能体、228 项技能和 75 个命令了。
|
||||
|
||||
***
|
||||
|
||||
@ -1138,7 +1138,7 @@ opencode
|
||||
|---------|-------------|----------|--------|
|
||||
| 智能体 | PASS: 60 个 | PASS: 12 个 | **Claude Code 领先** |
|
||||
| 命令 | PASS: 75 个 | PASS: 35 个 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 225 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 技能 | PASS: 228 项 | PASS: 37 项 | **Claude Code 领先** |
|
||||
| 钩子 | PASS: 8 种事件类型 | PASS: 11 种事件 | **OpenCode 更多!** |
|
||||
| 规则 | PASS: 29 条 | PASS: 13 条指令 | **Claude Code 领先** |
|
||||
| MCP 服务器 | PASS: 14 个 | PASS: 完整 | **完全对等** |
|
||||
@ -1246,7 +1246,7 @@ ECC 是**第一个最大化利用每个主要 AI 编码工具的插件**。以
|
||||
|---------|------------|------------|-----------|----------|
|
||||
| **智能体** | 60 | 共享 (AGENTS.md) | 共享 (AGENTS.md) | 12 |
|
||||
| **命令** | 75 | 共享 | 基于指令 | 35 |
|
||||
| **技能** | 225 | 共享 | 10 (原生格式) | 37 |
|
||||
| **技能** | 228 | 共享 | 10 (原生格式) | 37 |
|
||||
| **钩子事件** | 8 种类型 | 15 种类型 | 暂无 | 11 种类型 |
|
||||
| **钩子脚本** | 20+ 个脚本 | 16 个脚本 (DRY 适配器) | N/A | 插件钩子 |
|
||||
| **规则** | 34 (通用 + 语言) | 34 (YAML 前页) | 基于指令 | 13 条指令 |
|
||||
|
||||
274
skills/homelab-pihole-dns/SKILL.md
Normal file
274
skills/homelab-pihole-dns/SKILL.md
Normal file
@ -0,0 +1,274 @@
|
||||
---
|
||||
name: homelab-pihole-dns
|
||||
description: Pi-hole installation, blocklist management, DNS-over-HTTPS setup, DHCP integration, local DNS records, and troubleshooting broken DNS resolution on a home network.
|
||||
origin: community
|
||||
---
|
||||
|
||||
# Homelab Pi-hole DNS
|
||||
|
||||
Pi-hole is a network-wide DNS ad blocker that runs on a Raspberry Pi or any Linux host.
|
||||
Every device on your network gets ad and malware domain blocking automatically — no browser
|
||||
extension needed.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Installing Pi-hole on a Raspberry Pi or Linux host
|
||||
- Configuring Pi-hole as the DNS server for a home network
|
||||
- Adding or managing blocklists
|
||||
- Setting up DNS-over-HTTPS (DoH) upstream resolvers
|
||||
- Creating local DNS records (e.g. `nas.home.lan`, `pi.home.lan`)
|
||||
- Troubleshooting devices that lose internet access after Pi-hole is installed
|
||||
- Running Pi-hole alongside or instead of DHCP
|
||||
|
||||
## How Pi-hole Works
|
||||
|
||||
```
|
||||
Normal flow (without Pi-hole):
|
||||
Device → requests ads.tracker.com → ISP DNS → real IP → ads load
|
||||
|
||||
With Pi-hole:
|
||||
Device → requests ads.tracker.com → Pi-hole DNS → blocked (returns 0.0.0.0) → no ad
|
||||
|
||||
All DNS queries go through Pi-hole first.
|
||||
Pi-hole checks against blocklists.
|
||||
Blocked domains return a null response — the ad/tracker never loads.
|
||||
Allowed domains get forwarded to your upstream resolver (Cloudflare, Google, etc.).
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Docker (Recommended)
|
||||
|
||||
Docker is the easiest way to install Pi-hole and makes updates and backups
|
||||
straightforward.
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
pihole:
|
||||
image: pihole/pihole:<pinned-release-tag>
|
||||
container_name: pihole
|
||||
ports:
|
||||
- "53:53/tcp"
|
||||
- "53:53/udp"
|
||||
- "80:80/tcp" # Web admin
|
||||
environment:
|
||||
TZ: "America/New_York"
|
||||
WEBPASSWORD: "${PIHOLE_WEBPASSWORD}" # set via .env file or secret
|
||||
PIHOLE_DNS_: "1.1.1.1;1.0.0.1"
|
||||
DNSMASQ_LISTENING: "all"
|
||||
volumes:
|
||||
- "./etc-pihole:/etc/pihole"
|
||||
- "./etc-dnsmasq.d:/etc/dnsmasq.d"
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN # only needed if Pi-hole will serve DHCP
|
||||
```
|
||||
|
||||
Replace `<pinned-release-tag>` with a current Pi-hole release tag before deploying.
|
||||
Avoid `latest` for long-lived DNS infrastructure so upgrades are deliberate and
|
||||
reviewable.
|
||||
|
||||
Set `PIHOLE_WEBPASSWORD` in a `.env` file next to `docker-compose.yml`, chmod it to
|
||||
`600`, and keep it out of git — do not put the password directly in the compose file.
|
||||
|
||||
Access web admin at: `http://<pi-ip>/admin`
|
||||
|
||||
### Bare-Metal Install (Raspberry Pi OS / Debian / Ubuntu)
|
||||
|
||||
Pi-hole requires a static IP before installing.
|
||||
|
||||
```bash
|
||||
# Step 1: Assign a static IP (edit /etc/dhcpcd.conf on Pi OS)
|
||||
sudo nano /etc/dhcpcd.conf
|
||||
# Add at the bottom:
|
||||
interface eth0
|
||||
static ip_address=192.168.3.2/24
|
||||
static routers=192.168.3.1
|
||||
static domain_name_servers=192.168.3.1
|
||||
|
||||
# Step 2: Download and inspect the installer before running it.
|
||||
# Prefer the package or installer path documented by Pi-hole for your OS/version.
|
||||
curl -sSL https://install.pi-hole.net -o pi-hole-install.sh
|
||||
less pi-hole-install.sh # review before proceeding
|
||||
|
||||
# Step 3: Run
|
||||
bash pi-hole-install.sh
|
||||
|
||||
# Follow the interactive installer:
|
||||
# 1. Select network interface (eth0 for wired — recommended)
|
||||
# 2. Select upstream DNS (Cloudflare or leave default — can change later)
|
||||
# 3. Confirm static IP
|
||||
# 4. Install the web admin interface (recommended)
|
||||
# 5. Note the admin password shown at the end
|
||||
```
|
||||
|
||||
## Pointing Your Network at Pi-hole
|
||||
|
||||
```
|
||||
# Method 1: Change DNS in your router DHCP settings (recommended)
|
||||
Router admin UI → DHCP Settings → DNS Server
|
||||
Primary DNS: 192.168.3.2 (Pi-hole IP)
|
||||
Secondary DNS: leave blank for strict blocking, or use a second Pi-hole.
|
||||
A public fallback such as 1.1.1.1 improves availability during
|
||||
rollout but can bypass blocking because clients may query it.
|
||||
|
||||
All devices get Pi-hole as DNS automatically on next DHCP renewal.
|
||||
Force renewal: reconnect Wi-Fi or run 'sudo dhclient -r && sudo dhclient' on Linux
|
||||
|
||||
# Method 2: Per-device DNS (useful for testing before network-wide rollout)
|
||||
Windows: Control Panel → Network Adapter → IPv4 Properties → set DNS manually
|
||||
macOS: System Settings → Network → Details → DNS → set manually
|
||||
Linux: /etc/resolv.conf or NetworkManager
|
||||
|
||||
# Method 3: Pi-hole as DHCP server (replaces router DHCP)
|
||||
Pi-hole admin → Settings → DHCP → Enable
|
||||
Disable DHCP on your router first — two DHCP servers on the same network cause conflicts
|
||||
Advantage: hostname resolution works automatically (devices register their names)
|
||||
```
|
||||
|
||||
## Blocklist Management
|
||||
|
||||
```
|
||||
# Pi-hole admin → Adlists → Add new adlist
|
||||
|
||||
# Recommended blocklists:
|
||||
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
|
||||
# default — 200k+ domains
|
||||
|
||||
https://blocklistproject.github.io/Lists/malware.txt
|
||||
# malware domains
|
||||
|
||||
https://blocklistproject.github.io/Lists/tracking.txt
|
||||
# tracking/telemetry
|
||||
|
||||
# After adding a list:
|
||||
Tools → Update Gravity (downloads and compiles all blocklists)
|
||||
|
||||
# If a site is blocked that should not be (false positive):
|
||||
Pi-hole admin → Whitelist → Add domain
|
||||
Example: api.my-legitimate-service.com
|
||||
|
||||
# Check what is being blocked in real time:
|
||||
Dashboard → Query Log (live DNS query stream with block/allow status)
|
||||
```
|
||||
|
||||
## DNS-over-HTTPS Upstream
|
||||
|
||||
DNS-over-HTTPS encrypts your DNS queries so your ISP cannot see what sites you resolve.
|
||||
|
||||
```bash
|
||||
# Install cloudflared (Cloudflare's DoH proxy).
|
||||
# Prefer Cloudflare's package repository for automatic signed package verification.
|
||||
# If you download a binary directly, pin a release version and verify its checksum.
|
||||
CLOUDFLARED_VERSION="<pinned-version>"
|
||||
curl -LO "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64"
|
||||
# Verify the checksum/signature from Cloudflare's release notes before installing.
|
||||
sudo mv cloudflared-linux-arm64 /usr/local/bin/cloudflared
|
||||
sudo chmod +x /usr/local/bin/cloudflared
|
||||
|
||||
# Create cloudflared config
|
||||
sudo mkdir -p /etc/cloudflared
|
||||
sudo tee /etc/cloudflared/config.yml << EOF
|
||||
proxy-dns: true
|
||||
proxy-dns-port: 5053
|
||||
proxy-dns-upstream:
|
||||
- https://1.1.1.1/dns-query
|
||||
- https://1.0.0.1/dns-query
|
||||
EOF
|
||||
|
||||
# Create systemd service
|
||||
sudo cloudflared service install
|
||||
sudo systemctl start cloudflared
|
||||
sudo systemctl enable cloudflared
|
||||
|
||||
# Now point Pi-hole at the local DoH proxy:
|
||||
# Pi-hole admin → Settings → DNS → Custom upstream DNS
|
||||
# Set to: 127.0.0.1#5053
|
||||
# Uncheck all other upstream resolvers
|
||||
```
|
||||
|
||||
## Local DNS Records
|
||||
|
||||
Make your services reachable by name (e.g. `nas.home.lan`, `grafana.home.lan`).
|
||||
|
||||
> **Domain name note:** `.home.lan` is widely used in homelabs and works in practice.
|
||||
> The IETF-reserved suffix for local use is `.home.arpa` (RFC 8375) — use that to
|
||||
> follow the standard. Avoid `.local` for Pi-hole DNS records as it conflicts with
|
||||
> mDNS/Bonjour.
|
||||
|
||||
```
|
||||
# Pi-hole admin → Local DNS → DNS Records
|
||||
|
||||
Domain IP
|
||||
nas.home.lan 192.168.30.10
|
||||
pi.home.lan 192.168.30.2
|
||||
grafana.home.lan 192.168.30.3
|
||||
proxmox.home.lan 192.168.30.4
|
||||
|
||||
# From any device on your network:
|
||||
ping nas.home.lan → 192.168.30.10
|
||||
http://grafana.home.lan → your Grafana dashboard
|
||||
|
||||
# For subdomains, add a CNAME:
|
||||
Pi-hole admin → Local DNS → CNAME Records
|
||||
Domain: portainer.home.lan → Target: pi.home.lan
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
# Pi-hole blocking something it should not
|
||||
pihole -q example.com # Check if domain is blocked and which list
|
||||
pihole -w example.com # Whitelist immediately
|
||||
|
||||
# DNS not resolving at all
|
||||
pihole status # Check if pihole-FTL is running
|
||||
dig @192.168.3.2 google.com # Test DNS directly against Pi-hole
|
||||
|
||||
# Restart Pi-hole DNS
|
||||
pihole restartdns
|
||||
|
||||
# Check query logs for a specific device
|
||||
pihole -t # Live tail of all queries
|
||||
# Or filter by client in the web admin Query Log
|
||||
|
||||
# Pi-hole gravity update (refresh blocklists)
|
||||
pihole -g
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
```
|
||||
# BAD: Depending on one Pi-hole without a recovery path
|
||||
# If Pi-hole crashes or the Pi loses power, DNS can stop working
|
||||
# GOOD: Keep a documented router fallback for rollback during setup
|
||||
# BETTER: Run two Pi-hole instances for redundancy; avoid public fallback DNS for strict blocking
|
||||
|
||||
# BAD: Installing Pi-hole without a static IP
|
||||
# If the Pi gets a new DHCP IP, all devices lose DNS
|
||||
# GOOD: Set static IP first, then install Pi-hole
|
||||
|
||||
# BAD: Enabling Pi-hole DHCP without disabling the router's DHCP first
|
||||
# Two DHCP servers on the same network hand out conflicting IPs
|
||||
# GOOD: Disable router DHCP, then enable Pi-hole DHCP
|
||||
|
||||
# BAD: Never updating gravity (blocklists)
|
||||
# New ad and malware domains accumulate — stale lists miss them
|
||||
# GOOD: Schedule weekly gravity update: pihole -g (or enable in Settings → API)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Give the Pi a static IP or DHCP reservation before installing Pi-hole
|
||||
- Use Pi-hole as primary DNS; for redundancy, add a second Pi-hole instead of a
|
||||
public resolver if you need strict blocking
|
||||
- Enable DoH (DNS-over-HTTPS) with cloudflared for encrypted upstream queries
|
||||
- Set `home.lan` as your local domain and create DNS records for all your services
|
||||
- Review the Query Log occasionally — blocked queries show you what devices are doing
|
||||
|
||||
## Related Skills
|
||||
|
||||
- homelab-network-setup
|
||||
- homelab-vlan-segmentation
|
||||
- homelab-wireguard-vpn
|
||||
311
skills/homelab-vlan-segmentation/SKILL.md
Normal file
311
skills/homelab-vlan-segmentation/SKILL.md
Normal file
@ -0,0 +1,311 @@
|
||||
---
|
||||
name: homelab-vlan-segmentation
|
||||
description: Segmenting home networks into VLANs for IoT, guest, trusted, and server traffic using UniFi, pfSense/OPNsense, and MikroTik — including switch trunk config, firewall rules, and wireless SSID mapping.
|
||||
origin: community
|
||||
---
|
||||
|
||||
# Homelab VLAN Segmentation
|
||||
|
||||
How to split a home network into isolated VLANs so IoT devices, guests, and your main
|
||||
PCs cannot talk to each other. The most impactful security upgrade for a home network.
|
||||
|
||||
All firewall rules shown here add isolation between segments — they do not remove
|
||||
existing protections. Apply changes in a maintenance window and verify connectivity
|
||||
between segments after each step before moving on.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Setting up VLANs on a home network for the first time
|
||||
- Isolating IoT devices (smart bulbs, cameras, TVs) from trusted devices
|
||||
- Creating a guest Wi-Fi network that cannot reach home devices
|
||||
- Explaining how VLANs work to someone unfamiliar with the concept
|
||||
- Configuring trunk ports, access ports, and SSID-to-VLAN mapping
|
||||
- Troubleshooting inter-VLAN routing or firewall rule issues on pfSense/OPNsense/UniFi
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
Without VLANs — flat network:
|
||||
All devices on 192.168.1.0/24
|
||||
Smart TV (potential malware) → can reach your NAS, PCs, everything
|
||||
|
||||
With VLANs:
|
||||
VLAN 10 — Trusted 192.168.10.0/24 (PCs, phones, laptops)
|
||||
VLAN 20 — IoT 192.168.20.0/24 (smart TV, bulbs, cameras)
|
||||
VLAN 30 — Servers 192.168.30.0/24 (NAS, Pi, VMs)
|
||||
VLAN 40 — Guest 192.168.40.0/24 (visitor Wi-Fi)
|
||||
VLAN 99 — Management 192.168.99.0/24 (switch/AP web UIs)
|
||||
|
||||
Smart TV → blocked from reaching 192.168.10.0/24 and 192.168.30.0/24
|
||||
Guests → internet only, cannot see any home devices
|
||||
```
|
||||
|
||||
## VLAN Design Template
|
||||
|
||||
```
|
||||
VLAN Name Subnet Gateway Purpose
|
||||
10 trusted 192.168.10.0/24 192.168.10.1 PCs, phones, laptops
|
||||
20 iot 192.168.20.0/24 192.168.20.1 Smart home devices
|
||||
30 servers 192.168.30.0/24 192.168.30.1 NAS, Pi, self-hosted
|
||||
40 guest 192.168.40.0/24 192.168.40.1 Visitor Wi-Fi
|
||||
99 management 192.168.99.0/24 192.168.99.1 Network gear web UIs
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
**Typical homelab with UniFi AP and managed switch:**
|
||||
|
||||
```
|
||||
Scenario: 3-bedroom house, UniFi Dream Machine + UniFi 8-port switch + 2 APs
|
||||
|
||||
VLAN 10 — Trusted 192.168.10.0/24 MacBook, iPhones, iPad
|
||||
VLAN 20 — IoT 192.168.20.0/24 Nest thermostat, Philips Hue, Ring doorbell, smart TVs
|
||||
VLAN 30 — Servers 192.168.30.0/24 Synology NAS (192.168.30.10), Pi-hole (192.168.30.2)
|
||||
VLAN 40 — Guest 192.168.40.0/24 Visitor Wi-Fi — internet only
|
||||
|
||||
SSID → VLAN mapping:
|
||||
"Home" → VLAN 10 (WPA2, strong password, trusted devices only)
|
||||
"IoT" → VLAN 20 (WPA2, separate password, printed on router for setup)
|
||||
"Guest" → VLAN 40 (WPA2, simple password you can share freely)
|
||||
|
||||
Switch port behavior:
|
||||
Port 1 → trunk to router (tagged VLANs 10,20,30,40,99)
|
||||
Port 2 → trunk to APs (tagged VLANs 10,20,40; AP handles per-SSID tagging)
|
||||
Port 3 → access VLAN 30 (NAS — untagged, no VLAN awareness needed)
|
||||
Port 4 → access VLAN 30 (Pi-hole — untagged)
|
||||
Port 5–8 → access VLAN 10 (wired workstations)
|
||||
|
||||
Firewall rules applied (all rules add isolation, none remove existing protections):
|
||||
IoT → Trusted: BLOCK
|
||||
IoT → Servers: BLOCK except 192.168.30.2:53 (Pi-hole DNS allowed)
|
||||
IoT → Internet: ALLOW
|
||||
Guest → Local networks: BLOCK
|
||||
Guest → Internet: ALLOW
|
||||
Trusted → everywhere: ALLOW
|
||||
```
|
||||
|
||||
## UniFi Configuration
|
||||
|
||||
### Create Networks in UniFi Controller
|
||||
|
||||
```
|
||||
Settings → Networks → Create New Network
|
||||
|
||||
For each VLAN:
|
||||
Name: IoT
|
||||
Purpose: Corporate (gives DHCP + routing)
|
||||
VLAN ID: 20
|
||||
Network: 192.168.20.0/24
|
||||
Gateway IP: 192.168.20.1
|
||||
DHCP: Enable
|
||||
DHCP Range: 192.168.20.100 – 192.168.20.254
|
||||
```
|
||||
|
||||
### Map SSIDs to VLANs (UniFi)
|
||||
|
||||
```
|
||||
Settings → WiFi → Create New WiFi
|
||||
|
||||
Name: IoT-Network
|
||||
Password: <separate password>
|
||||
Network: IoT ← select your VLAN here
|
||||
# All devices connecting to this SSID land in VLAN 20
|
||||
|
||||
Name: Guest
|
||||
Password: <guest password>
|
||||
Network: Guest
|
||||
Guest Policy: Enable ← isolates guests from each other too
|
||||
```
|
||||
|
||||
### UniFi Firewall Rules (Traffic Rules)
|
||||
|
||||
```
|
||||
Settings → Traffic & Security → Traffic Rules
|
||||
|
||||
# Block IoT from reaching Trusted VLAN
|
||||
Action: Block
|
||||
Category: Local Network
|
||||
Source: IoT (192.168.20.0/24)
|
||||
Destination: Trusted (192.168.10.0/24)
|
||||
|
||||
# Allow IoT to reach internet only
|
||||
Action: Allow
|
||||
Source: IoT
|
||||
Destination: Internet
|
||||
|
||||
# Block Guest from all local networks
|
||||
Action: Block
|
||||
Source: Guest
|
||||
Destination: Local Networks
|
||||
```
|
||||
|
||||
## pfSense / OPNsense Configuration
|
||||
|
||||
### Create VLANs
|
||||
|
||||
```
|
||||
Interfaces → Assignments → VLANs → Add
|
||||
|
||||
Parent Interface: em1 (your LAN NIC)
|
||||
VLAN Tag: 20
|
||||
Description: IoT
|
||||
|
||||
# Repeat for each VLAN, then assign each VLAN to an interface:
|
||||
Interfaces → Assignments → Add
|
||||
Select the VLAN you created → click Add
|
||||
Enable the interface, set IP to gateway address (192.168.20.1/24)
|
||||
```
|
||||
|
||||
### DHCP for Each VLAN
|
||||
|
||||
```
|
||||
Services → DHCP Server → Select your VLAN interface
|
||||
|
||||
Enable DHCP
|
||||
Range: 192.168.20.100 to 192.168.20.254
|
||||
DNS Servers: 192.168.30.2 ← Pi-hole IP if you have one
|
||||
```
|
||||
|
||||
### Firewall Rules (pfSense/OPNsense)
|
||||
|
||||
```
|
||||
# Rules are processed top-to-bottom, first match wins.
|
||||
|
||||
# On the IoT interface (VLAN 20):
|
||||
Rule 1: Allow IoT → Pi-hole DNS ← MUST come before the RFC1918 block rule
|
||||
Protocol: UDP/TCP
|
||||
Source: IoT net
|
||||
Destination: 192.168.30.2 port 53
|
||||
Action: Allow
|
||||
|
||||
Rule 2: Block IoT → RFC1918 (all private IP ranges)
|
||||
Protocol: any
|
||||
Source: IoT net
|
||||
Destination: RFC1918 (192.168.0.0/16, 10.0.0.0/8, 172.16.0.0/12)
|
||||
Action: Block
|
||||
|
||||
Rule 3: Allow IoT → internet
|
||||
Protocol: any
|
||||
Source: IoT net
|
||||
Destination: any
|
||||
Action: Allow
|
||||
|
||||
# On the Trusted interface (VLAN 10):
|
||||
Allow all (trusted devices can reach everything)
|
||||
Source: Trusted net
|
||||
Destination: any
|
||||
Action: Allow
|
||||
|
||||
# Additional exceptions for IoT devices that need specific local services:
|
||||
Insert before Rule 2 (the RFC1918 block):
|
||||
Protocol: TCP
|
||||
Source: IoT net
|
||||
Destination: 192.168.30.x port 8123 ← Home Assistant
|
||||
Action: Allow
|
||||
```
|
||||
|
||||
## MikroTik Configuration
|
||||
|
||||
```
|
||||
# Step 1: Create a bridge with VLAN filtering enabled
|
||||
/interface bridge
|
||||
add name=bridge vlan-filtering=yes
|
||||
|
||||
# Step 2: Add physical ports to the bridge
|
||||
# Trunk port to router/uplink (tagged for all VLANs)
|
||||
/interface bridge port
|
||||
add bridge=bridge interface=ether1 frame-types=admit-only-vlan-tagged
|
||||
|
||||
# Access port for trusted devices (untagged VLAN 10)
|
||||
/interface bridge port
|
||||
add bridge=bridge interface=ether2 pvid=10 frame-types=admit-only-untagged-and-priority-tagged
|
||||
|
||||
# Access port for IoT devices (untagged VLAN 20)
|
||||
/interface bridge port
|
||||
add bridge=bridge interface=ether3 pvid=20 frame-types=admit-only-untagged-and-priority-tagged
|
||||
|
||||
# Step 3: Define which VLANs are allowed on which ports
|
||||
/interface bridge vlan
|
||||
add bridge=bridge tagged=ether1 untagged=ether2 vlan-ids=10
|
||||
add bridge=bridge tagged=ether1 untagged=ether3 vlan-ids=20
|
||||
|
||||
# Step 4: Create VLAN interfaces on the bridge (gateway IPs)
|
||||
/interface vlan
|
||||
add interface=bridge name=vlan10 vlan-id=10
|
||||
add interface=bridge name=vlan20 vlan-id=20
|
||||
|
||||
# Step 5: Assign gateway IPs
|
||||
/ip address
|
||||
add interface=vlan10 address=192.168.10.1/24
|
||||
add interface=vlan20 address=192.168.20.1/24
|
||||
|
||||
# Step 6: DHCP pools and servers
|
||||
/ip pool
|
||||
add name=pool-trusted ranges=192.168.10.100-192.168.10.254
|
||||
add name=pool-iot ranges=192.168.20.100-192.168.20.254
|
||||
|
||||
/ip dhcp-server
|
||||
add interface=vlan10 address-pool=pool-trusted name=dhcp-trusted
|
||||
add interface=vlan20 address-pool=pool-iot name=dhcp-iot
|
||||
|
||||
/ip dhcp-server network
|
||||
add address=192.168.10.0/24 gateway=192.168.10.1
|
||||
add address=192.168.20.0/24 gateway=192.168.20.1
|
||||
|
||||
# Step 7: Firewall — block IoT from reaching trusted VLAN
|
||||
/ip firewall filter
|
||||
add chain=forward src-address=192.168.20.0/24 dst-address=192.168.10.0/24 \
|
||||
action=drop comment="Block IoT to Trusted"
|
||||
```
|
||||
|
||||
## Switch Trunk vs Access Ports
|
||||
|
||||
```
|
||||
# Trunk port: carries multiple VLANs (tagged) — connects switch-to-switch, switch-to-router, switch-to-AP
|
||||
# Access port: carries one VLAN (untagged) — connects to end devices (PC, camera, NAS)
|
||||
|
||||
# A managed switch port connected to your router should be a trunk:
|
||||
Allowed VLANs: 10, 20, 30, 40, 99
|
||||
|
||||
# A port connecting to a PC should be an access port:
|
||||
VLAN: 10 (trusted)
|
||||
No tagging — the PC does not know or care about VLANs
|
||||
|
||||
# A port connecting to an AP must be a trunk:
|
||||
The AP tags traffic from each SSID with the right VLAN ID
|
||||
Allowed VLANs: 10, 20, 40 (whichever SSIDs the AP serves)
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
```
|
||||
# BAD: Creating VLANs without adding firewall rules
|
||||
# VLANs without firewall rules do not provide security — inter-VLAN routing is open by default
|
||||
# GOOD: Add explicit block rules immediately after creating VLANs
|
||||
|
||||
# BAD: Putting the Pi-hole in the IoT VLAN
|
||||
# IoT devices can reach it but trusted devices cannot (without extra rules)
|
||||
# GOOD: Pi-hole in the Servers VLAN with a rule allowing all VLANs to reach port 53
|
||||
|
||||
# BAD: Native VLAN equals management VLAN
|
||||
# Untagged traffic landing in your management VLAN enables VLAN hopping attacks
|
||||
# GOOD: Use a dedicated unused VLAN as native (e.g. VLAN 999), keep management traffic tagged
|
||||
|
||||
# BAD: Same Wi-Fi password for IoT SSID and trusted SSID
|
||||
# Anyone who learns the password can connect IoT devices to the wrong segment
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Start with 4 VLANs: Trusted, IoT, Servers, Guest — add more as needed
|
||||
- Put Pi-hole in the Servers VLAN (192.168.30.x)
|
||||
- Add a firewall rule allowing DNS (port 53) from all VLANs to the Pi-hole IP — before any RFC1918 block rule
|
||||
- Test isolation after every rule change: from the IoT VLAN, try to ping a trusted device — it should fail
|
||||
- Use a management VLAN for switch and AP web UIs and restrict access to the Trusted VLAN only
|
||||
- Document your VLAN design in a table (VLAN ID, name, subnet, purpose)
|
||||
|
||||
## Related Skills
|
||||
|
||||
- homelab-network-setup
|
||||
- homelab-pihole-dns
|
||||
- homelab-wireguard-vpn
|
||||
305
skills/homelab-wireguard-vpn/SKILL.md
Normal file
305
skills/homelab-wireguard-vpn/SKILL.md
Normal file
@ -0,0 +1,305 @@
|
||||
---
|
||||
name: homelab-wireguard-vpn
|
||||
description: WireGuard VPN server setup, peer configuration, key generation, split tunneling vs full tunnel routing, and remote access to a home network from mobile and laptop clients.
|
||||
origin: community
|
||||
---
|
||||
|
||||
# Homelab WireGuard VPN
|
||||
|
||||
WireGuard is a fast, modern VPN protocol. It is the right choice for remote access to a
|
||||
home network — simpler to configure than OpenVPN and faster than most alternatives.
|
||||
|
||||
All configuration examples show common setups. Review each command — especially the
|
||||
iptables forwarding rules and key file permissions — before applying them to your
|
||||
system, and make changes in a maintenance window.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Setting up WireGuard server on a Raspberry Pi, Linux host, pfSense, or router
|
||||
- Generating WireGuard keypairs and writing peer config files
|
||||
- Configuring remote access from a phone or laptop to a home network
|
||||
- Explaining split tunneling (route only home traffic) vs full tunnel (route all traffic)
|
||||
- Troubleshooting WireGuard connections that will not come up
|
||||
- Automating peer configuration generation for multiple clients
|
||||
|
||||
## How WireGuard Works
|
||||
|
||||
```
|
||||
Your phone (WireGuard client)
|
||||
│
|
||||
│ Encrypted UDP tunnel (port 51820)
|
||||
│
|
||||
Your home router (WireGuard server — needs a public IP or DDNS)
|
||||
│
|
||||
Your home network (192.168.1.0/24, NAS, Pi, etc.)
|
||||
|
||||
Every device has a keypair (public + private key).
|
||||
The server knows each client's public key.
|
||||
The client knows the server's public key + endpoint (IP:port).
|
||||
Traffic is encrypted end-to-end with no central server or certificate authority.
|
||||
```
|
||||
|
||||
## Server Setup (Linux)
|
||||
|
||||
```bash
|
||||
# Install WireGuard
|
||||
sudo apt update && sudo apt install wireguard -y
|
||||
|
||||
# Generate server keypair — create files with private permissions from the start
|
||||
sudo mkdir -p /etc/wireguard
|
||||
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server_private.key'
|
||||
sudo sh -c 'wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key'
|
||||
|
||||
# Write server config — substitute the actual private key value
|
||||
# Do not store private keys in version control or share them
|
||||
sudo tee /etc/wireguard/wg0.conf << 'EOF'
|
||||
[Interface]
|
||||
Address = 10.8.0.1/24 # VPN subnet — server gets .1
|
||||
ListenPort = 51820
|
||||
PrivateKey = <paste_server_private_key_here>
|
||||
|
||||
# Scoped forwarding rules: allow VPN traffic in/out, not a blanket FORWARD ACCEPT
|
||||
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
|
||||
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
|
||||
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
||||
|
||||
[Peer]
|
||||
# Phone — replace with the actual phone public key
|
||||
PublicKey = <phone_public_key>
|
||||
AllowedIPs = 10.8.0.2/32
|
||||
|
||||
[Peer]
|
||||
# Laptop — replace with the actual laptop public key
|
||||
PublicKey = <laptop_public_key>
|
||||
AllowedIPs = 10.8.0.3/32
|
||||
EOF
|
||||
sudo chmod 600 /etc/wireguard/wg0.conf
|
||||
|
||||
# Replace eth0 with your actual outbound interface name
|
||||
# Check with: ip route show default
|
||||
|
||||
# Enable IP forwarding (required for routing traffic through the server)
|
||||
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
|
||||
sudo sysctl --system
|
||||
|
||||
# Start WireGuard and enable on boot
|
||||
sudo wg-quick up wg0
|
||||
sudo systemctl enable wg-quick@wg0
|
||||
```
|
||||
|
||||
## Client Configuration
|
||||
|
||||
```bash
|
||||
# Generate a unique keypair for each client device
|
||||
# Run on the client, or on the server and transfer the private key securely — never in plaintext
|
||||
umask 077
|
||||
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
|
||||
|
||||
# Client config file (phone_wg0.conf):
|
||||
[Interface]
|
||||
PrivateKey = <phone_private_key>
|
||||
Address = 10.8.0.2/32
|
||||
DNS = 192.168.1.2 # Optional: use Pi-hole for DNS over the tunnel
|
||||
|
||||
[Peer]
|
||||
PublicKey = <server_public_key>
|
||||
Endpoint = your-home-ip.ddns.net:51820 # Your public IP or DDNS hostname
|
||||
AllowedIPs = 192.168.1.0/24 # Split tunnel: only home network traffic
|
||||
# AllowedIPs = 0.0.0.0/0, ::/0 # Full tunnel: all traffic through VPN
|
||||
|
||||
PersistentKeepalive = 25 # Keep NAT hole open (required for mobile clients)
|
||||
```
|
||||
|
||||
## Split Tunnel vs Full Tunnel
|
||||
|
||||
```
|
||||
# Split tunnel: AllowedIPs = 192.168.1.0/24
|
||||
Only traffic destined for your home network goes through the VPN.
|
||||
Internet traffic (YouTube, Spotify) goes directly — better performance on mobile.
|
||||
Best for: "I just want to reach my NAS and Pi from anywhere."
|
||||
|
||||
# Full tunnel: AllowedIPs = 0.0.0.0/0, ::/0
|
||||
ALL traffic goes through your home internet connection.
|
||||
Useful for: piggybacking home DNS/Pi-hole ad blocking.
|
||||
Downside: home upload speed becomes your bottleneck everywhere.
|
||||
|
||||
# Multi-subnet split tunnel (most common homelab use case):
|
||||
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24, 10.8.0.0/24
|
||||
Routes all your VLANs through the tunnel; internet stays direct.
|
||||
```
|
||||
|
||||
## Key Generation and Peer Management
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
|
||||
def generate_keypair() -> tuple[str, str]:
|
||||
"""Generate a WireGuard keypair. Returns (private_key, public_key)."""
|
||||
private = subprocess.check_output(["wg", "genkey"]).decode().strip()
|
||||
public = subprocess.run(
|
||||
["wg", "pubkey"], input=private.encode(), capture_output=True
|
||||
).stdout.decode().strip()
|
||||
return private, public
|
||||
|
||||
def generate_preshared_key() -> str:
|
||||
return subprocess.check_output(["wg", "genpsk"]).decode().strip()
|
||||
|
||||
def build_client_config(
|
||||
client_private_key: str,
|
||||
client_vpn_ip: str, # e.g. "10.8.0.3"
|
||||
server_public_key: str,
|
||||
server_endpoint: str, # e.g. "home.example.com:51820"
|
||||
allowed_ips: str = "192.168.1.0/24",
|
||||
dns: str = "",
|
||||
) -> str:
|
||||
dns_line = f"DNS = {dns}\n" if dns else ""
|
||||
return f"""[Interface]
|
||||
PrivateKey = {client_private_key}
|
||||
Address = {client_vpn_ip}/32
|
||||
{dns_line}
|
||||
[Peer]
|
||||
PublicKey = {server_public_key}
|
||||
Endpoint = {server_endpoint}
|
||||
AllowedIPs = {allowed_ips}
|
||||
PersistentKeepalive = 25
|
||||
"""
|
||||
|
||||
def build_server_peer_block(
|
||||
client_public_key: str,
|
||||
client_vpn_ip: str,
|
||||
comment: str = "",
|
||||
) -> str:
|
||||
comment_line = f"# {comment}\n" if comment else ""
|
||||
return f"""
|
||||
{comment_line}[Peer]
|
||||
PublicKey = {client_public_key}
|
||||
AllowedIPs = {client_vpn_ip}/32
|
||||
"""
|
||||
```
|
||||
|
||||
Keep private keys out of source control. If you use this script, write key material
|
||||
to files with mode 600 and never log or print it.
|
||||
|
||||
## pfSense / OPNsense WireGuard
|
||||
|
||||
```
|
||||
# pfSense: VPN → WireGuard → Add Tunnel
|
||||
Interface Keys: Generate (creates keypair automatically)
|
||||
Listen Port: 51820
|
||||
Interface Address: 10.8.0.1/24
|
||||
|
||||
# Add Peer (one per client):
|
||||
Public Key: <client public key>
|
||||
Allowed IPs: 10.8.0.2/32
|
||||
|
||||
# Assign the WireGuard interface:
|
||||
Interfaces → Assignments → Add (select wg0)
|
||||
Enable interface, no IP needed (it is set in the tunnel config)
|
||||
|
||||
# Firewall rules:
|
||||
WAN → Allow UDP port 51820 inbound (so clients can reach the server)
|
||||
WireGuard interface → Allow traffic to LAN networks you want reachable
|
||||
```
|
||||
|
||||
## DDNS (Dynamic DNS) for Home Servers
|
||||
|
||||
Most home internet connections have a dynamic IP. Use DDNS so your VPN endpoint
|
||||
stays reachable after an IP change.
|
||||
|
||||
```bash
|
||||
# Option 1: Cloudflare DDNS — store credentials in a secrets file, not inline
|
||||
# docker-compose entry using an env file:
|
||||
ddns-updater:
|
||||
image: qmcgaw/ddns-updater
|
||||
env_file: ./ddns.env # store zone_id and token here, not in compose
|
||||
restart: unless-stopped
|
||||
|
||||
# ddns.env (chmod 600, not committed to git):
|
||||
# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
|
||||
# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
|
||||
|
||||
# Option 2: DuckDNS (free, simple)
|
||||
Sign up at duckdns.org → get a token and subdomain (myhome.duckdns.org)
|
||||
Store token in /etc/ddns.env (mode 600), then use a small root-owned script:
|
||||
|
||||
# /usr/local/bin/update-duckdns
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
. /etc/ddns.env
|
||||
curl --fail --silent --show-error --max-time 10 \
|
||||
--get "https://www.duckdns.org/update" \
|
||||
--data-urlencode "domains=myhome" \
|
||||
--data-urlencode "token=${DUCKDNS_TOKEN}" \
|
||||
--data-urlencode "ip="
|
||||
|
||||
# Cron job:
|
||||
*/5 * * * * /usr/local/bin/update-duckdns >/dev/null 2>&1
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
# Check WireGuard status and last handshake
|
||||
sudo wg show
|
||||
|
||||
# If "latest handshake" is never or very old, the tunnel is not connected.
|
||||
# Check:
|
||||
# 1. Is UDP port 51820 open on the router/firewall?
|
||||
sudo ufw status # or check pfSense/UniFi firewall rules
|
||||
|
||||
# 2. Is the server public key in the client config correct?
|
||||
sudo wg show wg0 public-key # Compare to what is in the client config
|
||||
|
||||
# 3. Is IP forwarding enabled on the server?
|
||||
cat /proc/sys/net/ipv4/ip_forward # Should be 1
|
||||
|
||||
# 4. Does the client AllowedIPs cover the IP you are trying to reach?
|
||||
# If AllowedIPs = 192.168.1.0/24 and you are trying to reach 192.168.3.5, it will not route.
|
||||
|
||||
# Check kernel logs for WireGuard errors
|
||||
dmesg | grep wireguard
|
||||
|
||||
# Restart WireGuard
|
||||
sudo wg-quick down wg0 && sudo wg-quick up wg0
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
```
|
||||
# BAD: Storing private keys in version control or sharing them
|
||||
# Private keys are equivalent to passwords — never commit them to git
|
||||
|
||||
# BAD: Using AllowedIPs = 0.0.0.0/0 on mobile without considering the impact
|
||||
# Full tunnel routes all mobile traffic through your home upload — usually slow
|
||||
|
||||
# BAD: Not setting PersistentKeepalive on mobile clients
|
||||
# Mobile clients behind NAT drop idle tunnels without it
|
||||
|
||||
# BAD: Opening port 51820 in the firewall but forgetting IP forwarding on the server
|
||||
# Tunnel connects but no traffic routes — confusing to debug
|
||||
|
||||
# BAD: Sharing a keypair across multiple client devices
|
||||
# Each device must have its own unique keypair — shared keys break the security model
|
||||
|
||||
# BAD: Using a broad "FORWARD ACCEPT" iptables rule
|
||||
# Scope forwarding rules to the wg0 interface and direction only
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Generate a unique keypair per client device — never reuse keys
|
||||
- Use split tunneling (`AllowedIPs = <home subnets>`) for mobile
|
||||
- Set `PersistentKeepalive = 25` on all mobile clients
|
||||
- Use DDNS if your ISP assigns a dynamic IP; store credentials in env files, not inline
|
||||
- Use scoped iptables forwarding rules (inbound on wg0 only) rather than a blanket FORWARD ACCEPT
|
||||
- Add Pi-hole's IP as `DNS =` in client configs to get ad blocking over the VPN
|
||||
- Rotate the server keypair periodically and update all client configs
|
||||
|
||||
## Related Skills
|
||||
|
||||
- homelab-network-setup
|
||||
- homelab-vlan-segmentation
|
||||
- homelab-pihole-dns
|
||||
Loading…
x
Reference in New Issue
Block a user