Skip to content
IaC Templates

Private Network + VPN

Template · Updated Jul 2026
Validated Jul 2026

Private network + VPN

This pattern composes Network and Compute.

What this template does#

Provisions a private network topology with site-to-site VPN connectivity:

  • Private network and subnet for internal workloads
  • VPN gateway instance with WireGuard or IPsec (cloud-init installs WireGuard; see cloud-init and first-boot configuration)
  • Security groups restricting VPN traffic to authorized endpoints
  • Router configuration for VPN tunnel routing
  • Split DNS or route-based forwarding to on-premises networks

Parameters#

ParameterDescriptionDefault
key_nameSSH keypair name (must already exist in your project)required
vpn_protocolVPN protocol (wireguard or ipsec)wireguard
private_cidrPrivate network CIDR10.0.0.0/24
remote_cidrRemote network CIDRNo default
remote_endpointRemote VPN endpoint IPNo default
remote_wireguard_public_keyPublic key of the WireGuard peerNo default
gateway_flavorVPN gateway instance sizes1a.small
image_nameOperating system imageUbuntu-24.04
external_networkExternal network for router gateway and floating IPPublicStatic
vpn_portUDP port WireGuard listens on51820

When to use this pattern#

Connect a private network to remote clients through a WireGuard VPN gateway instance. Choose Development Environment for a bastion-based lab without VPN, or Simple VM with Floating IP when a single public VM is enough.

Estimated cost#

Monthly cost estimate

Pricing calculator ↗

Sized as a custom package on shared vCPU.

Starting template$13.10/mo

Monthly total for the required template above. Use the configurator below to add optional pieces and see the total update.

What each resource is for

Gateway

s1a.small · 2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50/mo

Compute shown per role at custom-package rates ($29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM). The headline above is the billed total: the cheaper of a named plan and the custom package, plus add-ons.

Included in baseline

s1a.small

2 shared vCPU, 2 GiB RAM, 0.5 Gbps

$16.50

Compute + RAM rate basis

2 vCPU + 2 GiB RAM at $29/dedicated vCPU, $7.25/shared vCPU, $1/GiB RAM (regular). Totals apply the flat −$5/mo package promotion.

—

Block storage (20 GiB)

20 GiB at $0.08/GiB/mo

$1.60

Public IP (included)

1 included with the custom package

$0.00

Package promotional discount

Flat −$5.00/mo on the custom package (same promotion as named plans).

$-5.00

Included at no charge

These line items are zero on Quake AI. Many other providers meter them separately.

Data transfer (inbound and outbound)

Unlimited data transfer on every plan; Quake AI does not meter per-GB egress.

AWS, GCP, and Azure meter outbound transfer per GB. DigitalOcean and Hetzner include an allowance on compute plans, then charge overage.

Learn more
$0.00

Private networking

Private networks, subnets, Neutron routers, and security groups are included with the plan.

VPC objects are usually free to create elsewhere, but NAT gateways bill hourly plus per-GB processed. Quake AI uses router SNAT with no separate NAT line item.

$0.00

Control-plane API requests

OpenStack API calls for provisioning and management are included.

Some managed services on other clouds meter API calls or charge for premium control-plane features.

$0.00

Pricing data last validated: . For current rates, check quake.ai/pricing.

Template source#

7 files. Download the zip or expand to copy any file.Download private-network-vpn.zip
Show source (7 files)
main.tfHCL
data "openstack_images_image_v2" "os" {
  name        = var.image_name
  most_recent = true
}

data "openstack_networking_network_v2" "external" {
  name = var.external_network
}

resource "openstack_networking_network_v2" "private" {
  name           = "private-network-vpn-net"
  admin_state_up = true
}

resource "openstack_networking_subnet_v2" "private" {
  name            = "private-network-vpn-subnet"
  network_id      = openstack_networking_network_v2.private.id
  cidr            = var.private_cidr
  ip_version      = 4
  dns_nameservers = ["1.1.1.1", "8.8.8.8"]
}

resource "openstack_networking_router_v2" "main" {
  name                = "private-network-vpn-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "private" {
  router_id = openstack_networking_router_v2.main.id
  subnet_id = openstack_networking_subnet_v2.private.id
}

