---
title: "How to Host Your Own VPN on a VPS with WireGuard"
description: "Set up WireGuard on an Ubuntu or Debian VPS: keys, server config, forwarding and NAT, firewall, phone clients by QR code, leak checks."
url: https://offshoreserv.com/docs/vps/wireguard-vpn
lang: en
updated: 2026-09-26
source: HTML page at the url above (canonical); this is its Markdown version
---

Knowledge base · VPS

# How to host your own VPN on a VPS with WireGuard

Host a VPN on a VPS with WireGuard: install it on Ubuntu 24.04 or Debian, write wg0.conf with NAT, open UFW, add clients by QR code and test for leaks.

Updated 26 September 2026 11 min read

In this guide

- WireGuard runs inside the Linux kernel, so the smallest VPS plan is enough for a personal VPN.
- Create keys with umask 077, then write /etc/wireguard/wg0.conf with NAT on your real network interface.
- Turn on IP forwarding, allow 51820/udp and the tunnel's route in UFW, and keep SSH open first.
- Give every device its own key pair and address; phones scan their configuration as a QR code.
- Check your public IP, DNS and IPv6 from a connected device before you rely on the tunnel.

VPS13 sections

To host your own VPN on a VPS with WireGuard, install the `wireguard` package, create key pairs for the server and each device, describe the tunnel in `/etc/wireguard/wg0.conf` with NAT to the server's network interface, turn on IP forwarding, open UDP port 51820 and start `wg-quick@wg0`. Each device connects with its own configuration file or QR code.

