Generating SSH configs with Terraform

Posted on March 7, 2026 by Jarvis Cochrane

After using Terraform for a while, it became a little tedious to keep updating ~/.ssh/config every time something changed on my EC2 instances.

I wondered if this could somehow be automated from within Terraform. Here’s the little solution I came up with.

Given a list of host structures this little module writes an SSH configuration file in ~/.ssh. It supports using either plain SSH or SSH tunnelled over SSM Session Manager. The host SSH port is also configurable since, for reasons, I sometimes need to use a non-standard port number.

This solution assumes some flavour of Unix (MacOS in my case), Terraform, AWS, and having an AWS CLI profile configured.

Usage

Assuming variables.tf and main.tf are in a module called ssh-config, and the modules host-web1 and host-web2 return suitable host data structures, you’d use it something like this:

module "ssh-config" {
  source = "../ssh-config"

  config_prefix = "web"
  aws_profile = "web_profile"
  hosts         = [module.host-web1.host, module.host-web2.host]
}

The generated configuration (~/.ssh/web_config) will look something like:

# Generated by Terraform 
 
Host web1 
  IdentityFile ~/.ssh/web.pem 
  User ec2-user 
  ProxyCommand sh -c "aws --profile web_profile ssm start-session --target i-a89d4be23b593efc8 --document-name AWS-StartSSHSession --parameters 'portNumber=22'"

Host web2
  IdentityFile ~/.ssh/web.pem 
  User ec2-user 
  HostName web2.example.org
  Port 22

This can be included in the default ~/.ssh/config config file with an include clause: include web_config.

variables.tf

config_prefix is used as the first part of the name of the SSH configuration file, which is created as ~/.ssh/<config_prefix>_config.

aws_profile is the name of the AWS profile used to setup the SSM Session Manager tunnel. It’s not used if you’re not tunnelling SSH over SSM.

hosts is a list of Terraform objects. I hope the attribute names are all fairly self-explanatory. I have another custom module to create EC2 instances which returns this structure, so everything fits together nicely.

# Copyright (c) 2022 Cinnabar Services Pty Ltd. All Rights Reserved.

variable "config_prefix" {
  description = "Prefix to add to ssh config file"
  type        = string
}

variable "aws_profile" {
  description = "AWS profile to use for tunnelling SSH over SSM"
  type        = string
  default     = ""
}

variable "hosts" {
  description = "List of hosts"
  type = list(object({
    instance_id : string,
    hostname : string,
    public_ip : string,
    ssh_port : number,
    ssh_over_ssm : bool,
    private_key_file : string,
    user : string
    }
  ))
}

main.tf

This file creates the actual SSH config file ‘resource’. Within the ‘here document’ template, it iterates over the list of host structures and generates the appropriate configuration for each host.

# Copyright (c) 2022 Cinnabar Services Pty Ltd. All Rights Reserved.

# Write out ssh config for hosts

resource "local_file" "ssh_config" {
  filename = pathexpand(
    "~/.ssh/${var.config_prefix}_config"
  )
  file_permission = "0644"
  content         = <<EOT
# Generated by Terraform
%{for host in var.hosts}

Host ${host.hostname}
  IdentityFile ${host.private_key_file}
  User ${host.user}
  %{ if host.ssh_over_ssm }
  ProxyCommand sh -c "aws --profile ${var.aws_profile} ssm start-session --target ${host.instance_id} --document-name AWS-StartSSHSession --parameters 'portNumber=${host.ssh_port}'"
  %{ else }
  HostName ${host.public_ip}
  Port ${host.ssh_port}
  %{ endif }
%{endfor}
EOT
}

Note

The SSH config file is created with permissions 0644. This shouldn’t be an issue since it only references the private keys (which should be restricted to 0600), but you may opt for a more restrictive mode.

This probably isn’t a drop-in module that you can immediately use in your own Terraform setups, but I hope the general technique is useful.