Clean, minimal, self-hosted WireGuard setup with:
- Full tunnel support
- NAT for internet access
- Multi-user capability
- Per-user access control via firewall
0. Assumptions
- Public interface:
eth0 - VPN subnet:
10.10.10.0/24 - Server is publicly reachable
Replace eth0 with your real interface name if needed, such as ens18, ens3, or enp1s0.
1. Install WireGuard
apt update
apt install wireguard
2. Enable IP Forwarding
sysctl -w net.ipv4.ip_forward=1
Persist it:
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p
3. Generate Keys
cd /etc/wireguard
wg genkey | tee server.key | wg pubkey > server.pub
wg genkey | tee client.key | wg pubkey > client.pub
chmod 600 *.key
4. Server Configuration
Create:
nano /etc/wireguard/wg0.conf
[Interface]
Address = 10.10.10.1/24
PrivateKey = <contents of server.key>
ListenPort = 51820
# NAT + forwarding
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -A FORWARD -o wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -o wg0 -j ACCEPT
[Peer]
PublicKey = <contents of client.pub>
AllowedIPs = 10.10.10.2/32
Notes:
Address = 10.10.10.1/24is the server’s VPN IP.AllowedIPs = 10.10.10.2/32means that client owns only10.10.10.2.- Do not reuse keys between devices.
5. Start WireGuard
wg-quick up wg0
systemctl enable wg-quick@wg0
Check status:
wg show
ip addr show wg0
6. Client Configuration
Use this on Windows, Linux, or macOS:
[Interface]
Address = 10.10.10.2/24
PrivateKey = <contents of client.key>
DNS = 1.1.1.1
[Peer]
PublicKey = <contents of server.pub>
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Notes:
AllowedIPs = 0.0.0.0/0sends all client traffic through the VPN.- If you only want access to specific private networks, replace
0.0.0.0/0with those subnets.
Example split tunnel:
AllowedIPs = 10.10.10.0/24, 192.168.50.0/24
7. Open Firewall Port
If using UFW:
ufw allow 51820/udp
Or raw iptables:
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
8. Test Connection
From the client:
curl ip.me
Expected result:
- If full tunnel is enabled, this should show the server’s public IP.
Also test:
ping 10.10.10.1
9. Add Additional Users
Generate new keys:
wg genkey | tee user2.key | wg pubkey > user2.pub
chmod 600 user2.key
Add to server config:
[Peer]
PublicKey = <contents of user2.pub>
AllowedIPs = 10.10.10.3/32
Apply changes:
wg syncconf wg0 <(wg-quick strip wg0)
Client config example for user2:
[Interface]
Address = 10.10.10.3/24
PrivateKey = <contents of user2.key>
DNS = 1.1.1.1
[Peer]
PublicKey = <contents of server.pub>
Endpoint = YOUR_SERVER_IP:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
10. Access Control and Network Design
WireGuard handles authentication by key.
WireGuard does not decide what a connected peer is allowed to reach.
That is handled by your firewall, typically with iptables or nftables.
Think of it like this:
- WireGuard key + peer IP = identity
- Firewall rules = authorization
Important
The examples below use:
10.10.10.2= full access user10.10.10.3= restricted user10.10.10.4= internet-only user
Adjust these to your actual peer IP assignments.
Scenario A: Public server only, no private networks behind it
This is the simplest case.
The server only has:
eth0facing the internetwg0for VPN clients
There is no LAN behind the server. You just want clients to:
- connect to the server
- route internet traffic through it
- maybe reach services running on the server itself
Full tunnel user
iptables -A FORWARD -i wg0 -s 10.10.10.2 -o eth0 -j ACCEPT
iptables -A FORWARD -o wg0 -d 10.10.10.2 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
Internet-only restricted user
iptables -A FORWARD -i wg0 -s 10.10.10.4 -o eth0 -j ACCEPT
iptables -A FORWARD -o wg0 -d 10.10.10.4 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wg0 -s 10.10.10.4 -j DROP
That last rule says:
- allow that peer out to the internet
- drop any other forwarded access attempt
If the user also needs to reach services hosted directly on the VPN server itself, that is controlled by INPUT rules, not FORWARD.
Example: allow SSH from one VPN peer to the server:
iptables -A INPUT -i wg0 -s 10.10.10.3 -p tcp --dport 22 -j ACCEPT
Example: block all other VPN clients from SSH to the server:
iptables -A INPUT -i wg0 -p tcp --dport 22 -j DROP
Scenario B: Server has private networks behind it
Use this if the Debian server can route to private networks such as:
192.168.1.0/2410.0.0.0/24172.16.50.0/24
In this case, your VPN clients can be selectively allowed to those networks.
Full access user to all routed private networks
iptables -A FORWARD -i wg0 -s 10.10.10.2 -j ACCEPT
That is broad. It allows that peer to anything the server can route to.
Restricted user to a single host
Example: only allow 10.10.10.3 to reach 192.168.1.50
iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.50 -j ACCEPT
iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
Restricted user to a whole subnet
Example: only allow 10.10.10.3 to reach 192.168.1.0/24
iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
Internet plus one private subnet
Example: allow 10.10.10.3 to:
- use the server as an internet gateway
- access
192.168.1.0/24 - nothing else
iptables -A FORWARD -i wg0 -s 10.10.10.3 -d 192.168.1.0/24 -j ACCEPT
iptables -A FORWARD -i wg0 -s 10.10.10.3 -o eth0 -j ACCEPT
iptables -A FORWARD -o wg0 -d 10.10.10.3 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wg0 -s 10.10.10.3 -j DROP
Client-side route restriction
You should also tighten the client config.
If a peer should only reach one private subnet, do not send all traffic through the tunnel.
Example client config:
AllowedIPs = 10.10.10.0/24, 192.168.1.0/24
That gives split-tunnel access only to:
- the VPN subnet
- the private subnet behind the server
11. Remove or Revoke Access
Remove a peer immediately:
wg set wg0 peer <public_key> remove
Or edit wg0.conf and reload:
wg syncconf wg0 <(wg-quick strip wg0)
12. Common Issues
- IP forwarding not enabled
- Wrong public interface name
- Firewall blocking UDP 51820
- NAT rule missing
- Server not publicly reachable
- Client
AllowedIPstoo broad or too narrow - Client clock wildly wrong
- Forgot
PersistentKeepalive = 25for clients behind NAT
13. Key Concepts
- Each peer should have its own keypair
- No passwords or usernames in WireGuard
- One device should not share another device’s key
- VPN peer IPs should be treated like identities
- Firewall rules decide what each peer can reach
14. Recommended Next Step
This walkthrough uses iptables because it is straightforward and familiar.
For a cleaner long-term setup on Debian 13, consider moving the policy to nftables, especially if you will have:
- multiple users
- different access classes
- persistent firewall rules you want managed sanely