Wireguard
Deploy Wireguard VPN server.
Hosts in the
wireguardgroup must not run NordVPN: thenordvpntag actively uninstalls NordVPN fromwireguardhosts to avoid the two VPN stacks conflicting over routing/firewall control.
Ansible hosts group: wireguard
Variables
| Option | Type | Description | Default |
|---|---|---|---|
cs_wireguard_cluster | string | Wireguard cluster name | {{ cs_cluster_name }} |
cs_wireguard_interface | string | Wireguard interface name | wg0 |
cs_wireguard_server_ip | string | Internal VPN IP of the server | First IP of the cluster's VPN CIDR (from Vault) |
cs_wireguard_dns_servers | list | DNS servers for VPN clients | The cluster's DNS servers (from Vault) |
cs_wireguard_cluster_access_cidr | list | List of CIDRs to allow access to via VPN | The cluster CIDR (from Vault) |
Vault configurations
- key:
{{ cs_project_code }}/application-deployer/clusters/{{ cs_wireguard_cluster }}/hosts/{{ inventory_hostname }}/apps/wireguard/config
{
"port": "(int) WireGuard listen port.",
"peers": {
"<peer name>": {
"to": "(Optional) Email address to send the peer config to."
}
}
}
Each peer name becomes the interface name on the client side, and the peer config is mailed as <peer name>.conf.
To work with wg-quick and the WireGuard mobile apps, a peer name must:
- be 1 to 15 characters long and use only letters, digits, and
_ = + . - - start with a letter or digit, so the
.conffile stays visible in phone file managers - differ from every other peer name on the host when case is ignored, since phone storage is case-insensitive
The wireguard tag fails before touching the host if any peer name breaks these rules.
Generated peer configs are stored at:
- key:
{{ cs_project_code }}/application-deployer/clusters/{{ cs_wireguard_cluster }}/hosts/{{ inventory_hostname }}/apps/wireguard/generated/peer-<peer name>
Tasks
Install (wireguard)
Every run regenerates the server key pair and every peer's key pair.
- Validates the peer names (see above), then looks up the server's public IP (
ipify) and default network interface. - Stops
wg-quick@{{ cs_wireguard_interface }}.service(tolerating hosts where it doesn't exist yet) and installs thewireguardpackage. - Writes
/etc/wireguard/{{ cs_wireguard_interface }}.conffrom the template, with NAT masquerade and UFW forwarding rules on the default interface inPostUp/PreDown. - For each peer: generates a key pair, appends a
[Peer]block with the next VPN IP after the server's, builds the client config, stores it in Vault, and, whentois set, mails it through the shared SMTP endpoint. - Allows the listen port in UFW (UDP), enables
net.ipv4.ip_forward, then restarts and enables the service.
Cleanup (dangerously_cleanup_wireguard)
Destructive. Only runs when this exact tag is passed explicitly (it is intentionally excluded from the
plain wireguard tag) and permanently deletes Wireguard's own data on the host:
- Stops and disables the
wg-quick@{{ cs_wireguard_interface }}.servicesystemd unit; tolerates hosts where Wireguard was never installed (the unit doesn't exist) by ignoring the module's "Could not find the requested service" error and failing on anything else. - Purges the
wireguardpackage (apt ... purge: true), autoremoves any dependencies left unused, and autocleans the local.debpackage cache. - Deletes the UFW allow rule opened for Wireguard's configured listen port.
- Deletes
/etc/wireguard(the interface config and generated keys).
Generated peer configs stored in Vault are left untouched.
Tags
wireguard: Deploy and configure Wireguard.dangerously_cleanup_wireguard: Destructive. Stops and disables the Wireguard service, purges the package, closes its UFW port, and deletes/etc/wireguard. Never included by the plainwireguardtag.
CI workflow
The workflow (wireguard.yml) runs on any runners and runs playbook.yml --tags "wireguard".
Deployment
uv sync --all-extras --all-packages --no-progress
uv --offline run --no-sync --no-progress ansible-galaxy install -r requirements.yml
uv --offline run --no-sync --no-progress ansible-playbook playbook.yml --tags wireguard