[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M70G7C7CAT56QNP5K7KS84FN":3,"skill-same:01M70G7C7CAT56QNP5K7KS84FN":67},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":6,"ownerHtmlUrl":50,"ownerBlogUrl":6,"ownerDescription":51,"updatedAt":52,"install":53,"resources":66},"01M70G7C7CAT56QNP5K7KS84FN","homelab-wireguard-vpn",null,"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. Use when setting up WireGuard for remote access to a home network, or deciding between split and full tunnel routing.","mdnaimul22\u002Fhuman-skills","mdnaimul22","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F64520358?v=4","01M70FH95MH2H796Q01TYSFZ03","Naimul","01M70FH96798CQR4YHA13F17GA",3,"MIT","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Fhomelab-wireguard-vpn\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"wireguard","homelab","homelab wireguard vpn","vpn","routing","home","peer","split","use","for","full","tunnel","network","mobile","server","when","access","remote","up","community","2026-11-10 10:12:30",false,"---\nname: homelab-wireguard-vpn\ndescription: 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. Use when setting up WireGuard for remote access to a home network, or deciding between split and full tunnel routing.\nmetadata:\n  origin: community\n---\n\n# Homelab WireGuard VPN\n\nWireGuard is a fast, modern VPN protocol. It is the right choice for remote access to a\nhome network — simpler to configure than OpenVPN and faster than most alternatives.\n\nAll configuration examples show common setups. Review each command — especially the\niptables forwarding rules and key file permissions — before applying them to your\nsystem, and make changes in a maintenance window.\n\n## When to Use\n\n- Setting up WireGuard server on a Raspberry Pi, Linux host, pfSense, or router\n- Generating WireGuard keypairs and writing peer config files\n- Configuring remote access from a phone or laptop to a home network\n- Explaining split tunneling (route only home traffic) vs full tunnel (route all traffic)\n- Troubleshooting WireGuard connections that will not come up\n- Automating peer configuration generation for multiple clients\n\n## How WireGuard Works\n\n```\nYour phone (WireGuard client)\n    │\n    │  Encrypted UDP tunnel (port 51820)\n    │\nYour home router (WireGuard server — needs a public IP or DDNS)\n    │\n    Your home network (192.168.1.0\u002F24, NAS, Pi, etc.)\n\nEvery device has a keypair (public + private key).\nThe server knows each client's public key.\nThe client knows the server's public key + endpoint (IP:port).\nTraffic is encrypted end-to-end with no central server or certificate authority.\n```\n\n## Server Setup (Linux)\n\n```bash\n# Install WireGuard\nsudo apt update && sudo apt install wireguard -y\n\n# Generate server keypair — create files with private permissions from the start\nsudo mkdir -p \u002Fetc\u002Fwireguard\nsudo sh -c 'umask 077; wg genkey > \u002Fetc\u002Fwireguard\u002Fserver_private.key'\nsudo sh -c 'wg pubkey \u003C \u002Fetc\u002Fwireguard\u002Fserver_private.key > \u002Fetc\u002Fwireguard\u002Fserver_public.key'\n\n# Write server config — substitute the actual private key value\n# Do not store private keys in version control or share them\nsudo tee \u002Fetc\u002Fwireguard\u002Fwg0.conf \u003C\u003C 'EOF'\n[Interface]\nAddress = 10.8.0.1\u002F24              # VPN subnet — server gets .1\nListenPort = 51820\nPrivateKey = \u003Cpaste_server_private_key_here>\n\n# Scoped forwarding rules: allow VPN traffic in\u002Fout, not a blanket FORWARD ACCEPT\nPostUp   = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT\nPostUp   = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT\nPostUp   = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\nPostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT\nPostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT\nPostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\n\n[Peer]\n# Phone — replace with the actual phone public key\nPublicKey = \u003Cphone_public_key>\nAllowedIPs = 10.8.0.2\u002F32\n\n[Peer]\n# Laptop — replace with the actual laptop public key\nPublicKey = \u003Claptop_public_key>\nAllowedIPs = 10.8.0.3\u002F32\nEOF\nsudo chmod 600 \u002Fetc\u002Fwireguard\u002Fwg0.conf\n\n# Replace eth0 with your actual outbound interface name\n# Check with: ip route show default\n\n# Enable IP forwarding (required for routing traffic through the server)\necho \"net.ipv4.ip_forward=1\" | sudo tee \u002Fetc\u002Fsysctl.d\u002F99-wireguard.conf\nsudo sysctl --system\n\n# Start WireGuard and enable on boot\nsudo wg-quick up wg0\nsudo systemctl enable wg-quick@wg0\n```\n\n## Client Configuration\n\n```bash\n# Generate a unique keypair for each client device\n# Run on the client, or on the server and transfer the private key securely — never in plaintext\numask 077\nwg genkey | tee phone_private.key | wg pubkey > phone_public.key\n\n# Client config file (phone_wg0.conf):\n[Interface]\nPrivateKey = \u003Cphone_private_key>\nAddress = 10.8.0.2\u002F32\nDNS = 192.168.1.2                  # Optional: use Pi-hole for DNS over the tunnel\n\n[Peer]\nPublicKey = \u003Cserver_public_key>\nEndpoint = your-home-ip.ddns.net:51820  # Your public IP or DDNS hostname\nAllowedIPs = 192.168.1.0\u002F24            # Split tunnel: only home network traffic\n# AllowedIPs = 0.0.0.0\u002F0, ::\u002F0        # Full tunnel: all traffic through VPN\n\nPersistentKeepalive = 25              # Keep NAT hole open (required for mobile clients)\n```\n\n## Split Tunnel vs Full Tunnel\n\n```\n# Split tunnel: AllowedIPs = 192.168.1.0\u002F24\n  Only traffic destined for your home network goes through the VPN.\n  Internet traffic (YouTube, Spotify) goes directly — better performance on mobile.\n  Best for: \"I just want to reach my NAS and Pi from anywhere.\"\n\n# Full tunnel: AllowedIPs = 0.0.0.0\u002F0, ::\u002F0\n  ALL traffic goes through your home internet connection.\n  Useful for: piggybacking home DNS\u002FPi-hole ad blocking.\n  Downside: home upload speed becomes your bottleneck everywhere.\n\n# Multi-subnet split tunnel (most common homelab use case):\n  AllowedIPs = 192.168.10.0\u002F24, 192.168.20.0\u002F24, 192.168.30.0\u002F24, 10.8.0.0\u002F24\n  Routes all your VLANs through the tunnel; internet stays direct.\n```\n\n## Key Generation and Peer Management\n\n```python\nimport subprocess\n\ndef generate_keypair() -> tuple[str, str]:\n    \"\"\"Generate a WireGuard keypair. Returns (private_key, public_key).\"\"\"\n    private = subprocess.check_output([\"wg\", \"genkey\"]).decode().strip()\n    public = subprocess.run(\n        [\"wg\", \"pubkey\"], input=private.encode(), capture_output=True\n    ).stdout.decode().strip()\n    return private, public\n\ndef generate_preshared_key() -> str:\n    return subprocess.check_output([\"wg\", \"genpsk\"]).decode().strip()\n\ndef build_client_config(\n    client_private_key: str,\n    client_vpn_ip: str,       # e.g. \"10.8.0.3\"\n    server_public_key: str,\n    server_endpoint: str,     # e.g. \"home.example.com:51820\"\n    allowed_ips: str = \"192.168.1.0\u002F24\",\n    dns: str = \"\",\n) -> str:\n    dns_line = f\"DNS = {dns}\\n\" if dns else \"\"\n    return f\"\"\"[Interface]\nPrivateKey = {client_private_key}\nAddress = {client_vpn_ip}\u002F32\n{dns_line}\n[Peer]\nPublicKey = {server_public_key}\nEndpoint = {server_endpoint}\nAllowedIPs = {allowed_ips}\nPersistentKeepalive = 25\n\"\"\"\n\ndef build_server_peer_block(\n    client_public_key: str,\n    client_vpn_ip: str,\n    comment: str = \"\",\n) -> str:\n    comment_line = f\"# {comment}\\n\" if comment else \"\"\n    return f\"\"\"\n{comment_line}[Peer]\nPublicKey = {client_public_key}\nAllowedIPs = {client_vpn_ip}\u002F32\n\"\"\"\n```\n\nKeep private keys out of source control. If you use this script, write key material\nto files with mode 600 and never log or print it.\n\n## pfSense \u002F OPNsense WireGuard\n\n```\n# pfSense: VPN → WireGuard → Add Tunnel\n  Interface Keys: Generate (creates keypair automatically)\n  Listen Port: 51820\n  Interface Address: 10.8.0.1\u002F24\n\n# Add Peer (one per client):\n  Public Key: \u003Cclient public key>\n  Allowed IPs: 10.8.0.2\u002F32\n\n# Assign the WireGuard interface:\n  Interfaces → Assignments → Add (select wg0)\n  Enable interface, no IP needed (it is set in the tunnel config)\n\n# Firewall rules:\n  WAN → Allow UDP port 51820 inbound (so clients can reach the server)\n  WireGuard interface → Allow traffic to LAN networks you want reachable\n```\n\n## DDNS (Dynamic DNS) for Home Servers\n\nMost home internet connections have a dynamic IP. Use DDNS so your VPN endpoint\nstays reachable after an IP change.\n\n```bash\n# Option 1: Cloudflare DDNS — store credentials in a secrets file, not inline\n# docker-compose entry using an env file:\n  ddns-updater:\n    image: qmcgaw\u002Fddns-updater\n    env_file: .\u002Fddns.env   # store zone_id and token here, not in compose\n    restart: unless-stopped\n\n# ddns.env (chmod 600, not committed to git):\n# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id\n# SETTINGS_CLOUDFLARE_TOKEN=your_api_token\n\n# Option 2: DuckDNS (free, simple)\n  Sign up at duckdns.org → get a token and subdomain (myhome.duckdns.org)\n  Store token in \u002Fetc\u002Fddns.env (mode 600), then use a small root-owned script:\n\n  # \u002Fusr\u002Flocal\u002Fbin\u002Fupdate-duckdns\n  #!\u002Fbin\u002Fsh\n  set -eu\n  . \u002Fetc\u002Fddns.env\n  curl --fail --silent --show-error --max-time 10 \\\n    --get \"https:\u002F\u002Fwww.duckdns.org\u002Fupdate\" \\\n    --data-urlencode \"domains=myhome\" \\\n    --data-urlencode \"token=${DUCKDNS_TOKEN}\" \\\n    --data-urlencode \"ip=\"\n\n  # Cron job:\n  *\u002F5 * * * * \u002Fusr\u002Flocal\u002Fbin\u002Fupdate-duckdns >\u002Fdev\u002Fnull 2>&1\n```\n\n## Troubleshooting\n\n```bash\n# Check WireGuard status and last handshake\nsudo wg show\n\n# If \"latest handshake\" is never or very old, the tunnel is not connected.\n# Check:\n# 1. Is UDP port 51820 open on the router\u002Ffirewall?\nsudo ufw status  # or check pfSense\u002FUniFi firewall rules\n\n# 2. Is the server public key in the client config correct?\nsudo wg show wg0 public-key   # Compare to what is in the client config\n\n# 3. Is IP forwarding enabled on the server?\ncat \u002Fproc\u002Fsys\u002Fnet\u002Fipv4\u002Fip_forward  # Should be 1\n\n# 4. Does the client AllowedIPs cover the IP you are trying to reach?\n# If AllowedIPs = 192.168.1.0\u002F24 and you are trying to reach 192.168.3.5, it will not route.\n\n# Check kernel logs for WireGuard errors\ndmesg | grep wireguard\n\n# Restart WireGuard\nsudo wg-quick down wg0 && sudo wg-quick up wg0\n```\n\n## Anti-Patterns\n\n```\n# BAD: Storing private keys in version control or sharing them\n# Private keys are equivalent to passwords — never commit them to git\n\n# BAD: Using AllowedIPs = 0.0.0.0\u002F0 on mobile without considering the impact\n# Full tunnel routes all mobile traffic through your home upload — usually slow\n\n# BAD: Not setting PersistentKeepalive on mobile clients\n# Mobile clients behind NAT drop idle tunnels without it\n\n# BAD: Opening port 51820 in the firewall but forgetting IP forwarding on the server\n# Tunnel connects but no traffic routes — confusing to debug\n\n# BAD: Sharing a keypair across multiple client devices\n# Each device must have its own unique keypair — shared keys break the security model\n\n# BAD: Using a broad \"FORWARD ACCEPT\" iptables rule\n# Scope forwarding rules to the wg0 interface and direction only\n```\n\n## Best Practices\n\n- Generate a unique keypair per client device — never reuse keys\n- Use split tunneling (`AllowedIPs = \u003Chome subnets>`) for mobile\n- Set `PersistentKeepalive = 25` on all mobile clients\n- Use DDNS if your ISP assigns a dynamic IP; store credentials in env files, not inline\n- Use scoped iptables forwarding rules (inbound on wg0 only) rather than a blanket FORWARD ACCEPT\n- Add Pi-hole's IP as `DNS =` in client configs to get ad blocking over the VPN\n- Rotate the server keypair periodically and update all client configs\n\n## Related Skills\n\n- homelab-network-setup\n- homelab-vlan-segmentation\n- homelab-pihole-dns\n","# Homelab WireGuard VPN\n\nWireGuard is a fast, modern VPN protocol. It is the right choice for remote access to a\nhome network — simpler to configure than OpenVPN and faster than most alternatives.\n\nAll configuration examples show common setups. Review each command — especially the\niptables forwarding rules and key file permissions — before applying them to your\nsystem, and make changes in a maintenance window.\n\n## When to Use\n\n- Setting up WireGuard server on a Raspberry Pi, Linux host, pfSense, or router\n- Generating WireGuard keypairs and writing peer config files\n- Configuring remote access from a phone or laptop to a home network\n- Explaining split tunneling (route only home traffic) vs full tunnel (route all traffic)\n- Troubleshooting WireGuard connections that will not come up\n- Automating peer configuration generation for multiple clients\n\n## How WireGuard Works\n\n```\nYour phone (WireGuard client)\n    │\n    │  Encrypted UDP tunnel (port 51820)\n    │\nYour home router (WireGuard server — needs a public IP or DDNS)\n    │\n    Your home network (192.168.1.0\u002F24, NAS, Pi, etc.)\n\nEvery device has a keypair (public + private key).\nThe server knows each client's public key.\nThe client knows the server's public key + endpoint (IP:port).\nTraffic is encrypted end-to-end with no central server or certificate authority.\n```\n\n## Server Setup (Linux)\n\n```bash\n# Install WireGuard\nsudo apt update && sudo apt install wireguard -y\n\n# Generate server keypair — create files with private permissions from the start\nsudo mkdir -p \u002Fetc\u002Fwireguard\nsudo sh -c 'umask 077; wg genkey > \u002Fetc\u002Fwireguard\u002Fserver_private.key'\nsudo sh -c 'wg pubkey \u003C \u002Fetc\u002Fwireguard\u002Fserver_private.key > \u002Fetc\u002Fwireguard\u002Fserver_public.key'\n\n# Write server config — substitute the actual private key value\n# Do not store private keys in version control or share them\nsudo tee \u002Fetc\u002Fwireguard\u002Fwg0.conf \u003C\u003C 'EOF'\n[Interface]\nAddress = 10.8.0.1\u002F24              # VPN subnet — server gets .1\nListenPort = 51820\nPrivateKey = \u003Cpaste_server_private_key_here>\n\n# Scoped forwarding rules: allow VPN traffic in\u002Fout, not a blanket FORWARD ACCEPT\nPostUp   = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT\nPostUp   = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT\nPostUp   = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\nPostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT\nPostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT\nPostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\n\n[Peer]\n# Phone — replace with the actual phone public key\nPublicKey = \u003Cphone_public_key>\nAllowedIPs = 10.8.0.2\u002F32\n\n[Peer]\n# Laptop — replace with the actual laptop public key\nPublicKey = \u003Claptop_public_key>\nAllowedIPs = 10.8.0.3\u002F32\nEOF\nsudo chmod 600 \u002Fetc\u002Fwireguard\u002Fwg0.conf\n\n# Replace eth0 with your actual outbound interface name\n# Check with: ip route show default\n\n# Enable IP forwarding (required for routing traffic through the server)\necho \"net.ipv4.ip_forward=1\" | sudo tee \u002Fetc\u002Fsysctl.d\u002F99-wireguard.conf\nsudo sysctl --system\n\n# Start WireGuard and enable on boot\nsudo wg-quick up wg0\nsudo systemctl enable wg-quick@wg0\n```\n\n## Client Configuration\n\n```bash\n# Generate a unique keypair for each client device\n# Run on the client, or on the server and transfer the private key securely — never in plaintext\numask 077\nwg genkey | tee phone_private.key | wg pubkey > phone_public.key\n\n# Client config file (phone_wg0.conf):\n[Interface]\nPrivateKey = \u003Cphone_private_key>\nAddress = 10.8.0.2\u002F32\nDNS = 192.168.1.2                  # Optional: use Pi-hole for DNS over the tunnel\n\n[Peer]\nPublicKey = \u003Cserver_public_key>\nEndpoint = your-home-ip.ddns.net:51820  # Your public IP or DDNS hostname\nAllowedIPs = 192.168.1.0\u002F24            # Split tunnel: only home network traffic\n# AllowedIPs = 0.0.0.0\u002F0, ::\u002F0        # Full tunnel: all traffic through VPN\n\nPersistentKeepalive = 25              # Keep NAT hole open (required for mobile clients)\n```\n\n## Split Tunnel vs Full Tunnel\n\n```\n# Split tunnel: AllowedIPs = 192.168.1.0\u002F24\n  Only traffic destined for your home network goes through the VPN.\n  Internet traffic (YouTube, Spotify) goes directly — better performance on mobile.\n  Best for: \"I just want to reach my NAS and Pi from anywhere.\"\n\n# Full tunnel: AllowedIPs = 0.0.0.0\u002F0, ::\u002F0\n  ALL traffic goes through your home internet connection.\n  Useful for: piggybacking home DNS\u002FPi-hole ad blocking.\n  Downside: home upload speed becomes your bottleneck everywhere.\n\n# Multi-subnet split tunnel (most common homelab use case):\n  AllowedIPs = 192.168.10.0\u002F24, 192.168.20.0\u002F24, 192.168.30.0\u002F24, 10.8.0.0\u002F24\n  Routes all your VLANs through the tunnel; internet stays direct.\n```\n\n## Key Generation and Peer Management\n\n```python\nimport subprocess\n\ndef generate_keypair() -> tuple[str, str]:\n    \"\"\"Generate a WireGuard keypair. Returns (private_key, public_key).\"\"\"\n    private = subprocess.check_output([\"wg\", \"genkey\"]).decode().strip()\n    public = subprocess.run(\n        [\"wg\", \"pubkey\"], input=private.encode(), capture_output=True\n    ).stdout.decode().strip()\n    return private, public\n\ndef generate_preshared_key() -> str:\n    return subprocess.check_output([\"wg\", \"genpsk\"]).decode().strip()\n\ndef build_client_config(\n    client_private_key: str,\n    client_vpn_ip: str,       # e.g. \"10.8.0.3\"\n    server_public_key: str,\n    server_endpoint: str,     # e.g. \"home.example.com:51820\"\n    allowed_ips: str = \"192.168.1.0\u002F24\",\n    dns: str = \"\",\n) -> str:\n    dns_line = f\"DNS = {dns}\\n\" if dns else \"\"\n    return f\"\"\"[Interface]\nPrivateKey = {client_private_key}\nAddress = {client_vpn_ip}\u002F32\n{dns_line}\n[Peer]\nPublicKey = {server_public_key}\nEndpoint = {server_endpoint}\nAllowedIPs = {allowed_ips}\nPersistentKeepalive = 25\n\"\"\"\n\ndef build_server_peer_block(\n    client_public_key: str,\n    client_vpn_ip: str,\n    comment: str = \"\",\n) -> str:\n    comment_line = f\"# {comment}\\n\" if comment else \"\"\n    return f\"\"\"\n{comment_line}[Peer]\nPublicKey = {client_public_key}\nAllowedIPs = {client_vpn_ip}\u002F32\n\"\"\"\n```\n\nKeep private keys out of source control. If you use this script, write key material\nto files with mode 600 and never log or print it.\n\n## pfSense \u002F OPNsense WireGuard\n\n```\n# pfSense: VPN → WireGuard → Add Tunnel\n  Interface Keys: Generate (creates keypair automatically)\n  Listen Port: 51820\n  Interface Address: 10.8.0.1\u002F24\n\n# Add Peer (one per client):\n  Public Key: \u003Cclient public key>\n  Allowed IPs: 10.8.0.2\u002F32\n\n# Assign the WireGuard interface:\n  Interfaces → Assignments → Add (select wg0)\n  Enable interface, no IP needed (it is set in the tunnel config)\n\n# Firewall rules:\n  WAN → Allow UDP port 51820 inbound (so clients can reach the server)\n  WireGuard interface → Allow traffic to LAN networks you want reachable\n```\n\n## DDNS (Dynamic DNS) for Home Servers\n\nMost home internet connections have a dynamic IP. Use DDNS so your VPN endpoint\nstays reachable after an IP change.\n\n```bash\n# Option 1: Cloudflare DDNS — store credentials in a secrets file, not inline\n# docker-compose entry using an env file:\n  ddns-updater:\n    image: qmcgaw\u002Fddns-updater\n    env_file: .\u002Fddns.env   # store zone_id and token here, not in compose\n    restart: unless-stopped\n\n# ddns.env (chmod 600, not committed to git):\n# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id\n# SETTINGS_CLOUDFLARE_TOKEN=your_api_token\n\n# Option 2: DuckDNS (free, simple)\n  Sign up at duckdns.org → get a token and subdomain (myhome.duckdns.org)\n  Store token in \u002Fetc\u002Fddns.env (mode 600), then use a small root-owned script:\n\n  # \u002Fusr\u002Flocal\u002Fbin\u002Fupdate-duckdns\n  #!\u002Fbin\u002Fsh\n  set -eu\n  . \u002Fetc\u002Fddns.env\n  curl --fail --silent --show-error --max-time 10 \\\n    --get \"https:\u002F\u002Fwww.duckdns.org\u002Fupdate\" \\\n    --data-urlencode \"domains=myhome\" \\\n    --data-urlencode \"token=${DUCKDNS_TOKEN}\" \\\n    --data-urlencode \"ip=\"\n\n  # Cron job:\n  *\u002F5 * * * * \u002Fusr\u002Flocal\u002Fbin\u002Fupdate-duckdns >\u002Fdev\u002Fnull 2>&1\n```\n\n## Troubleshooting\n\n```bash\n# Check WireGuard status and last handshake\nsudo wg show\n\n# If \"latest handshake\" is never or very old, the tunnel is not connected.\n# Check:\n# 1. Is UDP port 51820 open on the router\u002Ffirewall?\nsudo ufw status  # or check pfSense\u002FUniFi firewall rules\n\n# 2. Is the server public key in the client config correct?\nsudo wg show wg0 public-key   # Compare to what is in the client config\n\n# 3. Is IP forwarding enabled on the server?\ncat \u002Fproc\u002Fsys\u002Fnet\u002Fipv4\u002Fip_forward  # Should be 1\n\n# 4. Does the client AllowedIPs cover the IP you are trying to reach?\n# If AllowedIPs = 192.168.1.0\u002F24 and you are trying to reach 192.168.3.5, it will not route.\n\n# Check kernel logs for WireGuard errors\ndmesg | grep wireguard\n\n# Restart WireGuard\nsudo wg-quick down wg0 && sudo wg-quick up wg0\n```\n\n## Anti-Patterns\n\n```\n# BAD: Storing private keys in version control or sharing them\n# Private keys are equivalent to passwords — never commit them to git\n\n# BAD: Using AllowedIPs = 0.0.0.0\u002F0 on mobile without considering the impact\n# Full tunnel routes all mobile traffic through your home upload — usually slow\n\n# BAD: Not setting PersistentKeepalive on mobile clients\n# Mobile clients behind NAT drop idle tunnels without it\n\n# BAD: Opening port 51820 in the firewall but forgetting IP forwarding on the server\n# Tunnel connects but no traffic routes — confusing to debug\n\n# BAD: Sharing a keypair across multiple client devices\n# Each device must have its own unique keypair — shared keys break the security model\n\n# BAD: Using a broad \"FORWARD ACCEPT\" iptables rule\n# Scope forwarding rules to the wg0 interface and direction only\n```\n\n## Best Practices\n\n- Generate a unique keypair per client device — never reuse keys\n- Use split tunneling (`AllowedIPs = \u003Chome subnets>`) for mobile\n- Set `PersistentKeepalive = 25` on all mobile clients\n- Use DDNS if your ISP assigns a dynamic IP; store credentials in env files, not inline\n- Use scoped iptables forwarding rules (inbound on wg0 only) rather than a blanket FORWARD ACCEPT\n- Add Pi-hole's IP as `DNS =` in client configs to get ad blocking over the VPN\n- Rotate the server keypair periodically and update all client configs\n\n## Related Skills\n\n- homelab-network-setup\n- homelab-vlan-segmentation\n- homelab-pihole-dns\n",{"name":5,"metadata":44,"description":7},{"origin":38},"main","74715deacdc69119d739f38916f8ee4f0f979ebf","skills\u002Fstorage\u002Faffaan-m_ECC\u002Fhomelab-wireguard-vpn",0,"https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills","https:\u002F\u002Fgithub.com\u002Fmdnaimul22","AI Agents Workflows Automation | Specializing in Python, FastAPI, MCP Agents & Custom ERP.","2026-11-11 21:16:57",{"source":8,"skillName":5,"commands":54,"prompt":64,"zipAvailable":65},[55,58,61],{"tool":56,"command":57},"npx","npx skills add mdnaimul22\u002Fhuman-skills --skill homelab-wireguard-vpn",{"tool":59,"command":60},"bunx","bunx skills add mdnaimul22\u002Fhuman-skills --skill homelab-wireguard-vpn",{"tool":62,"command":63},"pnpm","pnpm dlx skills add mdnaimul22\u002Fhuman-skills --skill homelab-wireguard-vpn","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Faffaan-m_ECC\u002Fhomelab-wireguard-vpn\u002FSKILL.md",true,[],[68,76,106,127,146,161],{"skillNo":69,"name":70,"nameZh":6,"description":71,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":72,"categories":73,"keywords":74,"publishedAt":75},"01M72JT3YP1SQHD0MANGDXQ4RT","higgsfield-websites","Build, edit, and deploy full-stack websites, apps and games via the Higgsfield CLI (`higgsfield website …`). Each is a React 19 + TanStack Start SSR app in one Cloudflare Worker (D1\u002FR2\u002FKV\u002FDO\u002FContainers). THREE product types, picked via `--type` on create: `website` (standalone, no Higgsfield integration — references\u002Fwebsite-flow.md), `app` (Sign in with Higgsfield + fnf SDK, Quanta — references\u002Fapp-flow.md), `game` (realtime multiplayer rooms — references\u002Fgame-flow.md). Routes to the right flow; each carries its own rules and deploy\u002Fpublish gates.\nUse when: \"build me a website\", \"make a landing page\", \"create a web app\", \"build a SaaS dashboard \u002F portfolio\", \"make me a game\", \"deploy this site\", \"publish\". Also owns GAME ART: \"make a spritesheet\", \"tileable texture\", \"animate a 3D character\", game music\u002FSFX — see the game-* references.\nNOT for: single image\u002Fvideo\u002Faudio generation (higgsfield-generate), product photos (higgsfield-product-photoshoot), marketplace cards (higgsfield-marketplace-cards).","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fhiggsfield-ai_skills\u002Fhiggsfield-websites\u002FSKILL.md",[],[],"2026-11-10 22:39:42",{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":98,"publishedAt":39},"01M70JPK8HAXXA85P7Q8NKZZYG","writing-skills","Use when creating new skills, editing existing skills, or verifying skills work before deployment","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-skills\u002FSKILL.md",[82,87,92,95],{"categoryNo":83,"categoryName":84,"taxonomyType":85,"taxonomyTypeDesc":86},"01JBSEEDCAT000000000000018","写作与文案","SCENARIO","使用场景",{"categoryNo":88,"categoryName":89,"taxonomyType":90,"taxonomyTypeDesc":91},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",{"categoryNo":93,"categoryName":94,"taxonomyType":85,"taxonomyTypeDesc":86},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":96,"categoryName":97,"taxonomyType":85,"taxonomyTypeDesc":86},"01JBSEEDCAT000000000000008","测试与质量",[99,100,101,102,27,103,104,34,105],"writing","writing skills","editing","deployment","Existing","new","work",{"skillNo":107,"name":108,"nameZh":6,"description":109,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":117,"publishedAt":39},"01M70JP4PHBH1CQVVDWZ94W159","writing-plans","Use when you have a spec or requirements for a multi-step task, before touching code","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fwriting-plans\u002FSKILL.md",[112,113,114],{"categoryNo":83,"categoryName":84,"taxonomyType":85,"taxonomyTypeDesc":86},{"categoryNo":88,"categoryName":89,"taxonomyType":90,"taxonomyTypeDesc":91},{"categoryNo":115,"categoryName":116,"taxonomyType":90,"taxonomyTypeDesc":91},"01JBSEEDCAT000000000000030","开发工程师",[99,118,119,120,34,121,28,27,122,123,124,125,126],"writing plans","plans","you","code","requirements","task","multi","spec","multi-step task",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":39},"01M70JNTYVGXFZT8PASZ844TH8","verification-before-completion","Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fverification-before-completion\u002FSKILL.md",[],[134,135,136,137,138,139,27,140,141,142,34,143,105,144,145],"verification","verification before completion","complete","about","output","assertions","prs","evidence","claim","claims","commands","running",{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":153,"publishedAt":39},"01M70JNGVSQDESWN0Q5BPJ875V","using-superpowers","Use when starting any conversation - establishes how to find and use skills, requiring skill invocation before ANY response including clarifying questions","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-superpowers\u002FSKILL.md",[152],{"categoryNo":93,"categoryName":94,"taxonomyType":85,"taxonomyTypeDesc":86},[154,155,156,157,27,34,158,159,160],"using superpowers","superpowers","conversation","how to","how","find","questions",{"skillNo":162,"name":163,"nameZh":6,"description":164,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":165,"categories":166,"keywords":167,"publishedAt":39},"01M70JN57CE588NPYPYTWH1DWG","using-git-worktrees","Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback","https:\u002F\u002Fgithub.com\u002Fmdnaimul22\u002Fhuman-skills\u002Fblob\u002Fmain\u002Fskills\u002Fstorage\u002Fobra_superpowers\u002Fusing-git-worktrees\u002FSKILL.md",[],[168,169,170,171,172,173,34,105,174,175,176,177,178,179,180,119,27,181,182],"using git worktrees","Using git","git","worktrees","git worktrees","implementation","git worktree","worktree","native","Tools","isolation","fallback","feature","via","workspace"]