Networking Tutorials
William  

Set Up WireGuard in OpenWrt LuCI and Verify It

Treat LuCI as a form that writes configuration, then check what it wrote. A working OpenWrt WireGuard LuCI setup comes down to a short sequence: install luci-proto-wireguard, add a WireGuard interface, enter or import the peer, put the interface in a firewall zone, and confirm with wg show that a handshake actually happened. A green Save & Apply proves the form was accepted. It does not prove the tunnel works.

Install the packages from System > Software

Install luci-proto-wireguard and let it pull in the rest. That package includes the required WireGuard packages as dependencies, so you don't need to hunt down each piece by hand. After it installs, restart the network service. Without that restart, the protocol list often won't show WireGuard at all.

From a shell, the whole step looks like this:

opkg update
opkg install luci-proto-wireguard
service network restart

Newer snapshot builds replace opkg with apk. On those, the equivalent is apk update && apk add luci-proto-wireguard. In LuCI, the same step is System > Software: click "Update lists", filter for luci-proto-wireguard, install it, then restart the network.

Here is what the dependency chain brings in, so you can recognize the names when something breaks:

PackageWhat it does
luci-proto-wireguardAdds "WireGuard VPN" as an interface protocol in LuCI
kmod-wireguardThe kernel module that actually moves encrypted packets
wireguard-toolsThe wg command for keys and status

Older guides tell you to install luci-app-wireguard for a status page. On recent releases that page ships with luci-proto-wireguard, and the separate package may not exist in the feed. If opkg can't find it, you probably don't need it.

What does an OpenWrt WireGuard LuCI setup actually create?

LuCI writes UCI sections into /etc/config/network, and netifd builds the tunnel from them. UCI is OpenWrt's Unified Configuration Interface, the plain-text config format behind every LuCI page. The interface gets one section. Each peer gets its own section, named wireguard_ plus the interface name.

A typical client tunnel ends up looking like this:

config interface 'wg0'
    option proto 'wireguard'
    option private_key '<this router private key>'
    list addresses '10.8.0.2/32'

config wireguard_wg0
    option description 'vpn-server'
    option public_key '<server public key>'
    option endpoint_host 'vpn.example.com'
    option endpoint_port '<server port>'
    list allowed_ips '0.0.0.0/0'
    option route_allowed_ips '1'

That file is the real configuration, and LuCI is a front end for it. Once you know the shape, you can read it, diff it, and back it up. Every later step in this guide comes back to that file. For the official field reference, the OpenWrt WireGuard documentation lists every option.

Create the WireGuard interface in LuCI

Go to Network > Interfaces and click "Add new interface". Name it something short like wg0 and pick "WireGuard VPN" as the protocol. If that option is missing, go back and restart the network. A missing protocol almost always means the restart was skipped.

On the General Settings tab, fill in these fields:

  • Private key: click "Generate new key pair" unless you're importing an existing profile.
  • IP addresses: the tunnel address in CIDR form, as your peer expects it.
  • Listen port: only needed if this router accepts incoming connections. A pure client can leave it empty.

The Firewall Settings tab is where most setups quietly go wrong. Don't drop wg0 into the lan zone because a video did it. That hands every tunnel peer full LAN trust. Instead, create a dedicated zone, say vpn.

For a client tunnel to a provider, enable masquerading on that zone and allow forwarding from lan to vpn. For a server your phone dials into, allow vpn to forward to lan and skip masquerading. You'll also need a firewall rule on wan for the listen port. In the OpenWrt forum example, the server port is 51820, matched with option src_dport '51820' in a port-forward rule to a server behind the router.

Import a client profile through LuCI

If your VPN provider or your own server handed you a .conf file, import it instead of retyping keys. Open the interface's edit dialog. On the General Settings tab, look for a button along the lines of "Load configuration…". It accepts a pasted or uploaded wg-quick style file, the same [Interface] and [Peer] format the WireGuard quickstart uses.

The importer maps the standard fields onto UCI options:

