DEV Community

Cover image for Setting Up a WireGuard VPN Client on Linux
Joshua Rothe
Joshua Rothe

Posted on • Edited on • Originally published at portfolio.rothellc.com

Setting Up a WireGuard VPN Client on Linux

Full text can also be viewed here.


Setting up a WireGuard VPN for privacy and security involves setting up both server and client side systems. This guide explains how to set up a client side Linux system - with or without Pi-hole DNS filtering on the home network - and then configure the system so that WireGuard settings will switch depending on if the client system is on the home network or not. This is necessary because the WireGuard client will break your network connection if you are on your home network, and there is no need to manually switch your VPN client on and off when automation exists.

This guide assumes a WireGuard VPN server is set up, and port forwarding is configured on the home router (UDP, port 51820 should be forwarding to your WireGuard server). This guide is also written for Linux Mint - while this should also work for most Debian systems, you may need to modify some file paths depending on your distro.

Note: This post was updated Oct 18 2025 to fix two critical issues with the original approach: the WireGuard client would break upon switching network types while the client was shut down, and it did not check if the WireGuard server itself was reachable when on an external network prior to enabling. This improved method fixes these issues and keeps the WireGuard client disabled until the following is verified:

  1. The client is not on the home network.
  2. The WireGuard server is reachable.

The old method is kept in the Appendix for historical purposes, but is depreciated.


Background

VPNs are a great tool for security and privacy, with key benefits being:

Privacy and Security:

  • Location Privacy: All traffic appears to be from your home IP address. Your actual location is obscured to anyone tracking your browsing activity.
  • Data Protection: On public wireless networks, your VPN tunnel ensures packet sniffers and other malicious actors can't see your activity.
  • Audited: WireGuard is open source and is audited regularly by both organizations and individuals. All of its code is viewable by any developer curious enough to know how it functions.

Functionality:

  • Ad Blocking: If you run a Pi-Hole system at home for ad blocking, you can use that same tool while off your home network.
  • Home Network Access: This secure tunnel to your home network means you can access devices on your home network such as cameras, NAS systems, and IoT devices without needing to expose them to the internet.
  • Speed: WireGuard is fast, faster than most other comparable VPN services.

Practicality:

  • Low Overhead: Works on virtually all modern hardware.
  • Cryptographically Secure: Uses proven, modern cryptographic primitives detailed on the WireGuard homepage.
  • Free: Self explanatory. No risk of subscription fees in the future, either. Open source comes with benefits!

If you do not have a WireGuard VPN server set up and find this interesting, I've worked through the official WireGuard documentation and found it more than sufficient. Make sure this is complete before setting up clients! Windows and MacOS have dedicated programs for clients, but Linux is a bit more complicated; hence this guide.


Prerequisites

  • WireGuard server, configured with port forwarding on your home router.
  • Linux client with sudo access (tested here on Mint/Debian).
  • Basic command line familiarity.

Client Setup

SSH Key Generation

This process should be familiar (for general server access, not WireGuard-specific handshakes), but direction is provided below just in case. On the client, run:

ssh-keygen -t ed25519 -C "your-email@example.com"
Enter fullscreen mode Exit fullscreen mode

Hit enter twice - use default location and no passkey (unless you really want one).

There are two ways to copy your key to the server, the Easy Way and the More Likely Way.


The Easy Way:

The easy way only works if your client can access the server. Since we are generating keys, you should probably have password authentication enabled for SSH. In case you forgot, you modify these lines in /etc/ssh/sshd_config:

PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
Enter fullscreen mode Exit fullscreen mode

Then on the client side you can simply run:

ssh-copy-id username@server-ip
Enter fullscreen mode Exit fullscreen mode

This copies SSH key settings to the host, if security settings allow it.

The More Likely Way:

Run this on the client:

cat ~/.ssh/id_ed25519.pub
Enter fullscreen mode Exit fullscreen mode

