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.
Production Environments
The information in this article should be used as a guide only. If you are deploying this method into a production environment, we recommend that you also follow all security and configuration best practices.
Code samples in this guide may contain references to specific versions of software or container images that may not be the latest versions available. Please refer to the official documentation for that software or container image for the most up-to-date information.
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 groupingtwingate_connectorandtwingate_connector_tokens— the network agent that routes traffictwingate_ssh_certificate_authority— the SSH CA used to authenticate to target serverstwingate_x509_certificate_authority— the X.509 CA used for Client-to-Gateway TLStwingate_group— controls which users can access the SSH Resourcetwingate_ssh_resource— the SSH server you want to protecttwingate_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)
If you don’t have a Remote Network and Connector set up yet, see the Getting Started with Terraform and Twingate guide to deploy one alongside your cloud infrastructure.
Set up the Terraform provider
Create a new folder for your Terraform configuration:
mkdir twingate-ssh-democd twingate-ssh-demoCreate 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"Protect Your Credentials
Do not commit terraform.tfvars to source control. Add it to your .gitignore file to avoid accidentally exposing your API token.
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 initGenerate 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)
For production deployments, consider using HashiCorp Vault to manage the CA private key instead of a local file. The Terraform provider supports Vault integration in the twingate_gateway_config resource.
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}If you already have a Remote Network, use a data source to reference it:
data "twingate_remote_network" "existing" { name = "My Existing Network"}Then replace twingate_remote_network.ssh_demo.id with data.twingate_remote_network.existing.id in subsequent resources.
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 serveralias— 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 servergateway_id— the Gateway that routes SSH traffic to this serveraccess_group— ties the Resource to a Twingate Group for access controlprotocols— 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 connectionsssh.username— the OS user account the Gateway runs asssh.key_type— the SSH key algorithm used for host certificatesssh.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 hostssh.resources— the SSH Resources this Gateway serves
The generated YAML configuration is available in the twingate_gateway_config.ssh_demo.content output attribute. You can write it to a file using a local_file resource or pass it directly to your container orchestrator.
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 serverecho "YOUR_SSH_CA_PUBLIC_KEY" >> /etc/ssh/trusted_ca_keys
# Add the following line to /etc/ssh/sshd_configTrustedUserCAKeys /etc/ssh/trusted_ca_keys
# Restart sshdsudo systemctl restart sshdReplace 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.
Twingate Privileged Access for SSH can coexist with existing SSH authentication methods. Existing authorized_keys entries are unaffected, so you can migrate at your own pace.
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 Connectorresource "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 Authorityresource "twingate_ssh_certificate_authority" "ssh_ca" { name = "SSH Demo CA" public_key = trimspace(file("${path.module}/ssh_ca_key.pub"))}
# X.509 Certificate Authorityresource "twingate_x509_certificate_authority" "x509_ca" { name = "SSH Demo X509 CA" certificate = file("${path.module}/x509_ca_cert.pem")}
# Access Groupresource "twingate_group" "ssh_users" { name = "SSH Demo Users"}
# SSH Resourceresource "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 Configurationresource "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 fileresource "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 planReview the plan output to confirm the resources match your expectations, then apply:
terraform applyType 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:latestFor Kubernetes deployments, see the Gateway deployment repository for Helm charts and reference architectures.
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_hostswith 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.intThe 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-gatewayYou 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 destroyterraform destroy will remove all Twingate Resources, Certificate Authorities, and Groups created by this configuration. Users will lose SSH access to the protected servers immediately.
Next steps
- Privileged Access for SSH overview — full feature documentation including supported protocols and security considerations
- Remote development with Twingate SSH — set up VS Code, JetBrains, and Cursor for remote development
- Managing contractor and vendor SSH access — grant time-limited access with automatic expiration
- SSH session recording for compliance — configure session capture and SIEM forwarding
- Automating infrastructure with Ansible — run playbooks over Twingate SSH with no key management
Join us in the community subreddit to share your setup experience or ask questions.
Last updated 13 minutes ago