AWS
AMI
Packer.io
SSH Timeout
Troubleshooting

Having trouble creating a basic AWS AMI with Packer.io. SSH Timeout

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Creating AWS AMIs with Packer is straightforward until the build hangs on SSH, which is usually a timing problem between instance boot, cloud-init completion, network availability, and your communicator settings. In practice, the fastest path is to reduce the problem to a small reproducible baseline first, then reintroduce production constraints one by one. That approach keeps debugging local, prevents overfitting to one failing symptom, and makes your final implementation easier to explain to teammates.

Treat SSH timeouts as an integration boundary issue, not only a timeout number issue. AMI source selection, subnet/NACL rules, security groups, username mismatch, and slow bootstrap scripts all influence whether Packer can connect in time. A strong implementation separates configuration from execution flow, adds measurable checkpoints, and captures enough telemetry to distinguish transient failures from deterministic misconfiguration.

Core Sections

1) Define a narrow baseline before optimization

Start by identifying the smallest end-to-end version that should work reliably. Keep external dependencies minimal, remove optional features, and make defaults explicit. Once the baseline is stable, layer complexity gradually and verify behavior after each change. This staged workflow is more predictable than changing multiple variables at once and trying to infer root cause afterward.

2) Build a minimal Packer template with explicit communicator settings

hcl
1packer {
2  required_plugins {
3    amazon = {
4      source  = "github.com/hashicorp/amazon"
5      version = ">= 1.3.0"
6    }
7  }
8}
9
10source "amazon-ebs" "ubuntu" {
11  region                  = "us-east-1"
12  instance_type           = "t3.micro"
13  source_ami_filter {
14    filters = {
15      name                = "ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"
16      root-device-type    = "ebs"
17      virtualization-type = "hvm"
18    }
19    owners      = ["099720109477"]
20    most_recent = true
21  }
22  ssh_username            = "ubuntu"
23  ssh_timeout             = "20m"
24  pause_before_connecting = "45s"
25  ami_name                = "app-base-{{timestamp}}"
26}
27
28build {
29  sources = ["source.amazon-ebs.ubuntu"]
30
31  provisioner "shell" {
32    inline = [
33      "cloud-init status --wait",
34      "sudo apt-get update -y"
35    ]
36  }
37}

This baseline snippet is intentionally conservative. It prioritizes readability, deterministic behavior, and explicit control points over clever shortcuts. For production, you can tune performance later, but first ensure the pipeline is correct and repeatable. If this step does not behave as expected, freeze further refactors and diagnose here; debugging gets exponentially harder once additional abstractions are layered on top.

3) Add diagnostics so you can see where the handshake fails

bash
1PACKER_LOG=1 PACKER_LOG_PATH=packer.log packer build image.pkr.hcl
2
3# Verify that the instance is reachable from the build host
4aws ec2 describe-instances --filters Name=tag:Name,Values=packer-*   --query 'Reservations[].Instances[].{Id:InstanceId,State:State.Name,IP:PrivateIpAddress}'
5
6# If bootstrap is slow, inspect cloud-init output
7aws ec2 get-console-output --instance-id i-xxxxxxxx --latest

Operational guardrails are what turn a working demo into a maintainable system. Add logging around key transitions, monitor latency and error classes, and define clear retry or fallback policy where failures are expected. Avoid silent recovery paths that hide data quality or state issues. Instead, emit structured signals that make post-incident analysis straightforward.

4) Validate behavior with repeatable checks

Validate in stages: packer validate, then one build in a known-good subnet, then a second build with temporary provisioners removed. This sequence isolates connectivity from provisioning logic and keeps troubleshooting time low. Write a short verification checklist that can run in local development, CI, and pre-release environments. Include both success-path assertions and at least one intentional failure case. Over time, this checklist becomes regression protection: it documents assumptions, catches environment drift, and prevents future edits from reintroducing the same class of bug.

Common Pitfalls

  • Using the wrong SSH username for the base AMI family, for example ec2-user vs ubuntu.
  • Opening port 22 in the instance security group but forgetting egress/NACL rules on the builder network path.
  • Raising ssh_timeout without waiting for cloud-init, which hides a readiness race instead of fixing it.
  • Provisioners that reboot the instance without expect_disconnect and reconnect settings.
  • Building in private subnets without a reachable route from the Packer runner.

Summary

A stable AMI pipeline comes from deterministic boot readiness, explicit SSH settings, and logs that explain every failed attempt. The key pattern is consistent across stacks: keep the core path simple, instrument the edges, and validate with deterministic tests before scaling complexity.


Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.