Public NotebookLast updated

WireGuard Private VPN for PostgreSQL on Ubuntu

This guide documents how to set up a private WireGuard VPN so a Windows computer can securely connect to a PostgreSQL database running on an Ubuntu server without exposing PostgreSQL to the public internet.

WireGuard Private VPN for PostgreSQL on Ubuntu

This guide documents how to set up a private WireGuard VPN so a Windows computer can securely connect to a PostgreSQL database running on an Ubuntu server without exposing PostgreSQL to the public internet.

neon-ubuntu-logo.webp

Goal

We want this setup:

text
Windows PC
   |
   | WireGuard VPN
   v
Ubuntu Server
   |
   PostgreSQL

PostgreSQL should only accept connections from:

  • Your WireGuard VPN network, for example 10.100.0.0/24
  • Your application server IP, for example 157.180.16.61

PostgreSQL should not be open to the whole internet.


1. Install WireGuard on Ubuntu Server

Run this on your Ubuntu database server:

bash
sudo apt update
sudo apt install wireguard -y

2. Generate Server Keys

Run:

bash
wg genkey | tee server\_private.key | wg pubkey > server\_public.key

View the keys:

bash
cat server\_private.key
cat server\_public.key

Keep these safe.

  • server\_private.key goes into the Ubuntu server config.
  • server\_public.key goes into the Windows WireGuard config.

3. Create the WireGuard Server Config

Edit the server config:

bash
sudo nano /etc/wireguard/wg0.conf

Add this:

ini
[Interface]
Address = 10.100.0.1/24
ListenPort = 51820
PrivateKey = YOUR\_SERVER\_PRIVATE\_KEY
PostUp = sysctl -w net.ipv4.ip\_forward=1
PostDown = sysctl -w net.ipv4.ip\_forward=0

Replace:

text
YOUR\_SERVER\_PRIVATE\_KEY

with the value from:

bash
cat server\_private.key

Important: do not wrap the key in quotes.

Correct:

ini
PrivateKey = abc123example=

Wrong:

ini
PrivateKey = "abc123example="

4. Enable IP Forwarding Permanently

Edit:

bash
sudo nano /etc/sysctl.conf

Find this line:

text
#net.ipv4.ip\_forward=1

Change it to:

text
net.ipv4.ip\_forward=1

Apply it:

bash
sudo sysctl -p

5. Open the WireGuard Firewall Port

Allow WireGuard through UFW:

bash
sudo ufw allow 51820/udp
sudo ufw reload

Check:

bash
sudo ufw status

You should see:

text
51820/udp ALLOW Anywhere

If your VPS provider has a cloud firewall, also allow:

text
Protocol: UDP
Port: 51820
Source: 0.0.0.0/0

6. Start WireGuard on Ubuntu

Set secure permissions:

bash
sudo chmod 600 /etc/wireguard/wg0.conf

Enable and start WireGuard:

bash
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Check status:

bash
sudo systemctl status wg-quick@wg0

You want to see:

text
Active: active (exited)

Check the WireGuard interface:

bash
ip addr show wg0

Expected:

text
inet 10.100.0.1/24 scope global wg0

Check WireGuard:

bash
sudo wg

7. Install WireGuard on Windows

Install WireGuard for Windows from:

text
https://www.wireguard.com/install/

Open WireGuard and click:

text
Add Tunnel > Add Empty Tunnel

WireGuard will automatically generate a Windows private key and public key.

Copy the Windows public key. This needs to be added to the Ubuntu server.


8. Add the Windows Client to the Ubuntu Server

On Ubuntu, edit:

bash
sudo nano /etc/wireguard/wg0.conf

Add this under the existing [Interface] section:

ini
[Peer]
PublicKey = YOUR\_WINDOWS\_PUBLIC\_KEY
AllowedIPs = 10.100.0.2/32

Replace:

text
YOUR\_WINDOWS\_PUBLIC\_KEY

with the public key shown in the WireGuard Windows app.

Your full Ubuntu config should look similar to this:

ini
[Interface]
Address = 10.100.0.1/24
ListenPort = 51820
PrivateKey = YOUR\_SERVER\_PRIVATE\_KEY
PostUp = sysctl -w net.ipv4.ip\_forward=1
PostDown = sysctl -w net.ipv4.ip\_forward=0

[Peer]
PublicKey = YOUR\_WINDOWS\_PUBLIC\_KEY
AllowedIPs = 10.100.0.2/32

Restart WireGuard:

bash
sudo systemctl restart wg-quick@wg0

Check status:

bash
sudo systemctl status wg-quick@wg0

9. Configure the Windows WireGuard Tunnel

In the WireGuard Windows app, edit the tunnel config so it looks like this:

ini
[Interface]
PrivateKey = YOUR\_WINDOWS\_PRIVATE\_KEY
Address = 10.100.0.2/24

[Peer]
PublicKey = YOUR\_SERVER\_PUBLIC\_KEY
AllowedIPs = 10.100.0.0/24
Endpoint = YOUR\_SERVER\_PUBLIC\_IP:51820
PersistentKeepalive = 25

Replace:

text
YOUR\_WINDOWS\_PRIVATE\_KEY

with the private key generated by the Windows app.

Replace:

text
YOUR\_SERVER\_PUBLIC\_KEY

with the value from Ubuntu:

bash
cat server\_public.key

Replace:

text
YOUR\_SERVER\_PUBLIC\_IP

with your Ubuntu server public IP address.

Important: the Windows config must include this line:

ini
Address = 10.100.0.2/24

Without it, Windows may create the tunnel but assign a broken 169.254.x.x address, causing ping and database access to fail.


10. Activate and Test the VPN

In Windows WireGuard, click:

text
Activate

On Ubuntu, run:

bash
sudo wg

You should see a handshake:

text
latest handshake: 3 seconds ago
transfer: ... received, ... sent

From Windows PowerShell, test:

powershell
ping 10.100.0.1

From Ubuntu, test:

bash
ping 10.100.0.2

If both work, the VPN is working.


11. Troubleshooting WireGuard

Check WireGuard service logs

bash
sudo journalctl -xeu wg-quick@wg0.service --no-pager

Common error: PostUp formatting

If you see this:

text
Line unrecognized: \`PostUp=sysctl-wnet.ipv4.ip\_forward=1'

Your config has lost spaces.

Wrong:

ini
PostUp=sysctl-wnet.ipv4.ip\_forward=1

Correct:

ini
PostUp = sysctl -w net.ipv4.ip\_forward=1

Check if WireGuard is listening

bash
sudo ss -ulpn | grep 51820

Check UFW

bash
sudo ufw status

You should see:

text
51820/udp ALLOW Anywhere

Check handshake

bash
sudo wg

If you do not see:

text
latest handshake

then the client is not reaching the server.

Check:

  • Windows endpoint IP is correct
  • Windows peer public key is the server public key
  • Ubuntu peer public key is the Windows public key
  • UDP 51820 is allowed in UFW
  • UDP 51820 is allowed in your VPS provider firewall

12. Lock PostgreSQL to VPN and App Server Only

Once the VPN works, lock down PostgreSQL.

Edit PostgreSQL host-based authentication:

bash
sudo nano /etc/postgresql/\*/main/pg\_hba.conf

Remove or comment out public rules like:

text
host all all 0.0.0.0/0 md5
host all all 0.0.0.0/0 scram-sha-256
host all all ::/0 md5
host all all ::/0 scram-sha-256

Add:

text
host    all    all    10.100.0.0/24       scram-sha-256
host    all    all    157.180.16.61/32    scram-sha-256

Restart PostgreSQL:

bash
sudo systemctl restart postgresql

13. Restrict PostgreSQL with UFW

Allow PostgreSQL from the VPN network:

bash
sudo ufw allow from 10.100.0.0/24 to any port 5432 proto tcp

Check numbered firewall rules:

bash
sudo ufw status numbered

If you see a public PostgreSQL rule like this:

text
5432/tcp ALLOW Anywhere

remove it:

bash
sudo ufw delete RULE\_NUMBER

Example:

bash
sudo ufw delete 5

Verify:

bash
sudo ufw status

You should have something like:

text
22/tcp       ALLOW Anywhere
80/tcp       ALLOW Anywhere
443/tcp      ALLOW Anywhere
51820/udp    ALLOW Anywhere
5432/tcp     ALLOW 10.100.0.0/24
5432/tcp     ALLOW 157.180.16.61

You should not have:

text
5432/tcp ALLOW Anywhere

14. Connect to PostgreSQL from Windows

Use the VPN IP, not the public server IP.

Connection details:

text
Host: 10.100.0.1
Port: 5432
Database: your\_database\_name
Username: your\_database\_user
Password: your\_database\_password

Using psql:

powershell
psql -h 10.100.0.1 -p 5432 -U your\_database\_user -d your\_database\_name

In tools such as pgAdmin or DBeaver, use:

text
Host: 10.100.0.1
Port: 5432

15. Optional: PostgreSQL Listen Address

You can leave PostgreSQL as:

ini
listen\_addresses = '\*'

as long as UFW and pg\_hba.conf are correctly restricted.

For a stricter setup, you can edit:

bash
sudo nano /etc/postgresql/\*/main/postgresql.conf

Set:

ini
listen\_addresses = '10.100.0.1'

Then restart PostgreSQL:

bash
sudo systemctl restart postgresql

Only do this if PostgreSQL does not also need to listen on another private/public interface for your application server.


16. Adding More Devices Later

For each new client device:

  1. Install WireGuard.
  2. Generate a new tunnel.
  3. Copy the new device public key.
  4. Add a new [Peer] block on Ubuntu.
  5. Give the device a new VPN IP.

Example second device:

Ubuntu server:

ini
[Peer]
PublicKey = SECOND\_DEVICE\_PUBLIC\_KEY
AllowedIPs = 10.100.0.3/32

Second device config:

ini
[Interface]
PrivateKey = SECOND\_DEVICE\_PRIVATE\_KEY
Address = 10.100.0.3/24

[Peer]
PublicKey = YOUR\_SERVER\_PUBLIC\_KEY
AllowedIPs = 10.100.0.0/24
Endpoint = YOUR\_SERVER\_PUBLIC\_IP:51820
PersistentKeepalive = 25

Restart WireGuard on Ubuntu:

bash
sudo systemctl restart wg-quick@wg0

17. Final Security Checklist

Run these checks after everything is configured.

WireGuard is running

bash
sudo systemctl status wg-quick@wg0
sudo wg

VPN IP exists on Ubuntu

bash
ip addr show wg0

Expected:

text
inet 10.100.0.1/24

PostgreSQL is not public

bash
sudo ufw status

Confirm there is no:

text
5432/tcp ALLOW Anywhere

PostgreSQL accepts VPN connections

From Windows:

powershell
psql -h 10.100.0.1 -U your\_database\_user -d your\_database\_name

VPN handshake exists

On Ubuntu:

bash
sudo wg

Expected:

text
latest handshake: ...

Summary

You now have:

  • A private WireGuard VPN server on Ubuntu
  • A Windows client connected to the VPN
  • A private VPN subnet: 10.100.0.0/24
  • Ubuntu VPN IP: 10.100.0.1
  • Windows VPN IP: 10.100.0.2
  • PostgreSQL accessible via 10.100.0.1:5432
  • Public PostgreSQL access removed
  • PostgreSQL restricted to VPN clients and your application server