Cloudflare Docs
Cloudflare Zero Trust
Visit Cloudflare Zero Trust on GitHub
Set theme to dark (⇧+D)

Deploy Tunnels with Ansible and Terraform

Ansible is a software tool that enables at scale management of infrastructure. Ansible is agentless — all it needs to function is the ability to SSH to the target and Python installed on the target.

Ansible works alongside Terraform to streamline the Cloudflare Tunnel setup process. In this guide, you will use Terraform to deploy an SSH server on Google Cloud and create a locally-managed tunnel that makes the server available over the Internet. Terraform will automatically run an Ansible playbook that installs and configures cloudflared on the server.

​​ Prerequisites

To complete the steps in this guide, you will need:

​​ 1. Install Ansible

Refer to the Ansible installation instructions.

​​ 2. (Optional) Create an SSH key pair

Terraform and Ansible require an unencrypted SSH key to connect to the GCP server. If you do not already have a key, you can generate one as follows:

  1. Open a terminal and type the following command:

    $ ssh-keygen -t rsa -f ~/.ssh/gcp_ssh -C <username in GCP>
  2. When prompted for a passphrase, press Enter twice to leave it blank. Terraform cannot decode encrypted private keys.

Two files will be generated: gcp_ssh which contains the private key, and which contains the public key.

​​ 3. Create a configuration directory

  1. Create a folder for your Terraform and Ansible configuration files:

    $ mkdir ansible-tunnel
  2. Change to the new directory:

    $ cd ansible-tunnel

​​ 4. Create Terraform configuration files

​​ Define input variables

