adllm Insights logo adllm Insights logo

WireGuard Site-to-Site VPN with Dynamic IPs: The DDNS Workaround

Published on by The adllm Team. Last modified: . Tags: WireGuard VPN Site-to-Site Dynamic IP DDNS Networking Security Linux

WireGuard has rapidly become a preferred VPN solution due to its simplicity, high performance, and robust security. While setting up WireGuard is often straightforward, configuring a resilient site-to-site VPN connection when one or both peers have dynamic IP addresses presents a common challenge. ISPs frequently assign dynamic IPs to residential and small business connections, which can break a VPN tunnel when an IP address changes.

This article provides a detailed, practical guide for establishing a reliable WireGuard site-to-site VPN where at least one peer uses a dynamic IP address. We’ll explore how to leverage Dynamic DNS (DDNS) services and scripting to maintain connectivity, ensuring your networks remain linked despite IP changes. This solution is ideal for connecting a home office to a central office, linking branch locations, or any scenario requiring a persistent VPN connection over dynamic IP infrastructure.

The Challenge: Dynamic IPs and WireGuard Endpoints

WireGuard’s core design relies on knowing the public IP address and port of its peers to establish encrypted tunnels. In a typical peer configuration, the Endpoint directive specifies this static IP address.

1
2
3
4
5
# Example Peer Configuration Snippet
[Peer]
PublicKey = <Peer's Public Key>
Endpoint = <Peer's Static IP Address>:51820
AllowedIPs = 10.0.2.0/24

When a peer’s public IP address changes (as is common with dynamic IP assignments), the Endpoint information in the other peer’s configuration becomes outdated. The VPN tunnel will fail because packets are being sent to an old, incorrect IP address, effectively severing the site-to-site connection until the configuration is manually updated. This manual intervention is impractical for maintaining a stable VPN.

The Solution: DDNS and Endpoint Management

The most effective workaround involves two key components:

  1. Dynamic DNS (DDNS): A DDNS service maps a chosen hostname (e.g., site-b.yourddns.com) to a dynamic IP address. When the IP address changes, a DDNS client on that network updates the DDNS provider, ensuring the hostname always resolves to the current public IP.
  2. WireGuard Endpoint Configuration with Hostnames & Keepalives: WireGuard’s Endpoint directive can accept a hostname. However, WireGuard typically resolves this hostname to an IP only at interface startup or on configuration reload.
  3. Persistent Keepalives: The PersistentKeepalive option in WireGuard helps maintain NAT traversal and can implicitly signal the current IP of a dynamic peer to a static one.
  4. Automated Endpoint Updates: For scenarios where WireGuard doesn’t automatically re-resolve or update the endpoint IP after a DDNS change, or when both ends are dynamic, scripts are often necessary to monitor and update the peer endpoint information dynamically.

This article focuses on using a DDNS hostname in the Endpoint field and strategies to ensure WireGuard uses the current IP address associated with that hostname.

Step 1: Setting Up DDNS

Before configuring WireGuard, the site(s) with dynamic IP addresses must set up DDNS.

  1. Choose a DDNS Provider: Numerous providers offer free and paid DDNS services. Popular choices include:

  2. Create a DDNS Hostname: Sign up with your chosen provider and create a hostname (e.g., myoffice.ddns.net).

  3. Configure a DDNS Update Client: This client software runs on a machine within the network that has the dynamic IP (often the router itself, or a server like the WireGuard host). Its job is to periodically check the network’s current public IP and notify the DDNS provider if it changes.

    • Router-based: Check your router’s administration interface for DDNS settings. This is often the simplest method.
    • Software Clients: Tools like ddclient (Linux) or provider-specific clients can be installed on a server.
    • Custom Scripts: You can use curl or other tools to interact with DDNS provider update APIs.

Ensure your DDNS hostname correctly resolves to your site’s current public IP address. You can verify this using nslookup your.ddns.hostname or dig your.ddns.hostname from an external network.

Step 2: Configuring WireGuard Peers

Let’s assume Site A has a static IP (or a very stable DDNS hostname) and Site B has a dynamic IP with DDNS configured (e.g., site-b.ddns.provider.com). If both are dynamic, the principles are similar but require careful handling of which side initiates or using scripts on both ends.

Site A Configuration (wg0.conf on Site A Server)

Site A’s WireGuard configuration will list Site B as a peer, using Site B’s DDNS hostname as the endpoint.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# /etc/wireguard/wg0.conf on Site A
[Interface]
Address = 10.100.1.1/24       # Site A's VPN IP
ListenPort = 51820            # Standard WireGuard port
PrivateKey = <Site A's Private Key>