wg-quick fieldUCI option
PrivateKeyprivate_key
Addressaddresses
PublicKeypeer public_key
PresharedKeypeer preshared_key
Endpointpeer endpoint_host and endpoint_port
AllowedIPspeer allowed_ips
PersistentKeepalivepeer persistent_keepalive

Some fields don't map, and this is where imports fail silently. PostUp, PreDown, and similar lines are wg-quick shell hooks. OpenWrt never runs wg-quick, so those lines are dropped. Any firewall or routing logic they carried has to be rebuilt in the OpenWrt firewall. Table has no direct equivalent. Whether DNS carries over depends on your release, so check the Advanced tab after importing.

Paste a peer snippet instead of a full profile

A full profile import fills in both this router's identity and the remote peer. A snippet import adds only a peer. On recent releases, the Peers tab has its own import control inside the "Add peer" dialog. That's the right tool when the interface already exists and you're adding a phone, a laptop, or a second site.

LuCI can't guess several things from a snippet, so set them yourself:

  • Route Allowed IPs: this checkbox decides whether netifd installs routes for the peer's allowed IPs. A snippet doesn't carry it.
  • Firewall zone: a snippet knows nothing about your zones.
  • Endpoint: a server-side peer for a roaming phone usually has none, and that's correct.
  • Which side made the keys: if you paste a client's [Interface] block into the peer dialog, you've just put its private key into a peer slot. Peers only ever get the other side's public key.

The peer dialog can also run in reverse. "Generate configuration…" produces a client profile, and a QR code if qrencode is installed, for phones. That's the cleanest way to onboard a mobile client, because the private key never leaves the screen you scan it from.

Configure peers, routes, DNS, and keepalive

Set each peer field on purpose. Most broken tunnels I see trace back to defaults pasted from someone else's network.

Allowed IPs does double duty. It's both the list of addresses a peer may send from and the routing table WireGuard uses to choose a peer. 0.0.0.0/0 means "send everything through this peer". That's right for a full-tunnel client and wrong for a site-to-site link where you only want the remote LAN. If several peers claim the same range, only one keeps it and the rest go quiet.

Route Allowed IPs turns those ranges into kernel routes. Leave it off and the tunnel handshakes fine, but nothing gets routed into it.

Persistent keepalive matters on the side that sits behind NAT and needs to stay reachable. A client behind a carrier's NAT needs it. A server with a public address usually doesn't.

DNS is separate from the tunnel. Set custom DNS servers on the interface's Advanced tab, and remember that dnsmasq serves your LAN. If clients still resolve through your ISP, the tunnel is up but your queries leak around it.

Preshared keys add a layer of symmetric encryption on top of the key exchange. The cost is another secret to move safely, so use them where you control both ends.

Verify what LuCI actually wrote

Run this first, before you trust the green status badge:

uci show network | grep -E "wg0|wireguard_wg0"
ifstatus wg0
wg show wg0
ip route show

uci show prints exactly what LuCI saved. Check the output for a peer section that's missing, an empty public_key, or route_allowed_ips set to 0. ifstatus wg0 tells you whether netifd brought the interface up. If it reports an error, the problem lives in the config, not out on the network.

wg show is the line that matters. A latest handshake value means keys and endpoint are correct and packets crossed the internet both ways. A transfer count that only grows on the sent side means you're talking and nobody is answering.

wg showconf wg0 dumps the running kernel state in wg-quick format. Diff it against your original profile and you'll spot a mangled import in seconds. The Status > WireGuard page in LuCI shows the same data, but the shell output is easier to copy into a bug report.

Read the logs before you change a setting

OpenWrt has no journalctl. Logs go into a ring buffer you read with logread, and kernel messages sit in the kernel ring buffer behind dmesg. Check both before you touch another field:

logread -e netifd
logread -e wireguard
dmesg | grep -i wireguard

netifd lines tell you whether the interface setup script failed, for example on a bad key or an unresolvable endpoint hostname. dmesg tells you whether the kernel module loaded at all. Then match the symptom to the layer:

SymptomWhere the real problem usually is
No handshake everWrong public key, wrong endpoint or port, or wan firewall blocking UDP
Handshake, but no trafficRoute Allowed IPs off, firewall zone forwarding missing, no masquerading
Handshake, IPs work, names failDNS still pointing at the ISP
Tunnel drops after idle timeKeepalive missing on the NATed side