The following variables will be passed into your GCP and Cloudflare configuration.

  1. In your configuration directory, create a .tf file:

    $ touch
  2. Open the file in a text editor and copy and paste the following:
    # GCP variables
    variable "gcp_project_id" {
    description = "Google Cloud Platform (GCP) project ID"
    type = string
    variable "zone" {
    description = "Geographical zone for the GCP VM instance"
    type = string
    variable "machine_type" {
    description = "Machine type for the GCP VM instance"
    type = string
    # Cloudflare variables
    variable "cloudflare_zone" {
    description = "Domain used to expose the GCP VM instance to the Internet"
    type = string
    variable "cloudflare_zone_id" {
    description = "Zone ID for your domain"
    type = string
    variable "cloudflare_account_id" {
    description = "Account ID for your Cloudflare account"
    type = string
    sensitive = true
    variable "cloudflare_email" {
    description = "Email address for your Cloudflare account"
    type = string
    sensitive = true
    variable "cloudflare_token" {
    description = "Cloudflare API token created at"
    type = string
    sensitive = true

​​ Assign values to the variables

  1. In your configuration directory, create a .tfvars file:

    $ touch terraform.tfvars

    Terraform will automatically use these variables if the file is named terraform.tfvars, otherwise the variable file will need to be manually passed in.

  2. Add the following variables to terraform.tfvars. Be sure to modify the example with your own values.

    cloudflare_zone = ""
    cloudflare_zone_id = "023e105f4ecef8ad9ca31a8372d0c353"
    cloudflare_account_id = "372e67954025e0ba6aaa6d586b9e0b59"
    cloudflare_email = ""
    cloudflare_token = "y3AalHS_E7Vabk3c3lX950F90_Xl7YtjSlzyFn_X"
    gcp_project_id = "testvm-123"
    zone = "us-central1-a"
    machine_type = "e2-medium"

​​ Configure Terraform providers

You will need to declare the providers used to provision the infrastructure.

  1. In your configuration directory, create a .tf file:

    $ touch
  2. Add the following providers to The random provider is used to generate a tunnel secret.
    terraform {
    required_providers {
    cloudflare = {
    source = "cloudflare/cloudflare"
    google = {
    source = "hashicorp/google"
    random = {
    source = "hashicorp/random"
    required_version = ">= 0.13"
    # Providers
    provider "cloudflare" {
    api_token = var.cloudflare_token
    provider "google" {
    project = var.gcp_project_id
    provider "random" {

​​ Configure Cloudflare resources

The following configuration will modify settings in your Cloudflare account.

  1. In your configuration directory, create a .tf file:

    $ touch
  2. Add the following resources to
    # Generates a 35-character secret for the tunnel.
    resource "random_id" "tunnel_secret" {
    byte_length = 35
    # Creates a new locally-managed tunnel for the GCP VM.
    resource "cloudflare_argo_tunnel" "auto_tunnel" {
    account_id = var.cloudflare_account_id
    name = "Ansible GCP tunnel"
    secret = random_id.tunnel_secret.b64_std
    # Creates the CNAME record that routes ssh_app.${var.cloudflare_zone} to the tunnel.
    resource "cloudflare_record" "ssh_app" {
    zone_id = var.cloudflare_zone_id
    name = "ssh_app"
    value = "${}"
    type = "CNAME"
    proxied = true

​​ Configure GCP resources

The following configuration defines the specifications for the GCP virtual machine and installs Python3 on the machine. Python3 allows Ansible to configure the GCP instance instead of having to run a startup script on boot.

  1. In your configuration directory, create a .tf file:

    $ touch
  2. Open the file in a text editor and copy and paste the following example. Be sure to insert your own GCP username and SSH key pair.
    # Selects the OS for the GCP VM.
    data "google_compute_image" "image" {
    family = "ubuntu-minimal-2004-lts"
    project = "ubuntu-os-cloud"
    # Sets up a GCP VM instance.
    resource "google_compute_instance" "origin" {
    name = "ansible-inst"
    machine_type = var.machine_type
    zone =
    tags = []
    boot_disk {
    initialize_params {
    image = data.google_compute_image.image.self_link
    network_interface {
    network = "default"
    access_config {
    // Ephemeral IP
    scheduling {
    preemptible = true
    automatic_restart = false
    // Installs Python3 on the VM.
    provisioner "remote-exec" {
    inline = [
    "sudo apt update", "sudo apt install python3 -y", "echo Done!"
    connection {
    host = self.network_interface.0.access_config.0.nat_ip
    user = "<username in GCP>"
    type = "ssh"
    private_key= file("<path to private key>")
    provisioner "local-exec" {
    // If specifying an SSH key and user, add `--private-key <path to private key> -u`
    command = "ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -u <username in GCP> --private-key <path to private key> -i ${self.network_interface.0.access_config.0.nat_ip}, playbook.yml"
    metadata = {
    cf-email = var.cloudflare_email
    cf-zone = var.cloudflare_zone
    ssh-keys = "<username in GCP>:${file("<path to public key>")}"
    depends_on = [

​​ Export variables to Ansible

The following Terraform resource exports the tunnel ID and other variables to tf_ansible_vars_file.yml. Ansible will use this data to configure and run cloudlared on the server.

  1. In your configuration directory, create a new tf file:

    $ touch
  2. Copy and paste the following content into
    resource "local_file" "tf_ansible_vars_file" {
    content = <<-DOC
    # Ansible vars_file containing variable values from Terraform.
    tunnel_id: ${}
    account: ${var.cloudflare_account_id}
    tunnel_name: ${}
    secret: ${random_id.tunnel_secret.b64_std}
    zone: ${var.cloudflare_zone}
    filename = "./tf_ansible_vars_file.yml"

​​ 5. Create the Ansible playbook

Ansible playbooks are YAML files that declare the configuration Ansible will deploy.

  1. Create a new .yml file:

    $ touch playbook.yml
  2. Open the file in a text editor and copy and paste the following content:

- hosts: all
become: yes
# Import tunnel variables into the VM.
- ./tf_ansible_vars_file.yml
# Execute the following commands on the VM.
- name: Download the cloudflared Linux package.
shell: wget
- name: Depackage cloudflared.
shell: sudo dpkg -i cloudflared-linux-amd64.deb
- name: Create a cloudflared service directory.
shell: mkdir -p /etc/cloudflared/
- name: Create the config file for cloudflared and define the ingress rules for the tunnel.
dest: "/etc/cloudflared/config.yml"
content: |
tunnel: "{{ tunnel_id }}"
credentials-file: /etc/cloudflared/cert.json
logfile: /var/log/cloudflared.log
loglevel: info
- hostname: "ssh_app.{{ zone }}"
service: ssh://localhost:22
- service: http_status:404
- name: Create the tunnel credentials file for cloudflared.
dest: "/etc/cloudflared/cert.json"
content: |
"AccountTag" : "{{ account | quote }}",
"TunnelID" : "{{ tunnel_id | quote }}",
"TunnelName" : "{{ tunnel_name | quote }}",
"TunnelSecret" : "{{ secret | quote }}"
- name: Install the tunnel as a systemd service.
shell: cloudflared service install
- name: Start the tunnel.
name: cloudflared
state: started
enabled: true
masked: no

Keywords define how Ansible will execute the configuration. In the example above, the vars_files keyword specifies where variable definitions are stored, and the tasks keyword specifies the actions Ansible will perform.

Modules specify what tasks to complete. In this example, the copy module creates a file and populates it with content.

​​ 6. Deploy the configuration

Once you have created the configuration files, you can deploy them through Terraform. The Ansible deployment happens within the Terraform deployment when the ansible-playbook command is run.

  1. Initialize your configuration directory:

    $ terraform init
  2. (Optional) Preview everything that will be created:

    $ terraform plan
  3. Deploy the configuration:

    $ terraform apply

It may take several minutes for the GCP instance and tunnel to come online. You can view your new tunnel in Zero Trust under Access > Tunnels.

​​ 7. Test the connection

You can now SSH to the GCP server through the new ssh_app.<zone> hostname. For instructions on how to connect, refer to our SSH guide.