resource "openstack_networking_secgroup_v2" "gateway" {
  name = "private-network-vpn-sg"
}

resource "openstack_networking_secgroup_rule_v2" "wireguard" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "udp"
  port_range_min    = var.vpn_port
  port_range_max    = var.vpn_port
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.gateway.id
}

resource "openstack_networking_secgroup_rule_v2" "ssh" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 22
  port_range_max    = 22
  remote_ip_prefix  = "0.0.0.0/0"
  security_group_id = openstack_networking_secgroup_v2.gateway.id
}

resource "openstack_networking_secgroup_rule_v2" "private_all" {
  direction         = "ingress"
  ethertype         = "IPv4"
  remote_ip_prefix  = var.private_cidr
  security_group_id = openstack_networking_secgroup_v2.gateway.id
}


resource "openstack_compute_instance_v2" "gateway" {
  name        = "private-network-vpn-gateway"
  flavor_name = var.gateway_flavor
  key_pair    = var.key_name

  metadata = {
    vpn_protocol = var.vpn_protocol
  }

  user_data = templatefile("${path.module}/cloud-init/wireguard.yaml", {
    private_cidr                = var.private_cidr
    vpn_port                    = var.vpn_port
    remote_cidr                 = var.remote_cidr
    remote_endpoint             = var.remote_endpoint
    remote_wireguard_public_key = var.remote_wireguard_public_key
  })

  block_device {
    uuid                  = data.openstack_images_image_v2.os.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 20
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.gateway.id
  }

  lifecycle {
    precondition {
      condition     = var.vpn_protocol == "wireguard"
      error_message = "vpn_protocol must be wireguard for this template."
    }
  }
}

resource "openstack_networking_port_v2" "gateway" {
  name               = "private-network-vpn-gateway-port"
  network_id         = openstack_networking_network_v2.private.id
  security_group_ids = [openstack_networking_secgroup_v2.gateway.id]

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

resource "openstack_networking_floatingip_v2" "gateway" {
  pool = var.external_network
}

resource "openstack_networking_floatingip_associate_v2" "gateway" {
  floating_ip = openstack_networking_floatingip_v2.gateway.address
  port_id     = openstack_networking_port_v2.gateway.id
}
variables.tfHCL
variable "vpn_protocol" {
  type    = string
  default = "wireguard"
}

variable "private_cidr" {
  type    = string
  default = "10.0.0.0/24"
}

variable "remote_cidr" {
  type = string
}

variable "remote_endpoint" {
  type = string
}

variable "remote_wireguard_public_key" {
  type      = string
  sensitive = true
}

variable "gateway_flavor" {
  type    = string
  default = "s1a.small"
}

variable "key_name" {
  description = "Existing SSH keypair name in the project for compute instances"
  type        = string
}

variable "image_name" {
  type    = string
  default = "Ubuntu-24.04"
}

variable "external_network" {
  description = "Shared external network for router gateway and floating IPs; defaults to PublicStatic (persisted FIP / production pattern). Override with PublicEphemeral for ephemeral demos."
  type        = string
  default     = "PublicStatic"
}

variable "vpn_port" {
  type    = number
  default = 51820
}
outputs.tfHCL
output "vpn_endpoint" {
  value = "${openstack_networking_floatingip_v2.gateway.address}:${var.vpn_port}"
}

output "gateway_private_ip" {
  value = openstack_compute_instance_v2.gateway.network[0].fixed_ip_v4
}
versions.tfHCL
terraform {
  required_version = ">= 1.6.0"

  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 2.0"
    }
  }
}

provider "openstack" {}
terraform.tfvars.exampleHCL
# Required
remote_cidr                   = "YOUR_REMOTE_CIDR"
remote_endpoint               = "YOUR_REMOTE_ENDPOINT_HOST_OR_IP"
remote_wireguard_public_key   = "YOUR_REMOTE_WIREGUARD_PUBLIC_KEY"
key_name                      = "YOUR_KEY_NAME"

