managing twingate

Getting Started with Identity Firewall SSH using Terraform

Deploy Twingate Privileged Access for SSH using Terraform to set up Certificate Authorities, SSH Resources, Gateway configuration, and access policies.

What you will build

This guide walks you through deploying Twingate Privileged Access for SSH using Terraform. By the end, you will have:

  • An SSH Certificate Authority registered with Twingate
  • An X.509 Certificate Authority for Client-to-Gateway TLS
  • An SSH Resource accessible through a Twingate Gateway
  • A generated Gateway configuration ready to deploy
  • An access Group controlling who can connect

Here is how the components fit together:

[ SSH Client ] ── Twingate Client ── Connector ──> [ Gateway ] ──> [ SSH Server ]
<====== Twingate Authentication & Authorization ======>

The Twingate Client authenticates the user via your identity provider. The Connector routes traffic to the Gateway, which terminates the SSH session and re-authenticates to the target server using a short-lived certificate from a known CA. No SSH keys are distributed to users, and no bastion host is required.

The Terraform resources you will create:

  • twingate_remote_network — a logical network grouping
  • twingate_connector and twingate_connector_tokens — the network agent that routes traffic
  • twingate_ssh_certificate_authority — the SSH CA used to authenticate to target servers
  • twingate_x509_certificate_authority — the X.509 CA used for Client-to-Gateway TLS
  • twingate_group — controls which users can access the SSH Resource
  • twingate_ssh_resource — the SSH server you want to protect
  • twingate_gateway_config — generates the YAML configuration for the Gateway

Prerequisites

  • Terraform installed (v1.0+)
  • A Twingate account with an API token that has Read, Write & Provision permissions
  • A Remote Network with at least one running Connector at version 1.82.0 or later
  • A host or Kubernetes cluster in your Remote Network where the Gateway will run
  • An SSH server in your Remote Network that you want to protect
  • The Twingate Client at version 2025.310 or later (macOS, Windows, or Linux)

Set up the Terraform provider

Create a new folder for your Terraform configuration:

mkdir twingate-ssh-demo
cd twingate-ssh-demo

Create a file called main.tf with the Twingate provider configuration:

terraform {
required_providers {
twingate = {
source = "twingate/twingate"
version = "~> 3.0"
}
}
}
variable "tg_api_key" {
sensitive = true
}
variable "tg_network" {}
provider "twingate" {
api_token = var.tg_api_key
network = var.tg_network
}

Create a terraform.tfvars file with your Twingate credentials:

tg_api_key = "YOUR_API_TOKEN"
tg_network = "your-network-name"

To generate an API token, navigate to Settings > API in the Admin Console and click Generate Token. Select Read, Write & Provision permissions.

Run terraform init to download the Twingate provider:

terraform init

Generate an SSH CA key pair

The SSH Certificate Authority is how the Gateway authenticates to your SSH servers. You provide the public key to Twingate, and the private key stays on the Gateway host where it signs short-lived certificates for each connection.

Generate an ed25519 key pair:

ssh-keygen -t ed25519 -f ./ssh_ca_key -C "twingate-ssh-ca" -N ""

This creates two files:

  • ssh_ca_key — the private key (stays on the Gateway host, never uploaded to Twingate)
  • ssh_ca_key.pub — the public key (registered with Twingate as a Certificate Authority)

Create the Twingate infrastructure

Remote Network and Connector

Start by creating a Remote Network and Connector. If you already have these, skip ahead and use data sources to reference them instead.

resource "twingate_remote_network" "ssh_demo" {
name = "SSH Demo Network"
}
resource "twingate_connector" "ssh_demo" {
remote_network_id = twingate_remote_network.ssh_demo.id
}
resource "twingate_connector_tokens" "ssh_demo" {
connector_id = twingate_connector.ssh_demo.id
}

SSH Certificate Authority

Register the SSH CA public key you generated earlier:

resource "twingate_ssh_certificate_authority" "ssh_ca" {
name = "SSH Demo CA"
public_key = trimspace(file("${path.module}/ssh_ca_key.pub"))
}

The public_key field accepts the SSH public key in OpenSSH authorized_keys format. The trimspace() function removes any trailing newline from the file.

X.509 Certificate Authority

The X.509 CA secures the TLS connection between the Twingate Client and the Gateway. Generate a self-signed certificate and register it:

openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-keyout x509_ca_key.pem -out x509_ca_cert.pem -days 365 -nodes \
-subj "/CN=Twingate Gateway CA"
resource "twingate_x509_certificate_authority" "x509_ca" {
name = "SSH Demo X509 CA"
certificate = file("${path.module}/x509_ca_cert.pem")
}

Access Group

Create a Group to control which users can access the SSH Resource:

resource "twingate_group" "ssh_users" {
name = "SSH Demo Users"
}

SSH Resource

Define the SSH server you want to protect. The address is the private IP or hostname reachable from the Gateway, and the alias is the DNS name users will use to connect.

resource "twingate_ssh_resource" "demo_server" {
name = "Demo SSH Server"
address = "10.0.1.50"
remote_network_id = twingate_remote_network.ssh_demo.id
gateway_id = twingate_connector.ssh_demo.id
alias = "demo-server.int"
username = "ubuntu"
access_group {
group_id = twingate_group.ssh_users.id
}
protocols {
tcp {
policy = "RESTRICTED"
ports = ["22"]
}
}
}

Key fields:

  • address — the private IP or FQDN of the SSH server
  • alias — the DNS name users type to connect (e.g., ssh demo-server.int)
  • username — the OS-level user account the Gateway authenticates as on the target server
  • gateway_id — the Gateway that routes SSH traffic to this server
  • access_group — ties the Resource to a Twingate Group for access control
  • protocols — restricts traffic to TCP port 22