When logs run out, go to the wire. Install tcpdump and run tcpdump -ni wan udp port <your port>. Outgoing packets with no replies point at the far end or something in between. No outgoing packets at all point at your own router. For deeper tracing across the encryption boundary, see how to trace VPN traffic with eBPF.

Common package and import failures

Most "LuCI is broken" reports land in a short list of causes. Find yours below.

WireGuard isn't in the protocol dropdown

The package installed but netifd never reloaded. Run service network restart, or reboot. Then hard-refresh the browser, because LuCI caches its JavaScript and will happily show you yesterday's menu.

Install fails with a kmod-wireguard dependency error

The message reads cannot find dependency kmod-wireguard for wireguard-tools. Kernel modules are built against an exact kernel, and the feed no longer carries a module matching yours. This hits snapshot builds hardest, because the feed moves on while your flashed kernel stays put.

Don't force-install a module from a different build. At best it refuses to load. Flash a current image instead, or bake luci-proto-wireguard into a custom image through the OpenWrt firmware selector.

The import button isn't there

Either luci-proto-wireguard isn't installed, the release predates the import feature, or the browser is serving a cached page. Check opkg list-installed | grep wireguard before blaming LuCI.

The profile imports but fields come out wrong

Usually the file is the problem. Windows line endings, a byte-order mark from a text editor, or smart quotes from a chat app will all break the parser. Open the file in a plain editor, or paste it through cat -A to see the hidden characters.

Permission errors on save

A LuCI login without write access to the network config gets refused. Log in as root, or fix the user's access rules under /usr/share/rpcd/acl.d/.

The interface vanishes after saving

"Save" only stages the change, and "Save & Apply" commits it. If you did apply, LuCI may have rolled back after the change cut your own connection, which is common when a new 0.0.0.0/0 route swallows the browser session. Apply from a LAN cable, or untick Route Allowed IPs until the handshake is confirmed.

A reproducible OpenWrt WireGuard LuCI workflow

This order survives LuCI redesigns because every step ends in something you can check from the shell:

  1. Install luci-proto-wireguard, restart the network, and confirm the module is loaded with dmesg | grep -i wireguard.
  2. Create wg0 in LuCI, generate or import keys, and assign it to its own firewall zone.
  3. Add peers, importing snippets where you have them and setting Route Allowed IPs on purpose.
  4. Click Save & Apply from a wired connection.
  5. Run uci show network and confirm the sections match what you meant to configure.
  6. Run wg show and confirm a handshake.
  7. Test routing and DNS from a LAN client, not from the router itself.
  8. Copy /etc/config/network and /etc/config/firewall somewhere off the router.

If the far end is a Linux box you run yourself, build it with the WireGuard Linux server setup guide, then point this router at it. Each side gets verified separately, which keeps failures reproducible instead of mysterious.

FAQ

Does WireGuard survive a firmware upgrade on OpenWrt?

The config usually survives, but the packages may not. A standard sysupgrade keeps /etc/config and drops packages you added after flashing, so you boot with a WireGuard interface and no module to run it. Reinstall after upgrading, or build the package into your image so it's always there.

Can one router act as a WireGuard client and server at the same time?

Yes. Create separate interfaces, for example wg0 for the outbound provider tunnel and wg1 for incoming phones. Give each its own keys, its own listen port where needed, and its own firewall zone. Keep their allowed IP ranges from overlapping, or routing gets unpredictable.

How do I see which LAN devices are pushing traffic through the tunnel?

wg show only reports totals per peer, not per LAN device. For a per-device breakdown, use a traffic accounting tool on the router, such as the one covered in mapping OpenWrt traffic to devices with nlbwmon.

Is WireGuard fast enough on a cheap router?

It runs in the kernel and is light compared with older VPN protocols. Still, encryption speed is bounded by the router's CPU, and older low-end chips will cap throughput well below a fast fiber line. Measure with iperf3 across the tunnel on your own hardware before trusting any spec sheet.