Skip to main content

Wireguard

Deploy Wireguard VPN server.

Hosts in the wireguard group must not run NordVPN: the nordvpn tag actively uninstalls NordVPN from wireguard hosts to avoid the two VPN stacks conflicting over routing/firewall control.

Ansible hosts group: wireguard​

Variables​

OptionTypeDescriptionDefault
cs_wireguard_clusterstringWireguard cluster name{{ cs_cluster_name }}
cs_wireguard_interfacestringWireguard interface namewg0
cs_wireguard_server_ipstringInternal VPN IP of the serverFirst IP of the cluster's VPN CIDR (from Vault)
cs_wireguard_dns_serverslistDNS servers for VPN clientsThe cluster's DNS servers (from Vault)
cs_wireguard_cluster_access_cidrlistList of CIDRs to allow access to via VPNThe 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 .conf file 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 the wireguard package.
  • Writes /etc/wireguard/{{ cs_wireguard_interface }}.conf from the template, with NAT masquerade and UFW forwarding rules on the default interface in PostUp/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, when to is 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 }}.service systemd 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 wireguard package (apt ... purge: true), autoremoves any dependencies left unused, and autocleans the local .deb package 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 plain wireguard tag.

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