# vpn_protocol = "wireguard"
# private_cidr = "10.0.0.0/24"
# gateway_flavor = "s1a.small"
# image_name = "Ubuntu-24.04"
# external_network = "PublicStatic"
# vpn_port = 51820
cloud-init/wireguard.yamlYAML
#cloud-config
packages:
  - wireguard-tools
write_files:
  - path: /etc/wireguard/remote.peer.pub
    owner: root:root
    permissions: "0600"
    content: |
      ${remote_wireguard_public_key}
runcmd:
  - |
    set -eux
    umask 077
    mkdir -p /etc/wireguard
    wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
    DEF_IF=$(ip -4 route show default | awk '{print $5; exit}')
    ADDR=$(ip -4 -o addr show "$DEF_IF" | awk '{print $4}' | head -n1)
    PRIV=$(tr -d '\n' < /etc/wireguard/private.key)
    REMOTE_PUB=$(tr -d '[:space:]' < /etc/wireguard/remote.peer.pub)
    cat > /etc/wireguard/wg0.conf <<WGEOF
    [Interface]
    # private_cidr ${private_cidr}
    PrivateKey = $PRIV
    Address = $ADDR
    ListenPort = ${vpn_port}
    PostUp = sysctl -w net.ipv4.ip_forward=1
    PostDown = sysctl -w net.ipv4.ip_forward=0

    [Peer]
    PublicKey = $REMOTE_PUB
    Endpoint = ${remote_endpoint}:${vpn_port}
    AllowedIPs = ${remote_cidr}
    PersistentKeepalive = 25
    WGEOF
    sed -i 's/^[[:space:]]*//' /etc/wireguard/wg0.conf
    chmod 600 /etc/wireguard/wg0.conf /etc/wireguard/private.key
    systemctl enable wg-quick@wg0
    systemctl start wg-quick@wg0
README.mdMarkdown
# Private Network VPN

Site-to-site WireGuard VPN with a gateway instance on your external network and private addressing for resources behind the gateway.


**Network class:** production — `external_network` defaults to `PublicStatic` for persisted floating IPs and multi-tier stacks; override with `PublicEphemeral` for ephemeral demos.

## Prerequisites

- OpenTofu >= 1.6.0 or Terraform >= 1.6.0
- Quake AI account with OpenStack credentials
- Remote site details: peer CIDR, public endpoint, and WireGuard public key for the peer
- UDP access to the chosen VPN port (default WireGuard) from the remote site

## Usage

1. Clone or copy this template directory
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in your values (treat `remote_wireguard_public_key` as sensitive)
3. Source your OpenStack credentials: `source openrc.sh`
4. Initialize: `tofu init`
5. Preview: `tofu plan`
6. Apply: `tofu apply`

## Variables

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `remote_cidr` | string | yes | n/a | CIDR of the remote network to route over the tunnel |
| `remote_endpoint` | string | yes | n/a | Remote peer public hostname or IP |
| `remote_wireguard_public_key` | string | yes | n/a | Remote peer WireGuard public key (sensitive) |
| `key_name` | string | yes | n/a | Existing SSH keypair name in the project for compute instances |
| `vpn_protocol` | string | no | `wireguard` | VPN protocol identifier |
| `private_cidr` | string | no | `10.0.0.0/24` | Local private network CIDR behind the gateway |
| `gateway_flavor` | string | no | `s1a.small` | Flavor for the VPN gateway |
| `image_name` | string | no | `Ubuntu-24.04` | Boot image name |
| `external_network` | string | no | `PublicStatic` | Persisted FIP / production default; override with `PublicEphemeral` for demos |
| `vpn_port` | number | no | `51820` | UDP port for WireGuard |

## Documentation

Full documentation: [Private network VPN template](/docs/automation/templates/private-network-vpn)
Resources, parameters, and variables
Provisions
Parameterized by
Variables
  • vpn_protocol="wireguard"
  • private_cidr="10.0.0.0/24"
  • remote_cidrrequired
  • remote_endpointrequired
  • remote_wireguard_public_keyrequired
  • gateway_flavor="s1a.small"
  • key_namerequired
  • image_name="Ubuntu-24.04"
  • external_network="PublicStatic"
  • vpn_port=51820

Customize this pattern#

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Last validated: 07.07.2026

Was this page helpful?