Get this clipboard item to the server however you like, probably using another system that does have SSH access, and append it to ~/.ssh/authorized_keys. No need to disable the server's security requirements this way.


Installation

Run on the client to install necessary dependencies:

sudo apt update
sudo apt install wireguard resolvconf netcat-openbsd
Enter fullscreen mode Exit fullscreen mode

Once that is done, generate WireGuard keys (these are different than SSH keys - the former are needed to access the WireGuard server for configuration):

cd /etc/wireguard
sudo umask 077
sudo wg genkey | sudo tee privatekey | sudo wg pubkey | sudo tee publickey
Enter fullscreen mode Exit fullscreen mode

Next, create the WireGuard config file on your client using:

sudo vim /etc/wireguard/wg0.conf
Enter fullscreen mode Exit fullscreen mode

And insert the following text to create your client's WireGuard network configuration file. This facilitates the key handshake your client system will do with the server:

[Interface]
PrivateKey = # Paste the contents of /etc/wireguard/privatekey here
Address = 10.0.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = # Your server's public key goes here.
Endpoint = your-server-ip:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Enter fullscreen mode Exit fullscreen mode

Note that you have three items to fill in above. You will need the PublicKey from the server, not from what you have here on the client. So hold off on that for now.

The private key on the client can be acquired using:

sudo cat /etc/wireguard/privatekey
Enter fullscreen mode Exit fullscreen mode

The public key on the client (which will be needed on the server) can be acquired using:

sudo cat /etc/wireguard/publickey
Enter fullscreen mode Exit fullscreen mode

Now SSH into the WireGuard server, and edit the WireGuard config using:

sudo vim /etc/wireguard/wg0.conf
Enter fullscreen mode Exit fullscreen mode

Append the following to the file:

[Peer]
PublicKey = # Paste your client's public key here.
AllowedIPs = 10.0.0.x/32
Enter fullscreen mode Exit fullscreen mode

For AllowedIPs, you will need to replace the x. For my setup, the server was .1, and I had two previous clients taking up .2 and .3, so I used .4. These are the IPs the WireGuard server uses to identify the peers on its network.

Back on the client, edit the client's WireGuard config using:

sudo vim /etc/wireguard/wg0.conf
Enter fullscreen mode Exit fullscreen mode

Remember how we didn't have the server's public key last time? Fix that now:

[Interface]
PrivateKey = # Paste(d) the contents of /etc/wireguard/privatekey here
Address = 10.0.0.2/24
DNS = 1.1.1.1

[Peer]
PublicKey = # Your server's public key goes here.
Endpoint = your-server-ip:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Enter fullscreen mode Exit fullscreen mode

Three items to check, above. Make sure the Address on your client side matches AllowedIps on the server side.


Automated VPN Management

Run this first to set the WireGuard client as disabled by default:

sudo systemctl disable wg-quick@wg0
sudo systemctl stop wg-quick@wg0
Enter fullscreen mode Exit fullscreen mode

Run:

sudo vim /etc/NetworkManager/dispatcher.d/99-wireguard-auto
Enter fullscreen mode Exit fullscreen mode

Replace the script text with the following, adjusting the HOME_* values as needed:

#!/bin/bash

INTERFACE=$1
ACTION=$2

# Configuration. Change these as needed.
HOME_NETWORK_GATEWAY="192.168.1.1" # Router's IP.
HOME_WIREGUARD_SERVER="192.168.1.114" # WireGuard server IP.
WIREGUARD_PORT="51820"
HOME_EXTERNAL_IP="YOUR.EXTERNAL.IP" # Your external home network IP.
LOG_FILE="/var/log/wireguard-auto.log"

PING_TIMEOUT=5
NC_TIMEOUT=10

log_message() {
    echo "$(date): [$INTERFACE/$ACTION] $1" | tee -a "$LOG_FILE"
}