# Peer: Site B (Dynamic IP with DDNS)
[Peer]
PublicKey = <Site B's Public Key>
# Site B's DDNS hostname is used for the endpoint.
Endpoint = site-b.ddns.provider.com:51820
AllowedIPs = 10.100.2.0/24    # Site B's VPN subnet
# PersistentKeepalive from Site A to Site B can help if Site A
# needs to actively re-establish. Optional if Site B always initiates.
# PersistentKeepalive = 25

Site B Configuration (wg0.conf on Site B Server)

Site B’s WireGuard configuration will list Site A as a peer. If Site A has a static IP, that’s straightforward. If Site A also uses DDNS, Site A’s DDNS hostname would be used here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# /etc/wireguard/wg0.conf on Site B
[Interface]
Address = 10.100.2.1/24       # Site B's VPN IP
# ListenPort is only needed if Site B expects incoming connections
# initiated by Site A. If Site B always initiates, it's not strictly
# necessary but doesn't hurt.
ListenPort = 51820
PrivateKey = <Site B's Private Key>

# Peer: Site A (Static IP or DDNS)
[Peer]
PublicKey = <Site A's Public Key>
Endpoint = <Site A's Static IP or site-a.ddns.provider.com>:51820
AllowedIPs = 10.100.1.0/24    # Site A's VPN subnet
# PersistentKeepalive is crucial for Site B (dynamic IP side)
# to keep the connection alive through NAT and inform Site A of its
# current IP address if it changes.
PersistentKeepalive = 25

Key Configuration Notes:

  • Endpoint: Use the DDNS hostname for the peer with the dynamic IP.
  • PersistentKeepalive = 25: This setting on Site B (the dynamic IP side, or any side behind NAT) causes it to send an encrypted keepalive packet to Site A every 25 seconds. This has two benefits:
    1. It keeps the NAT mapping open on Site B’s router.
    2. It continuously informs Site A of Site B’s current public IP address and port, allowing Site A to reply even if Site B’s IP has changed since the tunnel was last active. This can often be enough for the “static” side to learn the new dynamic IP.
  • Generate Keys: Use wg genkey | tee privatekey | wg pubkey > publickey to generate private and public key pairs for each site. See the WireGuard Quickstart Guide for details.

Step 3: Router and Firewall Configuration

Proper network configuration is essential.

Port Forwarding

On the site(s) that will receive initial incoming WireGuard connections (typically Site A, if it has a static IP, or any site designated as a “server”), you must configure port forwarding on the router.

  • Forward UDP port 51820 (or your chosen WireGuard port) from your router’s public interface to the internal IP address of the WireGuard server on that site.

If both sites are dynamic and behind NAT, and one is expected to “listen,” it must have reliable port forwarding. PersistentKeepalive from the “client” side helps punch through NAT.

Firewall Rules

Ensure that firewalls on both the routers and the WireGuard host machines allow:

  1. Incoming UDP traffic on port 51820 (or your custom port) to the WireGuard server.
  2. Traffic forwarded through the WireGuard tunnel interface (e.g., wg0). This often means allowing traffic from/to the AllowedIPs subnets.

For iptables on Linux, you might need rules like:

Allow UDP traffic to the WireGuard port:

1
sudo iptables -A INPUT -p udp --dport 51820 -j ACCEPT

Or, if using ufw (Uncomplicated Firewall):

1
sudo ufw allow 51820/udp

Allow traffic forwarding from WireGuard clients:

1
2
3
4
5
6
7
8
# Allow forwarding from WireGuard interface to LAN and vice-versa
sudo iptables -A FORWARD -i wg0 -o <lan_interface> -j ACCEPT
sudo iptables -A FORWARD -i <lan_interface> -o wg0 -j ACCEPT

# For NATing if WireGuard clients need to access the internet via the server
# This is more for remote access VPNs but can be part of site-to-site
# if one site routes internet for the other.
# sudo iptables -t nat -A POSTROUTING -o <wan_interface> -j MASQUERADE

Remember to make iptables rules persistent (e.g., using iptables-persistent).

Step 4: Automating WireGuard Endpoint Updates (The Scripting Solution)

While PersistentKeepalive can often maintain connections when one side is dynamic and initiates, or if the “static” side’s WireGuard implementation re-resolves DNS effectively, some WireGuard implementations might only resolve the Endpoint hostname at startup. If the DDNS IP changes, the connection can still break if the peer doesn’t update its understanding of the remote endpoint’s IP.