These steps set up a WireGuard VPS on an [Offshore VPS](https://offshoreserv.com/offshore-vps) running Ubuntu 24.04 LTS, Debian 12 or Debian 13, with root access over SSH as described in [getting started](https://offshoreserv.com/docs/getting-started). Replace `SERVER_IP` with your server's IPv4 address and `eth0` with your network interface.

> Keep your SSH session open until the tunnel works. If a firewall mistake locks you out, the console in the client area still reaches your VPS.

## Before you start: what your WireGuard VPS needs

- **A VPS with a public IPv4 address.** Every plan includes a dedicated IPv4 address and a /64 IPv6 block.
- **Root access over SSH.**
- **The WireGuard app on each device.** The [WireGuard install page](https://www.wireguard.com/install/) lists official apps for Windows, macOS, iOS and Android; Linux computers use the `wireguard-tools` package.

**The smallest plan is enough.** WireGuard runs inside the Linux kernel and needs little memory, so the Dinghy plan (1 vCPU, 1 GB RAM, $2.39 a month) carries a personal VPN. Encryption is CPU work: pick more vCPUs if many devices will move large amounts of data at once. Bandwidth is unmetered under fair use.

A location close to you keeps browsing fast; one in another country changes which law applies to the server. Compare latency on the [network page](https://offshoreserv.com/network) and read our guide to [an offshore VPS for a VPN](https://offshoreserv.com/blog/offshore-vps-for-vpn) before you choose. As for any [no-KYC VPS](https://offshoreserv.com/no-kyc-vps), your account is an email address and a password, and you pay in crypto.

Our [acceptable use policy](https://offshoreserv.com/acceptable-use-policy) lists VPNs among the welcome uses. Your devices' traffic leaves with your server's IP address, so abuse sent through the tunnel counts as coming from your server: give access only to people you trust. Outbound port 25 is closed by default.

## Install WireGuard on Ubuntu or Debian

To set up WireGuard on Ubuntu 24.04 or Debian, install three packages and check the version:

```
apt update
apt install -y wireguard ufw qrencode
wg --version
```

The `wireguard` package installs the `wg` and `wg-quick` tools. The tunnel itself is part of the kernel on Ubuntu 24.04 and Debian 12 and 13, so nothing needs compiling. `ufw` brings the `iptables` command that the NAT rules use; leave it out if you manage your firewall with nftables. `qrencode` draws QR codes for phones.

## Generate the server and client keys

```
cd /etc/wireguard
umask 077
wg genkey | tee server.key | wg pubkey > server.pub
wg genkey | tee laptop.key | wg pubkey > laptop.pub
```

`umask 077` makes the files you create in this shell readable by root only, and `/etc/wireguard` is already closed to other users. `wg genkey` prints a random private key and `wg pubkey` derives its public key, as in the [WireGuard quick start](https://www.wireguard.com/quickstart/). Whoever holds a private key can connect as that device, so keep it secret.

Use one key pair per device, named after it, so you can revoke one device without touching the others. You can also generate a device's keys on the device itself and copy only its public key to the server.

## Write the WireGuard server configuration

The whole WireGuard server setup lives in one file. First find the interface that carries the server's internet traffic:

```
ip route show default
```

The word after `dev` is its name, such as `eth0`, `ens3` or `enp1s0`: use it in both NAT lines. The `$(cat...)` parts copy the keys into the file:

```
cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = $(cat /etc/wireguard/server.key)
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

[Peer]
# laptop
PublicKey = $(cat /etc/wireguard/laptop.pub)
AllowedIPs = 10.8.0.2/32
EOF
chmod 600 /etc/wireguard/wg0.conf
```

- **Address**: The server's address inside the tunnel. The private range `10.8.0.0/24` leaves room for 253 devices.
- **ListenPort**: The UDP port that devices connect to; 51820 is the one in WireGuard's own examples.
- **PrivateKey**: The server's private key, which is why the file stays readable by root only.
- **PostUp and PostDown**: Commands that `wg-quick` runs with bash when the tunnel starts and stops, as its [manual page](https://manpages.ubuntu.com/manpages/noble/en/man8/wg-quick.8.html) describes. Here they add and remove NAT, so devices go online with the server's IPv4 address.
- **[Peer]**: One block per device. WireGuard drops any packet from a device whose source address is outside its `AllowedIPs`, a design it calls [cryptokey routing](https://www.wireguard.com/).

> To change `Address`, `PostUp` or `PostDown` later, stop the tunnel first with `systemctl stop wg-quick@wg0`, edit the file, then start it again. `wg-quick` removes the NAT rules with the `PostDown` lines as they read when it stops. Lines edited while the tunnel runs no longer match the loaded rules: the stop logs an error and can leave an old rule behind.

## Enable IP forwarding

Linux passes packets between interfaces only when forwarding is on, and the [kernel documentation](https://docs.kernel.org/networking/ip-sysctl.html) lists it as off by default. Turn it on now and at every boot:

```
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-wireguard.conf
sysctl --system
sysctl net.ipv4.ip_forward
```

The last command must print `net.ipv4.ip_forward = 1`. `sysctl --system` loads the files in `/etc/sysctl.d` and the other locations listed in its [manual page](https://manpages.debian.org/trixie/procps/sysctl.8.en.html), and the same files apply again at each boot.

## Open the firewall

Allow SSH first, so that enabling UFW cannot cut you off, then the WireGuard port and the tunnel's route out:

```
ufw allow 22/tcp
ufw allow 51820/udp
ufw route allow in on wg0 out on eth0
ufw enable
ufw status verbose
```

Allow your own SSH port instead of 22 if you changed it with the [hardening guide](https://offshoreserv.com/docs/security/hardening). `ufw enable` warns that it may disrupt existing SSH connections; answer `y`. UFW drops forwarded traffic by default, and its status shows `deny (routed)`, so the `route` rule is what lets tunnel traffic out through `eth0`. Replies return automatically, and each rule also covers IPv6.

### With nftables instead of UFW

If you keep your own `/etc/nftables.conf` from the hardening guide, skip UFW, delete the `PostUp` and `PostDown` lines from `wg0.conf`, add `udp dport 51820 accept` to the `input` chain, and add this table at the end of the file:

```
table inet wg-nat {
  chain postrouting {
    type nat hook postrouting priority srcnat;
    ip saddr 10.8.0.0/24 oifname "eth0" masquerade
  }
}
```

Check the file, then load it:

```
nft -c -f /etc/nftables.conf
nft -f /etc/nftables.conf
```

Written in the file, the NAT rule survives reloads. Added by `PostUp`, it would vanish whenever the file's `flush ruleset` line runs.

## Start the tunnel and enable it at boot

```
systemctl enable --now wg-quick@wg0
wg show
```

`wg-quick@wg0` reads `wg0.conf`, creates the `wg0` interface, runs the `PostUp` commands and starts again at every boot. `wg show` lists `listening port: 51820` and your laptop as a peer, without a handshake until the laptop connects. If the service fails, read its log with `journalctl -u wg-quick@wg0 -b`.

## Add a client

### A laptop or desktop: a configuration file

Write the laptop's file on the server, with your server's IPv4 address in place of `SERVER_IP`:

```
umask 077
cat > /etc/wireguard/laptop.conf <<EOF
[Interface]
PrivateKey = $(cat /etc/wireguard/laptop.key)
Address = 10.8.0.2/32
DNS = 9.9.9.9

[Peer]
PublicKey = $(cat /etc/wireguard/server.pub)
Endpoint = SERVER_IP:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
EOF
```

- **AllowedIPs = 0.0.0.0/0,::/0**: All traffic goes through the tunnel. Keep `::/0` even without IPv6 on the server: IPv6 then enters the tunnel and is dropped there, instead of leaking around it.
- **DNS**: 9.9.9.9 is [Quad9](https://quad9.net/service/service-addresses-and-features/), which blocks malware domains and validates DNSSEC; any resolver you trust works. Queries travel inside the tunnel.
- **PersistentKeepalive = 25**: Stops home routers and mobile networks from closing the connection. WireGuard's quick start calls 25 seconds "a sensible interval that works with a wide variety of firewalls".

Copy the file from the laptop (if root logins are off after hardening, copy it through your sudo user), then delete the laptop's private key and file from the server, which needs only the public key:

```
scp root@SERVER_IP:/etc/wireguard/laptop.conf .
```

```
rm /etc/wireguard/laptop.key /etc/wireguard/laptop.conf
```

On Windows and macOS, import the file in the WireGuard app. On Linux, copy it to `/etc/wireguard/` and run `wg-quick up laptop`; the `DNS` line needs resolvconf, which systemd-resolved provides on Ubuntu and the `openresolv` package on Debian.

### A phone: scan a QR code

A phone needs its own keys, address and `[Peer]` block on the server:

```
cd /etc/wireguard
umask 077
wg genkey | tee phone.key | wg pubkey > phone.pub
cat >> /etc/wireguard/wg0.conf <<EOF

[Peer]
# phone
PublicKey = $(cat /etc/wireguard/phone.pub)
AllowedIPs = 10.8.0.3/32
EOF
systemctl reload wg-quick@wg0
```

Write `phone.conf` like `laptop.conf`, with `phone.key` and `10.8.0.3/32`, and display it in the terminal:

```
qrencode -t ansiutf8 < /etc/wireguard/phone.conf
```

Scan it with the WireGuard app's QR code option, then delete `phone.key` and `phone.conf` from the server.

## Add IPv6 (optional)

The simplest way to give devices IPv6 is a private range inside the tunnel, translated to the server's public IPv6 address as with IPv4; devices then share that address. Private ranges start with `fd` followed by random digits ([RFC 4193](https://www.rfc-editor.org/rfc/rfc4193.html)), so choose your own instead of `fd8c:5e2a:91b4::/64`. Stop the tunnel with `systemctl stop wg-quick@wg0`, then in `wg0.conf` replace the `Address` line and add the IPv6 NAT lines below the IPv4 ones:

```
Address = 10.8.0.1/24, fd8c:5e2a:91b4::1/64
PostUp = ip6tables -t nat -A POSTROUTING -s fd8c:5e2a:91b4::/64 -o eth0 -j MASQUERADE
PostDown = ip6tables -t nat -D POSTROUTING -s fd8c:5e2a:91b4::/64 -o eth0 -j MASQUERADE
```

Give each device an IPv6 address on both sides, for example `AllowedIPs = 10.8.0.2/32, fd8c:5e2a:91b4::2/128` in its `[Peer]` block and `Address = 10.8.0.2/32, fd8c:5e2a:91b4::2/128` in its own file. Then enable IPv6 forwarding and start the tunnel again:

```
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.d/99-wireguard.conf
sysctl --system
systemctl start wg-quick@wg0
```

> With forwarding on, Linux ignores router advertisements. If `ip -6 route show default` shows `proto ra`, your server learns its IPv6 route that way: also add `net.ipv6.conf.eth0.accept_ra = 2` to the same file, or the server's own IPv6 stops working when that route expires.

With nftables, add `ip6 saddr fd8c:5e2a:91b4::/64 oifname "eth0" masquerade` to the `wg-nat` table instead. Public addresses from your /64 are possible too, but depending on how the block reaches your server, they usually need neighbor discovery proxying.

## Check for leaks

From a connected device, not from the server, check three things:

1. **Public IP.** A what-is-my-IP website must show your server's IPv4 address, plus its IPv6 address if you added IPv6, and never your home address.
2. **DNS.** A DNS leak test must list only resolvers of the service in your `DNS` line, not your internet provider's.
3. **IPv6.** Without IPv6 in the tunnel, no IPv6 address should appear at all. If your home IPv6 address shows, check that the device's `AllowedIPs` contains `::/0`.

On the server, `wg show` then lists a `latest handshake` for the device and a `transfer` line that grows as you browse. A tunnel changes the address websites see, not the accounts you sign in to.

## Keep your WireGuard VPS updated and add more clients

WireGuard updates arrive with the kernel and `wireguard-tools`. The automatic security updates from the hardening guide install them; a new kernel applies after a reboot, and `wg-quick@wg0` starts by itself. Back up `/etc/wireguard`, which holds the server's private key.

- **Another device:** repeat the phone steps with the next free address, `10.8.0.4/32` and so on.
- **Apply peer changes:** `systemctl reload wg-quick@wg0` runs `wg syncconf`, which, says the [wg manual](https://manpages.ubuntu.com/manpages/noble/en/man8/wg.8.html), "has the benefit of not disrupting current peer sessions".
- **Remove a device:** delete its `[Peer]` block and reload. Its key stops working at once.
- **Optional:** a `PresharedKey` from `wg genpsk`, set on both sides of a peer, adds symmetric-key cryptography "for post-quantum resistance".

## Troubleshooting

### No handshake

No `latest handshake` for a device in `wg show` means the key exchange never completed. Check that:

- the service runs: `systemctl status wg-quick@wg0`;
- UFW allows `51820/udp`. Some networks block UDP, and WireGuard has [no TCP mode](https://www.wireguard.com/known-limitations/), so also test from mobile data;
- the device's `Endpoint` has the right address and port;
- the keys are crossed: the device's file holds the server's public key, and the server's `[Peer]` block holds the device's.

### Handshake but no traffic

A handshake without working websites means the server does not forward or translate the traffic. Check that:

- `sysctl net.ipv4.ip_forward` prints 1;
- the NAT rule names the interface from `ip route show default`. A wrong name gives exactly this symptom, and the counters in `iptables -t nat -L POSTROUTING -n -v` show whether the rule matches. Fix it with the tunnel stopped, as described above;
- UFW has the `route allow` rule;
- the device's `Address` matches its `AllowedIPs` on the server;
- names resolve. If `ping 9.9.9.9` works but websites do not, fix the `DNS` line.

### Some sites load, others hang: the MTU

`wg-quick` sets the tunnel's MTU 80 bytes below the local network's: 1420 on a normal 1500-byte link. If a smaller MTU sits further along the path, such as a DSL line using PPPoE behind your router, large packets get lost: small pages load, while downloads and some sites stall. Add `MTU = 1380` to the device's `[Interface]` section and reconnect. If that is not enough, try 1280, the smallest MTU that [IPv6 allows](https://www.rfc-editor.org/rfc/rfc8200.html).

## Frequently asked questions

### Is it legal to host your own VPN on a VPS?

Running a VPN for yourself is an ordinary use of a server, and our acceptable use policy lists VPNs among the welcome uses on every plan. What you may do through it depends on the law where you are and where the server runs, so check your own jurisdiction. Traffic from the tunnel leaves with your server's IP address.

### How many devices can one WireGuard server handle?

The `10.8.0.0/24` range in this guide has room for 253 devices besides the server. In practice the limit is the CPU time that encryption takes and your bandwidth, not the protocol. Watch `top` during busy hours and move up a plan if the CPU stays near 100%.

### Does WireGuard keep logs?

WireGuard writes no connection logs. While the tunnel runs, it keeps each device's last IP address and port in memory, which `wg show` displays, and journald notes when `wg-quick` starts and stops. We do not log or inspect customer server traffic, so we keep no record of what passes through your tunnel.

### Which port does WireGuard use?

Whichever UDP port you set with `ListenPort`; this guide uses 51820, the port in WireGuard's own examples. To change it, edit `ListenPort`, the port in each device's `Endpoint` and the firewall rule. WireGuard runs over UDP only, so a network that blocks UDP blocks it on any port.

**Stuck on a step?**

Dedicated and GPU customers can open a ticket from the [client area](https://offshoreserv.com/account/support) with the server’s IP address and what they tried. First reply target: under 12 hours. For every other server, use the Server actions on its page, the [guides](https://offshoreserv.com/docs) and the [network status](https://offshoreserv.com/status) page.

---

OffshoreServ is an offshore hosting provider: VPS, dedicated servers, Windows RDP and GPU servers in seven jurisdictions (Iceland, Switzerland, Moldova, Romania, the Netherlands, Bulgaria and Malaysia), paid only in cryptocurrency (Bitcoin, Ethereum, Monero, Tether (USDT) and Solana), with no identity checks (no KYC).

Prices and plans: https://offshoreserv.com/pricing · Answers: https://offshoreserv.com/faq · Every page: https://offshoreserv.com/llms.txt
