This Ansible role can deploy a Wireguard VPN server on Debian-based hosts.
The role is designed for on-premises, self-hosted deployments.
It is idempotent.
Stack components:
- Wireguard kernel module and user tools
- Systemd for service management
- UFW firewall integration
- Optional support for vault-managed private keys
- NAT / IP forwarding configuration
- Configurable peers via Ansible variables
The role aims for simplicity, security, and maintainability - no manual configuration steps required after initial deployment.
ds_ufw: Set up the incoming firewall rules separately. The role handles only the postrouting and routing rules.
Example:
ufw_rules:
- { rule: allow, port: "51820", proto: udp, comment: "Wireguard VPN" }
- { rule: allow, port: "22", proto: tcp, src: "10.100.200.0/24", comment: "SSH from VPN" }- Debian 12+ (Bookworm) or compatible.
- Sudo access.
- Ports open on UDP
{{ wg_vpn_port }}(default 51820).
- Installs Wireguard package.
- Ensures
/etc/wireguarddirectory exists with proper permissions. - Checks for existing private key file.
- Generates a new key pair if no key exists and no vault-provided key is defined.
- Writes vault-provided private key if defined.
- Enables IPv4 forwarding in the kernel and UFW.
- Configures UFW NAT postrouting and routing rules.
- Reads the private key and deploys a Wireguard configuration from a Jinja2 template.
- Enables and starts the Wireguard service via Systemd.
The following variables must be defined, typically in group_vars or your inventory:
| Variable | Description |
|---|---|
wg_vpn_network | VPN network/subnet for Wireguard (e.g. 10.200.200.1/24). |
wg_vpn_interface | Wireguard interface name (default: wg0). |
wg_vpn_port | UDP port Wireguard listens on (default: 51820). |
wg_network_interface | Physical interface for NAT (e.g. enp1s0). |
vault_wg_private_key | Optional SOPS/vault-provided server private key. |
wg_peers | Dictionary of peers with keys public_key and allowed_ips. |
Example peer definition:
wg_peers:
iron:
public_key: e2V40zdPiX43lqOamcoEI8J10uKaXWBeKwf+spWDWgc=
allowed_ips: 10.200.200.2/32- name: Deploy Wireguard VPN server
hosts: vpn-servers
become: true
roles:
- role: ds-wireguard
vars:
wg_vpn_network: 10.100.200.1/24
wg_vpn_interface: wg0
wg_vpn_port: 51820
wg_network_interface: enp1s0
vault_wg_private_key: "{{ vault_wg_private_key }}" # Optional
wg_peers:
iron:
public_key: e2V40zdPiX43lqOamcoEI8J10uKaXWBeKwf+spWDWgc=
allowed_ips: 10.100.200.2/32The role is fully idempotent.
Re-running it will:
- Skip key generation if a private key exists.
- Skip firewall rules if already applied.
- Update Wireguard configuration only if variables or template change.
- Restart Wireguard only when the configuration changes.
- Ensure the physical network interface for NAT (
wg_network_interface) is correct. - UFW must be installed and enabled on the host.
- Vault-managed keys provide deterministic server identity across deployments.
- Peer definitions automatically generate the Wireguard [Peer] blocks in the configuration.
- IPv4 forwarding must be enabled for routing between VPN and LAN.
MIT
[ Fear the Silence. Fear the Switch. ]