is_wireguard_server_reachable() {
    local server_ip="$1"
    local port="$2"

    if command -v nc >/dev/null 2>&1; then
        if nc -u -z -w 5 "$server_ip" "$port" >/dev/null 2>&1; then
            log_message "Server $server_ip is reachable and responding"
            return 0
        else
            log_message "WireGuard port $port on $server_ip is not responding"
            return 1
        fi
    else
        # Fallback if nc is not available.
        log_message "Error: nc not available"
        return 1
    fi
}

is_home_network() {
    if ping -c 3 -W "$PING_TIMEOUT" "$HOME_NETWORK_GATEWAY" >/dev/null 2>&1; then
        if ping -c 3 -W "$PING_TIMEOUT" "$HOME_WIREGUARD_SERVER" >/dev/null 2>&1; then
            local_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{print $7; exit}')
            if [[ "$local_ip" =~ ^192\.168\.1\. ]]; then
                return 0
            fi
        fi
    fi
    return 1
}

enable_wireguard() {
    if ! systemctl is-active --quiet wg-quick@wg0; then
        log_message "Enabling WireGuard"
        systemctl enable wg-quick@wg0
        systemctl start wg-quick@wg0
    fi
}

disable_wireguard() {
    if systemctl is-active --quiet wg-quick@wg0; then
        log_message "Disabling WireGuard"
        systemctl disable wg-quick@wg0
        systemctl stop wg-quick@wg0
    fi
}

# Only act on interface up events.
if [ "$2" != "up" ]; then
    exit 0
fi

log_message "Network change detected on $1"

sleep 10

if is_home_network; then
    log_message "Home network confirmed - ensuring WireGuard is disabled"
    disable_wireguard
else
    log_message "External network confirmed - testing VPN server"
    if is_wireguard_server_reachable "$HOME_EXTERNAL_IP" "$WIREGUARD_PORT"; then
        log_message "VPN server reachable - enabling WireGuard"
        enable_wireguard
    else
        log_message "VPN server unreachable - WireGuard remains disabled"
        disable_wireguard
    fi
fi
Enter fullscreen mode Exit fullscreen mode

Next, run sudo vim /etc/systemd/system/wireguard-boot-check.service and add:

[Unit]
Description=WireGuard Boot Config
After=network-online.target
Wants=network-online.target
Before=wg-quick@wg0.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/wireguard-boot-check.sh
RemainAfterExit=yes
TimeoutStartSec=150

[Install]
WantedBy=multi-user.target
Enter fullscreen mode Exit fullscreen mode

Then run sudo vim /usr/local/bin/wireguard-boot-check.sh and add the following adjusting variables as needed:

#!/bin/bash

# Configuration - adjust these to match your setup.
HOME_NETWORK_GATEWAY="192.168.1.1"
HOME_WIREGUARD_SERVER="192.168.1.114"
HOME_EXTERNAL_IP="YOUR.EXTERNAL.IP.HERE"
WIREGUARD_PORT="51820"
LOG_FILE="/var/log/wireguard-boot-check.log"

# Timeout after 2 min.
NETWORK_WAIT_TIMEOUT=120
PING_TIMEOUT=5
NC_TIMEOUT=10

log_message() {
    echo "$(date): $1" | tee -a "$LOG_FILE"
}

is_wireguard_server_reachable() {
    local server_ip="$1"
    local port="$2"

    # Give network time to settle on boot.
    sleep 10
    check_timeout

    if command -v nc >/dev/null 2>&1; then
        if nc -u -z -w 5 "$server_ip" "$port" >/dev/null 2>&1; then
            log_message "Server $server_ip is reachable and responding"
            return 0
        else
            log_message "WireGuard port $port on $server_ip is not responding"
            return 1
        fi
    else
        # Fallback if nc is not available.
        log_message "Error: nc not available"
        return 1
    fi
}

