Skip to content

Instantly share code, notes, and snippets.

@fuomag9
Created February 26, 2026 19:59
Show Gist options
  • Select an option

  • Save fuomag9/e226fa1178957d5303ff97bee292ce2e to your computer and use it in GitHub Desktop.

Select an option

Save fuomag9/e226fa1178957d5303ff97bee292ce2e to your computer and use it in GitHub Desktop.
wg-reresolve-dns.sh
#!/bin/bash
# wg-reresolve-dns.sh — Re-resolve DNS for WireGuard peers managed by systemd-networkd
# Reads .netdev files, resolves endpoint hostnames, and updates via `wg set`.
set -euo pipefail
export LC_ALL=C
NETDEV_DIR="/etc/systemd/network"
update_peer() {
local iface="$1" pubkey="$2" endpoint="$3"
local host port current_endpoint resolved_ip current_ip
# Split endpoint into host:port
if [[ "$endpoint" =~ ^\[(.+)\]:([0-9]+)$ ]]; then
host="${BASH_REMATCH[1]}"
port="${BASH_REMATCH[2]}"
elif [[ "$endpoint" =~ ^(.+):([0-9]+)$ ]]; then
host="${BASH_REMATCH[1]}"
port="${BASH_REMATCH[2]}"
else
echo "WARNING: Cannot parse endpoint: $endpoint" >&2
return 0
fi
# Skip if endpoint is already a bare IP address
if [[ "$host" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$host" =~ : ]]; then
return 0
fi
# Resolve hostname
resolved_ip=$(getent ahosts "$host" 2>/dev/null | awk 'NR==1{ print $1 }') || true
if [ -z "$resolved_ip" ]; then
echo "WARNING: Cannot resolve $host for $iface" >&2
return 0
fi
# Get current endpoint from wg show (use awk fixed-string match to avoid regex issues with +/= in keys)
current_endpoint=$(wg show "$iface" endpoints 2>/dev/null \
| awk -v key="$pubkey" '$1 == key { print $2 }') || true
if [ -z "$current_endpoint" ]; then
echo "WARNING: Peer $pubkey not found on $iface" >&2
return 0
fi
# Extract current IP (strip port)
if [[ "$current_endpoint" =~ ^\[(.+)\]:[0-9]+$ ]]; then
current_ip="${BASH_REMATCH[1]}"
elif [[ "$current_endpoint" =~ ^(.+):[0-9]+$ ]]; then
current_ip="${BASH_REMATCH[1]}"
else
current_ip=""
fi
if [[ "$resolved_ip" != "$current_ip" ]]; then
echo "Updating $iface peer ${pubkey:0:8}...: $current_ip -> $resolved_ip ($host:$port)"
wg set "$iface" peer "$pubkey" endpoint "$resolved_ip:$port"
fi
}
# --- Main loop over .netdev files ---
for netdev_file in "$NETDEV_DIR"/*.netdev; do
[ -f "$netdev_file" ] || continue
iface=""
is_wireguard=0
in_peer=0
pubkey=""
endpoint=""
while IFS= read -r raw_line || [[ -n "$raw_line" ]]; do
# Strip inline comments and leading/trailing whitespace
line="${raw_line%%#*}"
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[ -z "$line" ] && continue
# Detect section headers
if [[ "$line" == "["* ]]; then
# Flush previous peer
if [[ $in_peer -eq 1 && -n "$pubkey" && -n "$endpoint" && -n "$iface" ]]; then
update_peer "$iface" "$pubkey" "$endpoint"
fi
pubkey=""
endpoint=""
in_peer=0
[[ "$line" == "[WireGuardPeer]" ]] && in_peer=1
continue
fi
key="${line%%=*}"
value="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"; key="${key%"${key##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"; value="${value%"${value##*[![:space:]]}"}"
if [[ $in_peer -eq 0 ]]; then
case "$key" in
Name) iface="$value" ;;
Kind) [[ "$value" == "wireguard" ]] && is_wireguard=1 ;;
esac
else
case "$key" in
PublicKey) pubkey="$value" ;;
Endpoint) endpoint="$value" ;;
esac
fi
done < "$netdev_file"
# Flush last peer in file
if [[ $in_peer -eq 1 && -n "$pubkey" && -n "$endpoint" && -n "$iface" && $is_wireguard -eq 1 ]]; then
update_peer "$iface" "$pubkey" "$endpoint"
fi
done
@fuomag9
Copy link
Author

fuomag9 commented Feb 26, 2026

Use like this

/etc/systemd/system/wg-reresolve-dns.service

[Unit]
Description=Reresolve DNS of all WireGuard endpoints (systemd-networkd)
Wants=network-online.target
After=network-online.target nss-lookup.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wg-reresolve-dns.sh

/etc/systemd/system/wg-reresolve-dns.timer

[Unit]
Description=Periodically reresolve DNS of all WireGuard endpoints

[Timer]
OnCalendar=*:*:0/10

[Install]
WantedBy=timers.target

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment