Connect Django Servers Over a WireGuard Private Network

Use WireGuard to connect Django app servers to PostgreSQL, Redis, and workers across hosts or providers: generate keys, configure wg0 interfaces, bind databases to the tunnel address, restrict firewalls, and keep Django connection settings on private IPs.

Problem statement

Your Django deployment has grown to several servers, perhaps a web server, a database server, and a worker, at a provider without private networking between them, or across two providers. Exposing PostgreSQL and Redis on public IPs with firewall allowlists is fragile and risky. WireGuard creates an encrypted private network between hosts with a few lines of configuration, so Django connects to 10.10.0.2:5432 and nothing listens publicly.

Django over WireGuardWeb serverwg0 10.10.0.1Encrypted tunnelUDP 51820DB serverwg0 10.10.0.2Services bindprivate IP onlyDatabases listen only on tunnel addresses, so nothing sensitive is public.
  1. Install WireGuard on every host and generate a key pair per host.
  2. Give each host a wg0 address in a private range (for example 10.10.0.0/24) and list peers with public keys and endpoints.
  3. Bind PostgreSQL and Redis to the WireGuard address, and add pg_hba.conf rules for the tunnel subnet.
  4. Point Django's DATABASE_URL and REDIS_URL at the private addresses.
  5. Firewall: allow UDP 51820 between hosts; block database ports on public interfaces.

Step-by-step solution

1. Install and generate keys

On each host:

sudo apt-get install -y wireguard
wg genkey | sudo tee /etc/wireguard/private.key | wg pubkey | sudo tee /etc/wireguard/public.key
sudo chmod 600 /etc/wireguard/private.key

Collect each host's public key; private keys never leave their host.

2. Configure interfaces

On the database server (10.10.0.2):

# /etc/wireguard/wg0.conf
[Interface]
Address = 10.10.0.2/24
ListenPort = 51820
PrivateKey = <db private key>

[Peer]
# web-1
PublicKey = <web-1 public key>
AllowedIPs = 10.10.0.1/32
Endpoint = 203.0.113.10:51820

[Peer]
# worker-1
PublicKey = <worker-1 public key>
AllowedIPs = 10.10.0.3/32
Endpoint = 203.0.113.12:51820

The web server's file mirrors this with its own address and the database as a peer. Bring the interface up and enable it at boot:

sudo systemctl enable --now wg-quick@wg0
sudo wg show
hosts on the private networkwg0 addressListens privately onweb-110.10.0.1Gunicorn (via LB)web-210.10.0.4Gunicorn (via LB)worker-110.10.0.3nothingdb-110.10.0.2Postgres 5432, Redis 6379Only the database host exposes ports, and only on its tunnel addressdjango-deployment.com
Keep the plan in your configuration repository so new hosts get unused addresses.

3. Bind services to the tunnel

# postgresql.conf
listen_addresses = 'localhost,10.10.0.2'
# pg_hba.conf
hostssl  shop  shop  10.10.0.0/24  scram-sha-256
# redis.conf
bind 127.0.0.1 10.10.0.2
requirepass <password>

Start PostgreSQL after WireGuard so the address exists: add After=wg-quick@wg0.service and Requires=wg-quick@wg0.service to a drop-in for the PostgreSQL unit.

4. Point Django at private addresses

DATABASE_URL=postgres://shop:...@10.10.0.2:5432/shop?sslmode=require
REDIS_URL=redis://:...@10.10.0.2:6379/0

WireGuard already encrypts traffic; keeping PostgreSQL TLS on as well protects against misrouting and satisfies compliance requirements that ask for encryption at the application layer.

5. Firewall rules

sudo ufw allow from 203.0.113.0/24 to any port 51820 proto udp
sudo ufw deny 5432
sudo ufw deny 6379
sudo ufw allow in on wg0

Check from the internet that 5432 and 6379 are closed, and from the web server that psql -h 10.10.0.2 works.

how app servers reach the databasePublic IP + allowlist✕port visible to scanners✕allowlist drifts as IPs change✕TLS is the only protectionWireGuard tunnel✓no public database port✓peers identified by keys✓works across providersKey-based peers replace IP allowlistsdjango-deployment.com
If your provider offers a private network, use it; WireGuard fills the gap when it does not.

Explanation

WireGuard is a kernel-level VPN that authenticates peers with public keys and encrypts traffic with modern cryptography. Each host gets a virtual interface with a private address; AllowedIPs defines which addresses each peer may use, acting as both routing table and access control. Binding PostgreSQL and Redis only to those addresses removes them from the public internet entirely, so Django's connections travel over the tunnel.

Edge cases / notes

  • Many hosts. Full-mesh configurations grow quickly; tools like Tailscale, Headscale, or Netmaker manage keys and peers for you.
  • MTU. Tunnels reduce the packet size available; if large queries hang, lower the interface MTU (for example MTU = 1380).
  • Docker. Containers can reach the tunnel through the host's routing; publish database containers only on the WireGuard address.

Next steps

FAQ

Is WireGuard fast enough for database traffic?

Yes. It runs in the kernel with low overhead, suitable for PostgreSQL and Redis traffic.

Do I still need PostgreSQL TLS over WireGuard?

It is optional but recommended as defense in depth and for compliance.

How do I add a new web server?

Generate keys, give it an unused address, add it as a peer on the database host, and reload WireGuard.

Should I use Tailscale instead?

Tailscale builds on WireGuard and manages keys and peers automatically, which is easier for more than a few hosts.

Keep going