WireGuard Remote Access

WireGuard is a modern VPN — lean, fast, and built on state-of-the-art cryptography, with a config so small it fits on a postcard. This guide sets up the classic road-warrior pattern: laptops and phones dialing into a home or office network from anywhere. It covers the three ways people actually run it — a native install on a Linux server, the wg-easy Docker container with a web UI, and the built-in implementations on pfSense and OPNsense — so you can pick the path that matches your environment.

Approaches: native (wg-quick) · Docker (wg-easy) · pfSense / OPNsense  ·  Pattern: road-warrior remote access
01 — How WireGuard Works

WireGuard has no "accounts" or "login" in the traditional sense. Every participant — server and each client — is a peer holding a keypair. A peer trusts another by listing its public key and the IP range it's allowed to use. There's no connection handshake to manage: traffic for an allowed IP simply goes to the matching peer.

ConceptMeaning
PeerAny node in the VPN — the server and every client are peers
KeypairEach peer has a private key (secret) and a public key (shared with others)
AllowedIPsOn a peer entry: which IPs route to that peer. The single most misunderstood setting
EndpointThe public ip:port where a peer can be reached (the server needs a stable one)
Listen portUDP port WireGuard listens on — default 51820
ℹ  AllowedIPs has two jobs. On the client, it decides what traffic to send through the tunnel (e.g. 0.0.0.0/0 = everything; 10.0.0.0/24 = only the office LAN). On the server, the same field for a client peer acts as a routing/permission filter for that client's tunnel IP. Get this wrong and traffic silently goes nowhere.
02 — Prerequisites (All Approaches)
RequirementNotes
A reachable serverA public IP, or a port forwarded to the server; a dynamic-DNS name is fine if your IP changes
UDP port openForward/allow 51820/udp (or your chosen port) to the WireGuard host
IP forwardingThe server must route between the tunnel and the LAN — enabled in each section below
A tunnel subnetPick a private range for the VPN itself, e.g. 10.8.0.0/24, separate from your LAN
Client appsOfficial WireGuard apps exist for Windows, macOS, Linux, iOS, and Android
⚠  WireGuard is UDP-only and, by design, is silent to unknown peers — it never replies to traffic that isn't cryptographically valid. That's great for stealth but means "I can't even ping the port" is normal; don't chase it as a fault. Test with a real client, not a port scanner.
03 — Native Linux: Keys & Server Config

The reference implementation. Install the tools, generate the server keypair, and write a single config file. On Debian/Ubuntu:

Install & generate the server keypair
sudo apt update && sudo apt install -y wireguard umask 077 wg genkey | tee server_private.key | wg pubkey > server_public.key cat server_private.key # you'll paste this into the config below
/etc/wireguard/wg0.conf
[Interface] Address = 10.8.0.1/24 ListenPort = 51820 PrivateKey = <server_private.key contents> # Enable NAT so clients reach the LAN/internet (eth0 = your WAN/LAN iface) PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE # --- one [Peer] block per client, added in step 04 ---
Enable IP forwarding permanently
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wg.conf sudo sysctl -p /etc/sysctl.d/99-wg.conf
ℹ  Replace eth0 with the server's real outbound interface (check with ip -brief link). The PostUp/PostDown lines are what let a connected client actually reach beyond the server itself.
04 — Native Linux: Add a Client & Start

Each client needs its own keypair. Generate one, add a matching [Peer] block to the server, and hand the client its own config. Repeat per device, incrementing the tunnel IP (10.8.0.2, .3, …).

Generate a client keypair
wg genkey | tee client1_private.key | wg pubkey > client1_public.key
Append to the server's wg0.conf
[Peer] # client1 (phone) PublicKey = <client1_public.key contents> AllowedIPs = 10.8.0.2/32
The client's own config (client1.conf)
[Interface] PrivateKey = <client1_private.key contents> Address = 10.8.0.2/24 DNS = 1.1.1.1 [Peer] PublicKey = <server_public.key contents> Endpoint = your-server-ip:51820 AllowedIPs = 0.0.0.0/0 # full-tunnel; use 10.8.0.0/24,10.0.0.0/24 for split-tunnel PersistentKeepalive = 25
Start the server & enable at boot
sudo systemctl enable --now wg-quick@wg0 sudo wg show # shows peers & handshakes
ℹ  Turn the client config into a QR code for phones: sudo apt install qrencode && qrencode -t ansiutf8 < client1.conf, then scan it in the WireGuard mobile app. PersistentKeepalive = 25 keeps the tunnel alive through NAT on the client side.
05 — Docker: wg-easy with a Web UI

If you'd rather click than hand-edit configs, wg-easy wraps WireGuard in a web admin panel — create clients, show QR codes, download configs, watch live traffic. The current image lives at ghcr.io/wg-easy/wg-easy and is best run with Compose.

docker-compose.yml
services: wg-easy: image: ghcr.io/wg-easy/wg-easy:15 container_name: wg-easy restart: unless-stopped environment: - WG_HOST=vpn.example.com # your public IP or DNS name - WG_PORT=51820 - WG_DEFAULT_ADDRESS=10.8.0.x - WG_DEFAULT_DNS=1.1.1.1 volumes: - ./etc_wireguard:/etc/wireguard ports: - "51820:51820/udp" # WireGuard tunnel - "51821:51821/tcp" # Web UI cap_add: - NET_ADMIN - SYS_MODULE sysctls: - net.ipv4.ip_forward=1 - net.ipv4.conf.all.src_valid_mark=1
Create the config dir & launch
mkdir -p etc_wireguard docker compose up -d

Browse to http://your-server-ip:51821. Recent wg-easy versions run a first-run setup wizard in the browser where you create the admin account — you no longer pass the password as an environment variable. After that, add a client, scan its QR code, and you're connected.

⚠  WG_HOST must be the address clients can reach from outside — a public IP or DNS name, not the server's private LAN IP. It gets baked into every client config's Endpoint; set it wrong and clients will try to connect to an unreachable address.
ℹ  The web UI on 51821 is an admin panel for your VPN — don't expose it raw to the internet. Keep it on the LAN, reach it over the VPN itself, or put it behind a reverse proxy with TLS and authentication (wg-easy's docs cover Traefik and Caddy setups).
06 — pfSense: Built-in WireGuard

pfSense ships WireGuard as an installable package. Install it from System → Package Manager, then configure under VPN → WireGuard. The flow mirrors the manual setup but through the GUI.

StepWhere / What
Create a tunnelVPN → WireGuard → Tunnels → Add: it generates the server keypair, set Address 10.8.0.1/24, Listen Port 51820
Add a peer per clientOn the tunnel, add a Peer with the client's public key and Allowed IPs 10.8.0.2/32
Assign & enable interfaceInterfaces → Assignments: add the WG interface, enable it (lets you firewall it)
Firewall: WAN ruleAllow UDP to 51820 on WAN so clients can connect
Firewall: WG ruleOn the WireGuard tab, allow the traffic you want clients to reach (e.g. LAN subnet)
NAT (full tunnel)Firewall → NAT → Outbound: ensure the VPN subnet is NATed out the WAN
⚠  Two firewall rules trip people up: one on WAN to let the encrypted UDP in, and a separate one on the WireGuard interface tab to permit the decrypted traffic out to your LAN. Creating the tunnel alone passes no traffic until both rules exist.
07 — OPNsense: Built-in WireGuard

OPNsense has WireGuard built in (older releases used the os-wireguard plugin). Configure it under VPN → WireGuard, which separates the server side ("Instances"/"Local") from clients ("Peers").

StepWhere / What
Create an InstanceVPN → WireGuard → Instances: generate keys, set Tunnel Address 10.8.0.1/24, Listen Port 51820
Add PeersVPN → WireGuard → Peers: client public key, Allowed IPs 10.8.0.2/32; link the peer to the instance
Enable & applyTurn on WireGuard (Settings → General) and Apply
Assign interfaceInterfaces → Assignments: add the wg0 device and enable it
Firewall rulesWAN: allow UDP 51820 in. WireGuard group/interface: allow client traffic to LAN
NAT (full tunnel)Firewall → NAT → Outbound: NAT the VPN subnet out the WAN
ℹ  On both firewalls the cleanest approach is to assign the WireGuard interface as a real interface. That gives you a dedicated firewall tab and lets you write precise rules (and run services like DNS) on the VPN, rather than relying on a floating catch-all.
08 — Split vs Full Tunnel & DNS

The client's AllowedIPs decides what goes through the VPN — this is the choice that defines the user experience.

ModeClient AllowedIPsEffect
Full tunnel0.0.0.0/0All traffic via VPN — privacy on hostile Wi-Fi, hides browsing from the local network
Split tunnel10.8.0.0/24, 10.0.0.0/24Only VPN + home LAN traffic tunnels; normal browsing stays local and fast
ℹ  For full tunnel, set the client's DNS to a resolver reachable through the tunnel (e.g. your firewall's LAN IP, or a public resolver) or DNS leaks defeat the privacy you set it up for. For split tunnel to resolve internal hostnames, point DNS at your internal resolver and it'll still use local DNS for everything else.
09 — Verify & Troubleshoot
SymptomCheck
No handshake at allUDP 51820 forwarded/allowed to the server? WG_HOST/Endpoint = a reachable public address?
Handshake, but no LAN accessIP forwarding on? NAT/masquerade rule present? Server peer AllowedIPs include the client IP?
Works then drops on mobileAdd PersistentKeepalive = 25 to the client — NAT timeout otherwise kills idle tunnels
Connected but no internet (full tunnel)DNS unreachable through tunnel — set client DNS to a resolver the tunnel can reach
Check status (native)sudo wg show — look for a recent "latest handshake"
Firewall (pfSense/OPNsense)Two rules needed: WAN allow-in on 51820, plus WG-interface allow-out to LAN