WireGuard VPN: server and clients
WireGuard is a modern VPN protocol that runs right inside the Linux kernel: a small codebase, modern cryptography, and noticeably simpler configuration than OpenVPN or IPsec. In this article you'll set up your own WireGuard server on an mHost VPS and connect a client device to it, routing that device's traffic through the server.

What you'll need
- An mHost VPS running Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9.
- SSH access as
rootor a user withsudorights. - The server's public IP address (or a domain name pointing to it) — you'll need it as the
Endpointin the client config. - A client device with the WireGuard app installed — official clients for Windows, macOS, Linux, iOS, and Android are listed at wireguard.com/install — or another Linux server.
Step 1. Install wireguard-tools
WireGuard has been built into the Linux kernel since version 5.6 — on Ubuntu 22.04/24.04, Debian 11/12, and AlmaLinux/Rocky Linux 9 there's no separate kernel module to build; you only need the wg and wg-quick userspace utilities from the wireguard-tools package.
Ubuntu and Debian
sudo apt update
sudo apt install -y wireguard-toolsAlmaLinux and Rocky Linux
sudo dnf install -y epel-release
sudo dnf install -y wireguard-toolssudo dnf install -y https://www.elrepo.org/elrepo-release-8.el8.elrepo.noarch.rpm
sudo dnf install -y kmod-wireguardCheck that the utilities were installed:
wg --versionStep 2. Generate the server and client keys
WireGuard uses a key pair — private and public — for each side of the tunnel. The private key is never shared with anyone; the public key is what the two sides exchange so they can recognize each other.
Create the directories for the server and client configuration and lock them down:
sudo mkdir -p /etc/wireguard/client
sudo chmod 700 /etc/wireguard /etc/wireguard/clientGenerate the server's key pair:
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key'And the client's key pair:
sudo sh -c 'umask 077; wg genkey | tee /etc/wireguard/client/client_private.key | wg pubkey > /etc/wireguard/client/client_public.key'wg genkeyprints a random private key to stdout;teesaves it to a file while also passing it along the pipeline;wg pubkeycomputes the matching public key from that private key.umask 077before creating the files guarantees600permissions (read/write for root only) — private keys shouldn't be readable by anyone but their owner.
You'll need the public keys in the next steps:
sudo cat /etc/wireguard/server_public.key
sudo cat /etc/wireguard/client/client_public.keyStep 3. Configure the server: wg0.conf
Find out which network interface the server uses to reach the internet — you'll need it for the NAT rules below:
ip route get 1.1.1.1The output will look like 1.1.1.1 via <gateway> dev eth0 src <server IP> — the name after dev (often eth0, but you'll also see ens3, enp1s0, and similar) is the interface you need. The examples below use eth0 — substitute your own value if it differs.
Create the server config:
sudo nano /etc/wireguard/wg0.conf[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# client
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.8.0.2/32Address— the server's own address inside the VPN subnet10.8.0.0/24.ListenPort— the UDP port WireGuard listens on; the standard WireGuard port is51820, which we'll open in the firewall in Step 6.PrivateKey— replace<SERVER_PRIVATE_KEY>with the contents of/etc/wireguard/server_private.key.PostUp/PostDown— bash commands run when the interface comes up and goes down (%iexpands to the interface name,wg0): they allow packets to be forwarded between the tunnel and the main interface (iptables -A FORWARD) and rewrite the client's source address to the server's address on the way out to the internet (MASQUERADEin thenattable) — without this, return packets from the internet wouldn't find their way back into the private10.8.0.0/24subnet.[Peer]— the client's public key and its tunnel address (AllowedIPs): this is both a permission (only accept packets from this peer if they come from10.8.0.2) and a route (send packets addressed to10.8.0.2to this specific client).
Lock down the file's permissions — it holds the server's private key:
sudo chmod 600 /etc/wireguard/wg0.confStep 4. Enable the WireGuard interface
sudo systemctl enable --now wg-quick@wg0This brings up the wg0 interface right away (it effectively runs wg-quick up wg0, including your PostUp rules) and enables it to start automatically on reboot — the unit name wg-quick@wg0 is built from the config file name /etc/wireguard/wg0.conf.
Check that everything came up:
sudo systemctl status wg-quick@wg0
sudo wg showwg show should display the wg0 interface, port 51820, and the client you added under peer — with no latest handshake yet; that appears once the client actually connects (Step 7).
Step 5. Enable IP forwarding
Without this setting, the kernel won't forward packets between the wg0 interface and the server's outbound interface at all, and the PostUp rules from Step 3 won't have any effect. Turn on forwarding persistently, via a dedicated file under /etc/sysctl.d/:
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --systemCheck the value:
sysctl net.ipv4.ip_forwardStep 6. Open the WireGuard port in ufw
WireGuard listens on UDP, not TCP — don't mix up the protocol when opening the port. For more on ufw itself, see "The ufw firewall: managing ports".
sudo ufw allow 51820/udp
sudo ufw statusStep 7. Set up the client and verify the connection
Create the client config — at the same path the ready-made recipe uses:
sudo nano /etc/wireguard/client/client.conf[Interface]
Address = 10.8.0.2/24
PrivateKey = <CLIENT_PRIVATE_KEY>
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = 203.0.113.10:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25PrivateKey— the contents of/etc/wireguard/client/client_private.key.PublicKeyunder[Peer]— the contents of/etc/wireguard/server_public.key.Endpoint— your server's public IP address (or domain) and port51820; replace203.0.113.10with your own.AllowedIPs = 0.0.0.0/0routes all of the client's traffic through the tunnel — a full VPN, not just access to the server's subnet. If you only need access to the internal10.8.0.0/24subnet without routing all other traffic, use that instead of0.0.0.0/0.PersistentKeepalive = 25— the client sends a keepalive every 25 seconds; useful if the client itself sits behind NAT (a home router, a mobile network) — without it, the server may not be able to reach the client first.
sudo chmod 600 /etc/wireguard/client/client.confTransfer the file to the client device, for example over scp (run this from the client device itself, replacing 203.0.113.10 with your server's IP):
scp root@203.0.113.10:/etc/wireguard/client/client.conf .- If the client is also Linux: copy the file to
/etc/wireguard/wg0.confand enable the interface the same way you did on the server —sudo systemctl enable --now wg-quick@wg0. - If the client is the official WireGuard app for Windows, macOS, iOS, or Android: import
client.confvia "Import tunnel(s) from file." On a phone it's often easier to scan a QR code instead:sudo apt install -y qrencode, thenqrencode -t ansiutf8 < /etc/wireguard/client/client.conf.
Bring up the tunnel on the client and check on the server that a handshake happened:
sudo wg show wg0interface: wg0
public key: <SERVER_PUBLIC_KEY>
private key: (hidden)
listening port: 51820
peer: <CLIENT_PUBLIC_KEY>
endpoint: 198.51.100.20:51280
allowed ips: 10.8.0.2/32
latest handshake: 1 minute, 5 seconds ago
transfer: 1.42 KiB received, 4.68 KiB sentWhat's next
To add another client: repeat Step 2 for a new key pair, add another [Peer] block to the server's wg0.conf with a free address in the subnet (for example, 10.8.0.3/32), and apply the change without dropping the tunnel — sudo wg syncconf wg0 <(sudo wg-quick strip wg0) (see Step 4). Create that client's config the same way as in Step 7, under its own name (for example, client2.conf).
Want a more familiar VPN option instead of WireGuard? VMmanager 6 has a ready-made Openvpn recipe — see "OpenVPN: server and clients".