Quote from: mtchetch on March 13, 2026, 01:20:05 PMBecause of the acute lack of solutions I decided to roll my own and share it.
An easy way of doing this with minimal modifications is to use userscripts.
The scipt has been tested on OPNsense 26.1.1-amd64
Steps to implement:
Log into the firewall shell with SSH and create the script file:vi /usr/local/opnsense/service/conf/actions.d/actions_wireguardlogger.conf
Add the content (if not familiar with VI press i and then paste content and press esc and :wq and enter)[restart]
command: /bin/sh -c 'S=/var/db/wg-peer-cron.state; T=$(mktemp /tmp/wg-peer-cron.XXXXXX) || exit 1; M=$(mktemp /tmp/wg-peer-map.XXXXXX) || exit 1; N=$(date +%s); mkdir -p /var/db; python3 -c '\''import xml.etree.ElementTree as ET; root=ET.parse("/conf/config.xml").getroot(); [print(((c.findtext("pubkey") or "").strip())+"|"+((c.findtext("name") or "").strip())) for c in root.findall("./OPNsense/wireguard/client/clients/client")]'\'' > "$M"; /usr/bin/wg show all dump | awk -F "\t" -v now="$N" '\''NF==9{hs=$6+0; age=(hs>0?now-hs:999999999); st=(hs>0&&age<=300?"connected":"disconnected"); print $1 "|" $2 "|" st "|" hs "|" $4 "|" $5 "|" age}'\'' > "$T" && [ -s "$T" ] || { rm -f "$T" "$M"; exit 0; }; [ -f "$S" ] || : > "$S"; while IFS="|" read -r IF PK ST HS EP AL AGE; do O=$(awk -F "|" -v i="$IF" -v p="$PK" '\''$1==i && $2==p {print; exit}'\'' "$S"); OS=$(printf "%s" "$O" | awk -F "|" '\''{print $3}'\''); [ -n "$OS" ] || OS=unknown; PN=$(awk -F "|" -v p="$PK" '\''$1==p {print $2; exit}'\'' "$M"); [ -n "$PN" ] || PN=unknown; [ -n "$EP" ] && [ "$EP" != "(none)" ] || EP=unknown; [ "$ST" = connected ] && [ "$OS" != connected ] && logger -t wireguard -p auth.notice "wireguard peer connected: instance=$IF, peer_name=$PN, peer_pubkey=$PK, endpoint=$EP, allowed_ips=$AL, handshake_age=${AGE}s"; [ "$ST" = disconnected ] && [ "$OS" != disconnected ] && logger -t wireguard -p auth.notice "wireguard peer disconnected: instance=$IF, peer_name=$PN, peer_pubkey=$PK, endpoint=$EP, allowed_ips=$AL, handshake_age=${AGE}s"; done < "$T"; cut -d"|" -f1-6 "$T" > "$S"; rm -f "$T" "$M"'
parameters:
type: script
message: checking wireguard connections
description: Wireguard connection monitor and logger
Pay attention to the command parameter, the whole long command needs to be one line.
Restart the configd service to see the new script:service configd restart
Log into web management and go to system -> settings -> cron
Create a new job and set it to run every minute
If you did everything correctly the Wireguard log will start logging events every minute. These are accessible directly in the wireguard VPN log menu.2026-03-13T14:04:00 Notice wireguard wireguard peer connected: instance=wg0, peer_name=phone, peer_pubkey=bOa1clBIOgmJEw2To7+StkqPaA2UxKsjw=, endpoint=192.168.11.65:51888, allowed_ips=192.168.12.2/32, handshake_age=56s
2026-03-13T14:03:00 Notice wireguard wireguard peer disconnected: instance=wg0, peer_name=laptop, peer_pubkey=9V3VB9ALJtB0lgvhpCetVVEbZW6YH6Rnk=, endpoint=192.168.11.228:54553, allowed_ips=192.168.12.3/32, handshake_age=513s
2026-03-13T13:58:12 Notice wireguard wireguard peer disconnected: instance=wg0, peer_name=phone, peer_pubkey=bOa1clBIOgmJEw2To7+StkqPaA2UxKsjw=, endpoint=192.168.11.65:51888, allowed_ips=192.168.12.2/32, handshake_age=381s
2026-03-13T13:54:36 Notice wireguard wireguard peer connected: instance=wg0, peer_name=laptop, peer_pubkey=9V3VB9ALJtB0lgvhpCetVVEbZW6YH6Rnk=, endpoint=192.168.11.228:54553, allowed_ips=192.168.12.3/32, handshake_age=9s
2026-03-13T13:51:57 Notice wireguard wireguard peer connected: instance=wg0, peer_name=phone, peer_pubkey=bOa1clBIOgmJEw2To7+StkqPaA2UxKsjw=, endpoint=192.168.11.65:51888, allowed_ips=192.168.12.2/32, handshake_age=6s
Peers are marked disconnected when they have not handshaked in 300 seconds / 5 min.
For the life of me I do not understand why this simple logging is not part of the Wireguard implementation on every firewall, since it is essential to know who is accessing your firewall and from where.
QuoteThe service-sockets-require-all option... (Default is false).
Quote'Caution should be taken when configuring the server to open multiple raw sockets on the interface with several IPv4 addresses assigned... all sockets on this link will receive this message and multiple responses will be sent to the client. ... the configuration with multiple IPv4 addresses assigned should not be used when the directly connected clients are operating on that link.'
QuoteThe service-sockets-require-all option makes Kea require all sockets to be successfully bound. If any opening fails, Kea interrupts the initialization and exits with a non-zero status. (Default is false).
"The port can be unavailable only temporary. In this case, retrying the opening may resolve the problem. Kea provides two options to specify the retrying: service-sockets-max-retries and service-sockets-retry-wait-time."