A representative image showing the setup of building a WireGuard VPN on a Linux server and connecting a client

Overview

WireGuard is a tool that lets you configure a peer-to-peer VPN with simple setup and lightweight performance.

This article covers the entire flow of installing WireGuard on a Linux server, configuring the server key, firewall, and service, and then connecting from a Mac client.

It goes through server key generation, wg0 configuration, opening UDP port 51820, client Peer registration, and connection verification in order, so you can follow the same steps to set it up.

Server Installation

Since it runs as a system service, proceed as root.

Basic Installation

1apt install wireguard

Verify installation

1wg --version

Generate Server Key

1mkdir -p /etc/wireguard
2wg genkey | tee /etc/wireguard/server.key | wg pubkey | tee /etc/wireguard/server.pub
3
4# Set permissions
5chmod 600 /etc/wireguard/server.key

Configuration

Create the configuration file in advance

1touch /etc/wireguard/wg0.conf
2chmod 600 /etc/wireguard/wg0.conf

Set up the interface in the configuration file

  • Register the server key
  • If SaveConfig is enabled, editing this file and then stopping the server will cause the server state to overwrite it
  • If you use SaveConfig mode, you should control it via wg set etc.
1# /etc/wireguard/wg0.conf
2[Interface]
3Address = 10.200.0.1/24
4ListenPort = 51820
5PrivateKey = {server key contents}
6SaveConfig = false

Inbound Configuration (Firewall Setup)

Even though it’s peer-to-peer, the UDP server port must be open for the initial handshake.

Additionally, if you use iptables, you also need to open the port.

1iptables -I INPUT 1 -p udp --dport 51820 -j ACCEPT
2
3# ubuntu 24
4# netfilter-persistent save
5
6# Result
7# run-parts: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables save
8# run-parts: executing /usr/share/netfilter-persistent/plugins.d/25-ip6tables save

Start the Server

Register and start the service

1systemctl enable wg-quick@wg0
2systemctl start wg-quick@wg0

Check wg

1wg
2
3# Result
4# interface: wg0
5#  public key: Y9oH7jnQVVILuRnhekWDRh9s7gCOyOf2HerAfS5Iymw=
6#  private key: (hidden)

Check the interface

1ip addr show wg0
2
3# Result
4# 3: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 8920 qdisc noqueue state UNKNOWN group default qlen 1000
5#     link/none 
6#     inet 10.200.0.1/24 scope global wg0
7#       valid_lft forever preferred_lft forever

Client Installation

Generate the User Client Key (local client work)

  • The user’s private key can be generated on the server, but it must not be left on the server afterward
  • The server only needs to know the public key text
  • Delete the generated private key after moving it to the client

If installing locally on a Mac, you’ll need to install wg if it isn’t already present.

1brew install wireguard-tools

Generate Key

1# Create the directory and move into it
2mkdir -p ~/.wireguard
3cd ~/.wireguard
4
5# Generate key: wg genkey | tee {KeyName}.key | wg pubkey > {KeyName}.pub
6wg genkey | tee user.key | wg pubkey > user.pub

Important! Register the User Key on the Server

Register the peer information in the wg0.conf file

  • Multiple Peers can be configured
  • Requests sent to AllowedIPs are forwarded to that Peer
1# /etc/wireguard/wg0.conf append
2
3# plzhans
4[Peer]
5PublicKey = {public key contents}
6AllowedIPs = 10.200.0.2/32

Restart the server

1systemctl restart wg-quick@wg0

Client-Server Connection

Understand this as a characteristic of peer-to-peer communication, and simply reverse the server configuration.

Configuring the WireGuard Client

Verify installation

1wg --version
2wg-quick --version

User Client Connection (local client work)

  • Endpoint: External public ip
  • PersistentKeepalive: Keep alive time
 1# ~/.wireguard/xx-server.conf
 2
 3[Interface]
 4PrivateKey = {user private key contents}
 5Address = 10.200.0.2/24
 6
 7[Peer]
 8PublicKey = {server key contents}
 9Endpoint = {server public IP}:51820
10AllowedIPs = 10.200.0.1/32
11PersistentKeepalive = 25

Run the Client

1sudo wg-quick up ~/.wireguard/xx-server.conf
2
3# stop
4sudo wg-quick down ~/.wireguard/xx-server.conf

Check Client Status

  • If this line appears, the connection is finally established: latest handshake: 5 seconds ago
 1wg
 2
 3# Result
 4# interface: utun12
 5#   public key: b69QMnldUd60JLXEUc4j8QzKI/1su1h4e6scx/YgrHE=
 6#   private key: (hidden)
 7#   listening port: 64874
 8
 9# peer: Y9oH7jnQVVILuRnhekWDRh9s7gCOyOf2HerAfS5Iymw=
10#   endpoint: 161.33.140.98:51820
11#   allowed ips: 10.200.0.1/32
12#   latest handshake: 5 seconds ago
13#   transfer: 92 B received, 180 B sent
14#   persistent keepalive: every 25 seconds

When Using a Client GUI Tool

Import the client conf file you created.

import

Screen showing the import of the client conf file in the WireGuard GUI client

Verify the registration

Screen showing the imported tunnel registered in the GUI client’s list

Verify the connection

Screen showing the VPN tunnel connected in the GUI client

Verify Access to the Private Server

Verify SSH Access via VPN IP

1nc -vz 10.200.0.1 22
2Connection to 10.200.0.1 port 22 [tcp/ssh] succeeded!

IP Routing

Now you need to set up access to the internal network from the local client via the VPN server (using MASQUERADE).

Assume the following:

  • Server’s private network: 10.200.0.0/24
  • Server’s network interface: enp0s6
 1# /etc/wireguard/wg0.conf append
 2[Interface]
 3...
 4
 5# IP FORWARD
 6PostUp = sysctl -w net.ipv4.ip_forward=1
 7PostUp = iptables -t nat -A POSTROUTING -s 10.200.0.0/24 -o enp0s6 -j MASQUERADE
 8PostUp = iptables -I FORWARD 1 -i %i -j ACCEPT
 9PostUp = iptables -I FORWARD 1 -o %i -j ACCEPT
10
11PostDown = iptables -t nat -D POSTROUTING -s 10.200.0.0/24 -o enp0s6 -j MASQUERADE
12PostDown = iptables -D FORWARD -i %i -j ACCEPT
13PostDown = iptables -D FORWARD -o %i -j ACCEPT

Conclusion

Once you’ve finished everything from server installation to client connection verification, you can access internal services such as SSH via the VPN IP.

When adding a Peer, you only need to register the public key and AllowedIPs on the server, and keep the private key only on the client.

If you use a GUI client, you can achieve the same connection simply by importing the conf file you created.

The SaveConfig option and the firewall’s UDP port are common sources of mistakes during operation, so it’s a good idea to double-check them before configuring.

References