Gateway configuration

The twingate_gateway_config resource generates a YAML configuration file that you mount into the Gateway container.

resource "twingate_gateway_config" "ssh_demo" {
gateway_listen_port = 8443
metrics_port = 9090
ssh {
username = "gateway"
key_type = "ed25519"
host_certificate_ttl = "24h"
user_certificate_ttl = "5m"
private_key_file = "/etc/gateway/ssh_ca_key"
resources = [
twingate_ssh_resource.demo_server
]
}
}

Key fields:

  • gateway_listen_port — the port the Gateway listens on for incoming connections
  • ssh.username — the OS user account the Gateway runs as
  • ssh.key_type — the SSH key algorithm used for host certificates
  • ssh.user_certificate_ttl — how long each user certificate is valid (keep this short for security)
  • ssh.private_key_file — the path to the CA private key on the Gateway host
  • ssh.resources — the SSH Resources this Gateway serves

Configure the SSH server

Each target SSH server must trust the SSH CA. Add the CA’s public key as a trusted authority:

# Copy the SSH CA public key to the server
echo "YOUR_SSH_CA_PUBLIC_KEY" >> /etc/ssh/trusted_ca_keys
# Add the following line to /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/trusted_ca_keys
# Restart sshd
sudo systemctl restart sshd

Replace YOUR_SSH_CA_PUBLIC_KEY with the contents of the ssh_ca_key.pub file you generated earlier. After this change, the SSH server will accept certificates signed by your CA alongside any existing authentication methods.

Full Terraform configuration

Here is the complete main.tf for reference:

terraform {
required_providers {
twingate = {
source = "twingate/twingate"
version = "~> 3.0"
}
}
}
variable "tg_api_key" {
sensitive = true
}
variable "tg_network" {}
provider "twingate" {
api_token = var.tg_api_key
network = var.tg_network
}
# Remote Network and Connector
resource "twingate_remote_network" "ssh_demo" {
name = "SSH Demo Network"
}
resource "twingate_connector" "ssh_demo" {
remote_network_id = twingate_remote_network.ssh_demo.id
}
resource "twingate_connector_tokens" "ssh_demo" {
connector_id = twingate_connector.ssh_demo.id
}
# SSH Certificate Authority
resource "twingate_ssh_certificate_authority" "ssh_ca" {
name = "SSH Demo CA"
public_key = trimspace(file("${path.module}/ssh_ca_key.pub"))
}
# X.509 Certificate Authority
resource "twingate_x509_certificate_authority" "x509_ca" {
name = "SSH Demo X509 CA"
certificate = file("${path.module}/x509_ca_cert.pem")
}
# Access Group
resource "twingate_group" "ssh_users" {
name = "SSH Demo Users"
}
# SSH Resource
resource "twingate_ssh_resource" "demo_server" {
name = "Demo SSH Server"
address = "10.0.1.50"
remote_network_id = twingate_remote_network.ssh_demo.id
gateway_id = twingate_connector.ssh_demo.id
alias = "demo-server.int"
username = "ubuntu"
access_group {
group_id = twingate_group.ssh_users.id
}
protocols {
tcp {
policy = "RESTRICTED"
ports = ["22"]
}
}
}
# Gateway Configuration
resource "twingate_gateway_config" "ssh_demo" {
gateway_listen_port = 8443
metrics_port = 9090
ssh {
username = "gateway"
key_type = "ed25519"
host_certificate_ttl = "24h"
user_certificate_ttl = "5m"
private_key_file = "/etc/gateway/ssh_ca_key"
resources = [
twingate_ssh_resource.demo_server
]
}
}
# Write the generated config to a local file
resource "local_file" "gateway_config" {
content = twingate_gateway_config.ssh_demo.content
filename = "${path.module}/gateway-config.yaml"
}

And the terraform.tfvars:

tg_api_key = "YOUR_API_TOKEN"
tg_network = "your-network-name"

Deploy

Run terraform plan to preview the resources that will be created:

terraform plan

Review the plan output to confirm the resources match your expectations, then apply:

terraform apply

Type yes when prompted to confirm.

After Terraform completes, it writes the Gateway configuration to gateway-config.yaml in your working directory. Deploy the Gateway using Docker:

docker run -d \
--name twingate-gateway \
-v $(pwd)/gateway-config.yaml:/etc/twingate/gateway.yaml \
-v $(pwd)/ssh_ca_key:/etc/gateway/ssh_ca_key:ro \
-v $(pwd)/x509_ca_key.pem:/etc/gateway/x509_ca_key.pem:ro \
-v $(pwd)/x509_ca_cert.pem:/etc/gateway/x509_ca_cert.pem:ro \
-p 8443:8443 \
twingate/gateway:latest

Verify the connection

  • In the Admin Console, navigate to Teams > Groups and add your user to the SSH Demo Users Group.
  • Open the Twingate Client on your machine.
  • Under More, select Auto-sync SSH Server Configuration. This updates ~/.ssh/known_hosts with the CA’s public key so you won’t see TOFU warnings.
  • Connect to the SSH Resource using the alias you configured:
ssh demo-server.int

The Twingate Client authenticates you in the background via your identity provider. The Gateway issues a short-lived certificate and connects you to the target server. No SSH keys needed on your device.

  • Verify session recording by checking the Gateway logs:
docker logs twingate-gateway

You should see session activity in asciicast v2 format. These logs can be forwarded to your SIEM or object storage for compliance.

Clean up

When you are done testing, remove all resources:

terraform destroy

Next steps

Join us in the community subreddit to share your setup experience or ask questions.

Last updated 13 minutes ago