VPNSmith
self-host-vpnINFO

Contabo VPS VPN setup step by step (Ubuntu 22.04) — 2026 guide

Complete tutorial: order Contabo VPS S, harden SSH + UFW, install WireGuard, generate peers, test for leaks. 20 minutes, scripts included.

By Eric Gerard · Fondateur · VPNSmith — Spécialiste self-host VPN & VPS GDPR6 min readPhoto via Unsplash

You want a VPN you own. No annual "no-logs" audit, no IP shared with 4,000 strangers, no price doubling when the promo ends. This guide gets WireGuard running on a Contabo VPS in 20 minutes, from signup to the first curl ifconfig.me returning your German VPS IP.

We've been running this exact config in production for 14 months. Zero unplanned tunnel incident, ~200 Mbps stable, ~24 ms latency Paris ↔ Nuremberg.

Step 1 — Order the Contabo VPS

Go to contabo.com via our link /go/contabo and pick:

  • Plan: VPS S Cloud (4 vCPU AMD EPYC, 8 GB RAM, 50 GB NVMe, 200 Mbps guaranteed, 32 TB/mo traffic)
  • Commitment: 24 months (locks the price at 4.99 €/mo, monthly is 8.49 €)
  • OS: Ubuntu 22.04 LTS (24.04 works too, but 22.04 stays more stable for WireGuard packages)
  • Datacenter: Nuremberg (DE) — best France/Europe latency + German GDPR jurisdiction
  • Root password: generate a strong one, stash temporarily in your password manager (delete after creating non-root user)
  • Add-ons: untick everything (Contabo backups not needed, we back up ourselves)

Total upfront ~119 € over 24 months. On activation you receive an email with the public IP and root password. Plan 5 minutes to 4 hours depending on time. Peak hours (6pm-10pm CET, Tuesday/Wednesday) are slowest.

Disclosure: /go/contabo is a sponsored link. If you grab the VPS, we earn a commission at zero cost to you. Our pricing is unaffected. We wouldn't write this guide if we weren't using Contabo ourselves.

Step 2 — First SSH and hardening

In your local terminal:

ssh root@YOUR.PUBLIC.IP
# Accept fingerprint, paste root password

Once connected, update first:

apt update && apt upgrade -y
apt install -y curl wget unzip fail2ban

Create a non-root user (SSH root will be disabled):

adduser eric
# Follow prompts (strong password, optional full name)
usermod -aG sudo eric

Copy your SSH public key from your local MacBook (in another terminal):

ssh-copy-id eric@YOUR.PUBLIC.IP
# If ssh-copy-id is missing: cat ~/.ssh/id_ed25519.pub | ssh root@IP "mkdir -p /home/eric/.ssh && cat >> /home/eric/.ssh/authorized_keys && chown -R eric:eric /home/eric/.ssh && chmod 700 /home/eric/.ssh && chmod 600 /home/eric/.ssh/authorized_keys"

Test non-root login in a new terminal without closing the first:

ssh eric@YOUR.PUBLIC.IP
# Should login with SSH key passphrase only

If that works, disable root SSH and password auth in /etc/ssh/sshd_config (from the root shell):

sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd

Now port 22 stays open but SSH-key only and only for eric. No more root brute-force risk.

Step 3 — UFW firewall

# Still as root
apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp comment 'SSH'
ufw allow 51820/udp comment 'WireGuard'
ufw enable
# Answer y
ufw status verbose

UFW is now active. Log out of the root session and continue as eric with sudo.

Step 4 — Install WireGuard

# As eric with sudo
sudo apt install -y wireguard qrencode iptables-persistent

Generate server keys:

sudo bash -c 'umask 077 && wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub'
sudo cat /etc/wireguard/server.pub
# Note the pubkey, you'll need it for clients

Identify the public interface (could be eth0, ens3, ens18, etc.):

ip route | awk '/default/ {print $5}'
# On Contabo usually eth0 or ens18

Step 5 — Configure /etc/wireguard/wg0.conf

Edit (adjust eth0 if different above):

sudo nano /etc/wireguard/wg0.conf

Paste this (replace SERVER_PRIVATE_KEY with the contents of /etc/wireguard/server.key):