is_home_network() {
    # Check if home gateway is reachable.
    if ping -c 3 -W "$PING_TIMEOUT" "$HOME_NETWORK_GATEWAY" >/dev/null 2>&1; then
        # Second check by pinging WireGuard server directly.
        if ping -c 3 -W "$PING_TIMEOUT" "$HOME_WIREGUARD_SERVER" >/dev/null 2>&1; then
            # Third check by seeing if client IP is in the home range.
            local_ip=$(ip route get 8.8.8.8 2>/dev/null | awk '{print $7; exit}')
            if [[ "$local_ip" =~ ^192\.168\.1\. ]]; then
                log_message "Confirmed home network (gateway: $HOME_NETWORK_GATEWAY, server: $HOME_WIREGUARD_SERVER, local IP: $local_ip)"
                return 0
            fi
        fi
    fi
    log_message "Not on home network"
    return 1
}

enable_wireguard() {
    log_message "Enabling WireGuard"
    systemctl enable wg-quick@wg0
    systemctl start wg-quick@wg0
}

log_message "Boot check: Starting (WireGuard disabled by default)"

# Wait for stable network connectivity.
elapsed=0
while [ $elapsed -lt $NETWORK_WAIT_TIMEOUT ]; do
    if ping -c 2 -W 3 8.8.8.8 >/dev/null 2>&1; then
        log_message "Network connectivity established after ${elapsed}s"
        break
    fi
    log_message "Waiting for network connectivity... (${elapsed}s)"
    sleep 10
    elapsed=$((elapsed + 10))
done

# If still no connectivity, exit.
if ! ping -c 2 -W 3 8.8.8.8 >/dev/null 2>&1; then
    log_message "No network connectivity after ${elapsed}s - WireGuard remains disabled"
    exit 0
fi

log_message "Allowing network to settle..."
sleep 15

# Network location detection.
if is_home_network; then
    log_message "Home network confirmed - WireGuard remains disabled"
else
    log_message "External network confirmed - testing VPN server accessibility"
    if is_wireguard_server_reachable "$HOME_EXTERNAL_IP" "$WIREGUARD_PORT"; then
        log_message "VPN server confirmed reachable - enabling WireGuard"
        enable_wireguard
    else
        log_message "VPN server not accessible - WireGuard remains disabled"
    fi
fi

log_message "Boot check: WireGuard configuration complete"
Enter fullscreen mode Exit fullscreen mode

Make the script executable and enable it.

sudo chmod +x /usr/local/bin/wireguard-boot-check.sh
sudo systemctl enable wireguard-boot-check.service
Enter fullscreen mode Exit fullscreen mode

Lastly, run:

sudo vim /etc/wireguard/wg0.conf
Enter fullscreen mode Exit fullscreen mode

And add this to the file:

[Interface]
PrivateKey = # Your laptop's private key.
Address = 10.0.0.2/24
DNS = 1.1.1.1 # Or 192.168.1.xxx for Pi-Hole.

[Peer]
PublicKey = # Your server's public key.
Endpoint = your-server-external-ip:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Enter fullscreen mode Exit fullscreen mode

Note for Endpoint above, you need your server's external IP. This will be your home router's IP, port forwarded to your VPN server.


Verification

After setup is complete, verify your VPN is working correctly.

On an 'away' network, run:

sudo wg show
sudo systemctl status wg-quick@wg0
ip addr show wg0
Enter fullscreen mode Exit fullscreen mode

You should see your WireGuard client configuration in the output.

You should also see your home external IP displayed when you try to view your client's public IP. Verify this using:

curl -s ifconfig.me
Enter fullscreen mode Exit fullscreen mode

Verify DNS resolution using:

nslookup google.com
Enter fullscreen mode Exit fullscreen mode

The logs should also populate as your client switches networks or reboots. This can be viewed using:

sudo tail -f /var/log/wireguard-auto.log
sudo tail -f /var/log/wireguard-boot-check.log
Enter fullscreen mode Exit fullscreen mode

Conclusion

That should get you a working VPN client! If you notice any issues with this guide, please feel free to reach out or leave a comment.


Appendix

The old (depreciated) method can be viewed here.

Top comments (0)