A script can be used on the peer that connects to the DDNS hostname to periodically check if the resolved IP for that hostname has changed and, if so, update WireGuard’s endpoint information using the wg set command.

This script is typically run on the “more static” peer (Site A in our example) or on any peer that needs to ensure it’s connecting to the correct, most recently updated IP of a dynamic peer.

Below is a conceptual Bash script. You’d run this on Site A to monitor Site B’s DDNS.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/bin/bash

# WireGuard interface name
WG_INTERFACE="wg0"
# Public key of the dynamic peer (Site B)
PEER_PUBLIC_KEY="<Site B's Public Key>"
# DDNS hostname of the dynamic peer (Site B)
PEER_DDNS_HOSTNAME="site-b.ddns.provider.com"
# WireGuard port for the dynamic peer
PEER_PORT="51820" # Ensure this matches Site B's ListenPort

# Get current endpoint IP known to WireGuard for the peer
# Uses 'wg show <interface> endpoints'
CURRENT_ENDPOINT_IP=$(wg show "${WG_INTERFACE}" endpoints | \
                        grep "${PEER_PUBLIC_KEY}" | awk '{print $2}' | \
                        cut -d: -f1)

if [ -z "${CURRENT_ENDPOINT_IP}" ]; then
    echo "Could not determine current WireGuard endpoint for peer."
    # Potentially initialize with resolved IP if no endpoint is set yet
    # This might happen on first run or if endpoint was cleared.
    RESOLVED_IP=$(dig +short "${PEER_DDNS_HOSTNAME}" | tail -n1)
    if [ -n "${RESOLVED_IP}" ] && \
       [[ "${RESOLVED_IP}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        echo "Initializing endpoint for ${PEER_PUBLIC_KEY} to ${RESOLVED_IP}:${PEER_PORT}"
        sudo wg set "${WG_INTERFACE}" peer "${PEER_PUBLIC_KEY}" \
            endpoint "${RESOLVED_IP}:${PEER_PORT}"
    else
        echo "Could not resolve DDNS hostname ${PEER_DDNS_HOSTNAME} to an IP."
    fi
    exit 1
fi

# Resolve the DDNS hostname to its current IP address
# Using 'dig +short' and 'tail -n1' to get the last IP (useful if multiple A records)
RESOLVED_IP=$(dig +short "${PEER_DDNS_HOSTNAME}" | tail -n1)

if [ -z "${RESOLVED_IP}" ]; then
    echo "Failed to resolve DDNS hostname: ${PEER_DDNS_HOSTNAME}"
    exit 1
fi

# Check if the resolved IP is a valid IPv4 address (basic check)
if ! [[ "${RESOLVED_IP}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Resolved IP ${RESOLVED_IP} is not a valid IPv4 address."
    exit 1
fi

# Compare and update if necessary
if [ "${CURRENT_ENDPOINT_IP}" != "${RESOLVED_IP}" ]; then
    echo "IP address for ${PEER_DDNS_HOSTNAME} has changed."
    echo "Old IP: ${CURRENT_ENDPOINT_IP}, New IP: ${RESOLVED_IP}"
    sudo wg set "${WG_INTERFACE}" peer "${PEER_PUBLIC_KEY}" \
        endpoint "${RESOLVED_IP}:${PEER_PORT}"
    echo "WireGuard endpoint updated for peer ${PEER_PUBLIC_KEY}."
else
    echo "IP address for ${PEER_DDNS_HOSTNAME} (${RESOLVED_IP}) is current."
    echo "No update needed for WireGuard endpoint."
fi

exit 0

Using the Script:

  1. Customize: Replace placeholders (<Site B's Public Key>, site-b.ddns.provider.com, wg0, 51820) with your actual values.

  2. Permissions: Make the script executable: chmod +x /path/to/update_wg_endpoint.sh.

  3. Test: Run it manually to ensure it works.

  4. Schedule: Run this script periodically using cron or a systemd timer. For example, every 5 minutes:

    Crontab entry:

    1
    
    */5 * * * * /path/to/update_wg_endpoint.sh >> /var/log/update_wg_endpoint.log 2>&1
    

    Ensure the user running the cron job has sudo privileges for the wg set command, or configure passwordless sudo for that specific command.

Note on DDNS Update Propagation: DNS changes take time to propagate (though DDNS services aim for low TTLs, e.g., 60-300 seconds). The script’s check interval should be balanced – too frequent might be excessive, too infrequent could lead to longer downtimes.

Alternative: Script to Update Local DDNS and Inform Peer If this machine has the dynamic IP, a script could first update its own DDNS record and then, if it were acting as a “client” to a static peer, it might not need further action if PersistentKeepalive is set. If it is the “server” with a dynamic IP, the above script on the “client” is more relevant.

Handling Both Ends Dynamic

If both Site A and Site B have dynamic IPs:

  1. Both use DDNS: Each site maintains its own DDNS hostname.
  2. PersistentKeepalive on Both Sides: Configure PersistentKeepalive in both peer stanzas.
  3. Endpoint Update Scripts on Both Sides: Each site would run a script similar to the one above to monitor and update the endpoint of the other site.
  4. One Site as “Initiator”: Even if both are dynamic, one often acts more like a “client” that is more aggressive in initiating/re-initiating the connection via its keepalives.
  5. Potential Race Conditions: This can be more complex to stabilize perfectly, as IP changes on both ends simultaneously can lead to brief discovery issues.
  6. Consider a Static “Hub”: For maximum reliability when multiple sites are dynamic, a cheap VPS with a static IP acting as a central WireGuard “hub” can simplify things. Each dynamic site connects to this static hub.

Troubleshooting Common Issues

  • wg show: Your primary diagnostic tool. The wg command documentation provides full details.
    1
    
    sudo wg show
    
    Check:
    • endpoint: Is the IP address and port correct and current for peers?
    • latest handshake: If recent, the connection is likely active. If old or (none), there’s a problem.
    • transfer: Shows data sent/received.
  • DDNS Resolution:
    1
    2
    
    nslookup your.ddns.hostname
    dig your.ddns.hostname
    
    Verify this from both inside your network and externally (e.g., using an online nslookup tool) to ensure the DDNS record is updated and has propagated.
  • Port Forwarding: Use an online port checker tool (e.g., YouGetSignal Port Checker or similar) to confirm your WireGuard UDP port is open on the public IP of the listening peer.
  • Firewall Logs: Check system and router firewall logs for dropped packets on the WireGuard port or related to the tunnel IPs.
  • DDNS Client Logs: Ensure your DDNS update client is running and successfully updating the IP with your DDNS provider.
  • Packet Captures: Use tcpdump or Wireshark on the WAN and WireGuard interfaces to inspect traffic.
    1
    2
    
    # Listen for WireGuard traffic on your WAN interface (e.g., eth0)
    sudo tcpdump -i eth0 udp port 51820 -n
    

Security Considerations

  • Strong Keys: Use strong, unique private/public key pairs for each WireGuard peer.
  • Pre-shared Keys: For an added layer of symmetric-key security against potential future quantum computing threats, consider using PresharedKey in your peer configurations.
    1
    2
    
    # Generate a preshared key using wg genpsk
    wg genpsk > preshared.key
    
    Then add PresharedKey = <contents of preshared.key> to the [Peer] section on both sides (must be the same key for that peer pair).
  • DDNS Account Security: Protect your DDNS account credentials. Use strong passwords and two-factor authentication if offered.
  • DDNS Update Security: If using scripts with API keys for DDNS updates, ensure the script and keys are secured with appropriate file permissions. Use HTTPS for update URLs.
  • Least Privilege: Run update scripts with the minimum necessary privileges.

Alternatives to DDNS Workaround

  • Static IP Address: The simplest, most reliable solution is to obtain a static IP address from your ISP for at least one of the sites.
  • VPS as a Hub: Deploy a WireGuard server on a low-cost Virtual Private Server (VPS) with a static IP. Both dynamic sites connect to this central hub. This is common for hub-and-spoke topologies.
  • Other VPN Protocols: OpenVPN or IPsec (IKEv2) have their own methods for handling dynamic IPs, but WireGuard’s performance and simplicity are often preferred if the DDNS workaround can be implemented effectively.
  • Managed VPN/SD-WAN Services: Solutions like Tailscale (uses WireGuard) or ZeroTier abstract away NAT traversal and dynamic IP complexities, often by using relay servers. They are easier to set up but rely on a third-party service.

Conclusion

Configuring a WireGuard site-to-site VPN with dynamic IP addresses requires a reliable DDNS service and a strategy for keeping WireGuard’s peer endpoint information current. By using DDNS hostnames in Endpoint configurations, leveraging PersistentKeepalive, and implementing simple scripts for endpoint updates, you can create a robust and stable VPN connection that automatically adapts to IP address changes. This approach harnesses WireGuard’s strengths while overcoming the common hurdle of dynamic IP addressing, providing a cost-effective and high-performance solution for connecting your networks.