[Interface]
PrivateKey = SERVER_PRIVATE_KEY
Address = 10.66.66.1/24
ListenPort = 51820
MTU = 1420

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Enable ip_forward:

sudo bash -c 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf'
sudo sysctl -p
# Verify: sysctl net.ipv4.ip_forward (should say 1)

Start WireGuard:

sudo systemctl enable --now wg-quick@wg0
sudo wg show
# Should show interface wg0, public key, listening port

Step 6 — Create the first peer (client)

On the server, generate a client key:

sudo mkdir -p /etc/wireguard/clients
sudo bash -c 'umask 077 && wg genkey | tee /etc/wireguard/clients/mac.key | wg pubkey > /etc/wireguard/clients/mac.pub'
sudo cat /etc/wireguard/clients/mac.pub
# Note this pubkey

Add the peer to server wg0.conf:

sudo bash -c 'cat >> /etc/wireguard/wg0.conf <<EOF

[Peer]
# MacBook
PublicKey = CLIENT_MAC_PUBKEY
AllowedIPs = 10.66.66.2/32
EOF'

Reload config without down:

sudo wg syncconf wg0 <(wg-quick strip wg0)

Generate the client file (transfer securely to MacBook/iPhone):

sudo bash -c 'cat > /etc/wireguard/clients/mac.conf <<EOF
[Interface]
PrivateKey = $(cat /etc/wireguard/clients/mac.key)
Address = 10.66.66.2/24
DNS = 9.9.9.9, 149.112.112.112
MTU = 1420

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

For iPhone / Android, encode as QR:

sudo qrencode -t ansiutf8 < /etc/wireguard/clients/mac.conf

Scan this QR from the WireGuard app (App Store, Play Store) and the tunnel is configured.

For macOS / Linux desktop, fetch the .conf locally:

# On your Mac
scp eric@YOUR.PUBLIC.IP:/etc/wireguard/clients/mac.conf ~/Downloads/
# Import from official WireGuard app

Step 7 — Leak tests

Enable the tunnel on your client, then in a browser:

  1. ipleak.net: your public IP must be the Contabo Nuremberg VPS. Geo = Germany. If you still see your French ISP IP → tunnel isn't routing, check client AllowedIPs.
  2. dnsleaktest.com → "Extended test" button: returned DNS must be Quad9 (9.9.9.9) or your VPS. If you see Orange/SFR/Free DNS → DNS leak, check DNS = 9.9.9.9 in client .conf.
  3. browserleaks.com/webrtc: no local IP must leak via WebRTC. If leaking → disable WebRTC in the browser (uBlock Origin → "Prevent WebRTC from leaking").
  4. curl ifconfig.me from client terminal: must return the VPS IP.

All green: your self-host VPN works. Welcome.

Step 8 — Backups and maintenance

The tunnel runs. Good habits:

Total cost over 5 years

To honestly compare with a commercial VPN service:

Option24-month cost5-year cost (extrapolated)Bandwidth
Contabo VPS S Cloud (self-host)119 €~298 €200 Mbps, 32 TB/mo
NordVPN 2 yrs + yearly renewal72 €~600 €"Unlimited" shared
ExpressVPN 1 yr + renewal100 €~750 €"Unlimited" shared

And on the Contabo VPS you can also host: a Pi-hole DNS, Nextcloud, Vaultwarden, a Discord bot, a personal Mastodon instance. The VPN itself uses ~1% of CPU and RAM in normal personal use.

Verdict after 14 months in prod

WireGuard on Contabo VPS S at 4.99 €/mo remains our #1 pick for self-host VPN. 20-min setup, ~200 Mbps stable, ~24 ms latency from Paris, German GDPR jurisdiction. The real downside: email-only support (3-6 h average reply), not ideal during urgent outage.

To get started: Contabo VPS S Cloud via our link /go/contabo. You can cancel within the first 30 days if the setup doesn't fit (we verified on a test account).

Our full Contabo VPS review details strengths and weaknesses across 5 categories (perf, price, RAM/CPU, support, dashboard) with the score breakdown.

★ Datacenter Nuremberg GDPR · ✓ IPv4 dédiée incluse · 200+ Mbps garantis

Get Contabo30 jours satisfait ou remboursé