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.
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.
| Concept | Meaning |
|---|---|
| Peer | Any node in the VPN — the server and every client are peers |
| Keypair | Each peer has a private key (secret) and a public key (shared with others) |
| AllowedIPs | On a peer entry: which IPs route to that peer. The single most misunderstood setting |
| Endpoint | The public ip:port where a peer can be reached (the server needs a stable one) |
| Listen port | UDP port WireGuard listens on — default 51820 |
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.| Requirement | Notes |
|---|---|
| A reachable server | A public IP, or a port forwarded to the server; a dynamic-DNS name is fine if your IP changes |
| UDP port open | Forward/allow 51820/udp (or your chosen port) to the WireGuard host |
| IP forwarding | The server must route between the tunnel and the LAN — enabled in each section below |
| A tunnel subnet | Pick a private range for the VPN itself, e.g. 10.8.0.0/24, separate from your LAN |
| Client apps | Official WireGuard apps exist for Windows, macOS, Linux, iOS, and Android |
The reference implementation. Install the tools, generate the server keypair, and write a single config file. On Debian/Ubuntu:
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.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, …).
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.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.
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.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.
| Step | Where / What |
|---|---|
| Create a tunnel | VPN → WireGuard → Tunnels → Add: it generates the server keypair, set Address 10.8.0.1/24, Listen Port 51820 |
| Add a peer per client | On the tunnel, add a Peer with the client's public key and Allowed IPs 10.8.0.2/32 |
| Assign & enable interface | Interfaces → Assignments: add the WG interface, enable it (lets you firewall it) |
| Firewall: WAN rule | Allow UDP to 51820 on WAN so clients can connect |
| Firewall: WG rule | On 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 |
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").
| Step | Where / What |
|---|---|
| Create an Instance | VPN → WireGuard → Instances: generate keys, set Tunnel Address 10.8.0.1/24, Listen Port 51820 |
| Add Peers | VPN → WireGuard → Peers: client public key, Allowed IPs 10.8.0.2/32; link the peer to the instance |
| Enable & apply | Turn on WireGuard (Settings → General) and Apply |
| Assign interface | Interfaces → Assignments: add the wg0 device and enable it |
| Firewall rules | WAN: 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 |
The client's AllowedIPs decides what goes through the VPN — this is the choice that defines the user experience.
| Mode | Client AllowedIPs | Effect |
|---|---|---|
| Full tunnel | 0.0.0.0/0 | All traffic via VPN — privacy on hostile Wi-Fi, hides browsing from the local network |
| Split tunnel | 10.8.0.0/24, 10.0.0.0/24 | Only VPN + home LAN traffic tunnels; normal browsing stays local and fast |
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.| Symptom | Check |
|---|---|
| No handshake at all | UDP 51820 forwarded/allowed to the server? WG_HOST/Endpoint = a reachable public address? |
| Handshake, but no LAN access | IP forwarding on? NAT/masquerade rule present? Server peer AllowedIPs include the client IP? |
| Works then drops on mobile | Add 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 |
