Beyond SSH: The Best Ways to Securely Access Your AWS EC2 Instances

Beyond SSH: The Best Ways to Securely Access Your AWS EC2 Instances

Introduction: Secure and Cost-Effective Ways to Access EC2 Instances

Amazon EC2 instances power a wide range of cloud workloads, from web applications to enterprise databases. However, securely accessing these instances is often an overlooked aspect of cloud security and cost optimization.

Traditionally, SSH access with key pairs has been the go-to method for developers, but modern AWS services like Session Manager, VPNs, and Bastion Hosts provide more secure and scalable alternatives. The choice of access method significantly impacts security risks, operational complexity, and costs—making it crucial to select the right approach for your environment.

In this blog, we'll dive deep into:

? The different methods of accessing EC2 instances (SSH, Session Manager, Bastion Hosts, VPNs, and Direct Connect).

? A detailed step-by-step Terraform implementation for each method.

? A comparative analysis of cost, security risks, and best practices.


By the end of this guide, you'll be equipped to choose the best access method that balances security, ease of use, and cost-effectiveness for your AWS environment. Let’s get started!


Comparative Summary Table

Accessing EC2 instances can be done using various methods, each with different cost implications and security risks.

Below is a comparison of the most common methods:?

1. SSH (Secure Shell) via Key Pair

  • Method: Connect using an SSH client with a key pair (.pem or .ppk file).
  • Cost: Free (only standard EC2 running costs).
  • Security Risks: If the private key is exposed, attackers can access the instance. Open SSH ports (22) can be a vulnerability if not properly secured with security groups and firewalls. Brute force attacks if password authentication is enabled (not recommended).

Risk Mitigation:

? Use key-based authentication only.

? Restrict SSH access to specific IPs in security groups.

? Rotate SSH keys periodically.?

Best for: Traditional remote access with Linux/macOS terminal or PuTTY on Windows.


Step-by-Step Implementation

Step 1: Create an SSH Key Pair

  1. AWS Console → EC2 → Key Pairs → Create Key Pair.
  2. Choose a name (e.g., my-key) and select RSA (for PuTTY use .ppk).
  3. Download and securely store the private key (.pem file).

Step 2: Launch EC2 with Key Pair

  1. AWS Console → EC2 → Launch Instance.
  2. Select AMI (Amazon Linux, Ubuntu, etc.).
  3. Choose instance type.
  4. Under Key Pair, select the key pair created earlier.
  5. Configure networking (allow inbound SSH on port 22 from your IP).
  6. Launch the instance.

Step 3: Connect to the EC2 Instance

On macOS/Linux

chmod 400 my-key.pem
ssh -i my-key.pem ec2-user@<Public-IP>        

(Use Ubuntu as the username if using Ubuntu AMI.)

On Windows (Using PuTTY)

  1. Convert .pem to .ppk using PuTTYgen.
  2. Open PuTTY → Enter Public IP → Load .ppk in SSH authentication settings.
  3. Click Open to connect.

Step 4: Security Best Practices

? Disable password authentication in /etc/ssh/sshd_config by setting:

PasswordAuthentication no        

? Use Security Groups to allow SSH only from trusted IPs.

? Use AWS Systems Manager (SSM) instead of SSH where possible.


We can also implement it through an IAC tool like Terraform, a sample solution will look like the following:

Creates an EC2 instance with SSH access using a key pair.

Terraform Configuration

provider "aws" {
  region = "us-east-1"
}

resource "aws_key_pair" "my_key" {
  key_name   = "my-key"
  public_key = file("~/.ssh/id_rsa.pub")  # Replace with your public key path
}

resource "aws_security_group" "ssh_sg" {
  name        = "allow_ssh"
  description = "Allow SSH inbound traffic"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["YOUR_IP/32"]  # Replace with your public IP
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "my_ec2" {
  ami             = "ami-0c55b159cbfafe1f0"  # Amazon Linux 2 AMI ID (update as needed)
  instance_type   = "t2.micro"
  key_name        = aws_key_pair.my_key.key_name
  security_groups = [aws_security_group.ssh_sg.name]

  tags = {
    Name = "SSH-Access-EC2"
  }
}        

Deployment Steps

  1. Save the above Terraform code in main.tf.
  2. Run:
  3. terraform init
  4. terraform apply -auto-approve
  5. Connect using:
  6. ssh -i ~/.ssh/id_rsa ec2-user@<EC2_PUBLIC_IP>


2. EC2 Instance Connect (for Amazon Linux & Ubuntu)

  • Method: Connect via the AWS Management Console without an SSH key.
  • Cost: Free.
  • Security Risks: Limited to Amazon Linux and Ubuntu. If AWS credentials are compromised, access could be gained to the instance.

Risk Mitigation:

? Use IAM policies with the least privilege.

? Enable MFA for AWS Console.

Best for: Temporary access via AWS Console (no need for SSH key).


Step-by-Step Implementation

Step 1: Enable EC2 Instance Connect

  1. Launch an EC2 instance with Amazon Linux 2 or Ubuntu 20.04/22.04.
  2. Ensure the instance has a public IP and allows inbound SSH on port 22.

Step 2: Connect Using AWS Console

  1. AWS Console → EC2 → Instances → Select instance.
  2. Click ConnectEC2 Instance ConnectConnect.
  3. A terminal opens inside the AWS console.

Step 3: Security Best Practices

? Use IAM roles to restrict who can access EC2 Instance Connect.

? Disable direct SSH if using this method exclusively.?


Steps to implement through the IAC tool Terraform:?

Terraform Configuration

provider "aws" {
  region = "us-east-1"
}

resource "aws_iam_role" "ssm_role" {
  name = "ssm-instance-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
  })
}

resource "aws_iam_policy_attachment" "ssm_attach" {
  name       = "ssm-attach"
  roles      = [aws_iam_role.ssm_role.name]
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_instance_profile" "ssm_instance_profile" {
  name = "ssm-instance-profile"
  role = aws_iam_role.ssm_role.name
}

resource "aws_instance" "ssm_ec2" {
  ami             = "ami-0c55b159cbfafe1f0"
  instance_type   = "t2.micro"
  iam_instance_profile = aws_iam_instance_profile.ssm_instance_profile.name
  tags = {
    Name = "SSM-Access-EC2"
  }
}        

Deployment Steps

  1. Save the code as main.tf and run:
  2. terraform init
  3. terraform apply -auto-approve
  4. Connect to the EC2 instance via AWS Systems Manager Session Manager: AWS Console → EC2 → Instance → Connect → Session Manager.


3. Systems Manager (SSM) Session Manager

  • Method: Access EC2 via AWS SSM agent (no SSH required).
  • Cost: Free (up to 1000 sessions per AWS account per month, after that, small charges apply).
  • Security Risks: Requires correct IAM permissions. If IAM permissions are misconfigured, unauthorized access is possible.

Risk Mitigation:

? Restrict IAM roles to allow only authorized users.

? Use AWS Organizations SCPs to prevent misconfigurations.?

Best for: Secure access without SSH, with full AWS IAM control.


Step-by-Step Implementation

Step 1: Attach IAM Role to EC2

  1. AWS Console → IAM → Roles → Create Role.
  2. Select AWS Service → EC2 → Attach AmazonSSMManagedInstanceCore policy.
  3. Name the role (e.g., EC2-SSM-Role) and attach it to the EC2 instance.

Step 2: Install & Start SSM Agent

For Amazon Linux & Ubuntu, the SSM agent is pre-installed. If using another OS:

sudo yum install -y amazon-ssm-agent   # Amazon Linux

sudo apt install -y amazon-ssm-agent   # Ubuntu

#Start the agent:

sudo systemctl enable amazon-ssm-agent

sudo systemctl start amazon-ssm-agent        

Step 3: Connect Using Session Manager

  1. AWS Console → EC2 → Select Instance → Connect.
  2. Click Session Manager → Start Session.

Step 4: Security Best Practices

? Remove SSH access (port 22) completely if using this method.

? Restrict IAM permissions to avoid misuse.

? Enable AWS CloudTrail to monitor session activity.


4. Bastion Host (Jump Server)

  • Method: Connect via an intermediary EC2 instance in a public subnet.
  • Cost: Extra cost for running a bastion host (small instance ~$3–$10/month).
  • Security Risks: If the bastion host is compromised, attackers can pivot into the private network.

Risk Mitigation:

? Use key-based authentication and restrict access by IP.

? Enable logging and monitoring with CloudTrail.

? Consider auto-shutdown policies for unused bastion hosts.

Best for: Private subnet instances needing indirect SSH access.


Step-by-Step Implementation

Step 1: Launch a Bastion Host

  1. AWS Console → Launch a small EC2 instance (Amazon Linux).
  2. Attach a public IP and security group allowing SSH (port 22) from trusted IPs.

Step 2: Launch Private EC2 Instance

  1. Launch another EC2 in a private subnet.
  2. Do not assign a public IP.
  3. Allow inbound SSH (port 22) only from the bastion host’s private IP.

Step 3: SSH via Bastion Host

ssh -i my-key.pem ec2-user@<Bastion-Public-IP>

ssh -i my-key.pem ec2-user@<Private-EC2-Private-IP>

Step 4: Security Best Practices

? Configure IAM Session Manager instead of SSH if possible.

? Auto-stop bastion host when not in use.

? Use fail2ban to block brute-force attacks.


Using Terraform to implement the above steps:?

Creates a bastion host in a public subnet and a private EC2 instance.

Terraform Configuration

provider "aws" {

  region = "us-east-1"

} 

resource "aws_vpc" "my_vpc" {

  cidr_block = "10.0.0.0/16"

} 

resource "aws_subnet" "public_subnet" {

  vpc_id                  = aws_vpc.my_vpc.id

  cidr_block              = "10.0.1.0/24"

  map_public_ip_on_launch = true

} 

resource "aws_subnet" "private_subnet" {

  vpc_id     = aws_vpc.my_vpc.id

  cidr_block = "10.0.2.0/24"

} 

resource "aws_security_group" "bastion_sg" {

  vpc_id = aws_vpc.my_vpc.id 

  ingress {

    from_port   = 22

    to_port     = 22

    protocol    = "tcp"

    cidr_blocks = ["YOUR_IP/32"]

  } 

  egress {

    from_port   = 0

    to_port     = 0

    protocol    = "-1"

    cidr_blocks = ["0.0.0.0/0"]

  }

} 

resource "aws_security_group" "private_sg" {

  vpc_id = aws_vpc.my_vpc.id 

  ingress {

    from_port       = 22

    to_port         = 22

    protocol        = "tcp"

    security_groups = [aws_security_group.bastion_sg.id]

  }

} 

resource "aws_instance" "bastion_host" {

  ami             = "ami-0c55b159cbfafe1f0"

  instance_type   = "t2.micro"

  subnet_id       = aws_subnet.public_subnet.id

  security_groups = [aws_security_group.bastion_sg.name]

  key_name        = aws_key_pair.my_key.key_name

} 

resource "aws_instance" "private_instance" {

  ami             = "ami-0c55b159cbfafe1f0"

  instance_type   = "t2.micro"

  subnet_id       = aws_subnet.private_subnet.id

  security_groups = [aws_security_group.private_sg.name]

  key_name        = aws_key_pair.my_key.key_name

}        

Deployment Steps

  1. Save the above Terraform code in main.tf.
  2. Run:
  3. terraform init
  4. terraform apply -auto-approve
  5. Connect:
  6. ssh -i ~/.ssh/id_rsa ec2-user@<BASTION_PUBLIC_IP>
  7. ssh -i ~/.ssh/id_rsa ec2-user@<PRIVATE_EC2_PRIVATE_IP>


5. VPN (Virtual Private Network)

  • Method: Connect via a VPN (AWS Site-to-Site VPN or AWS Client VPN).
  • Cost: AWS Site-to-Site VPN: ~$36/month per VPN tunnel. AWS Client VPN: ~$0.05 per connection/hour.
  • Security Risks: If VPN credentials are stolen, access to the internal network is possible.

Risk Mitigation:

? Use strong authentication (e.g., MFA).

? Regularly rotate VPN credentials.?

Best for: Secure private network access without exposing instances to the internet.


Step-by-Step Implementation

Step 1: Create a Client VPN Endpoint

  1. AWS Console → VPC → Client VPN Endpoints → Create.
  2. Configure: CIDR block (e.g., 10.0.0.0/22). Server certificate (ACM). Authentication (Active Directory, mutual auth, or IAM). Associate with VPC subnets.

Step 2: Connect to the VPN

  1. Download the VPN configuration file from AWS.
  2. Use OpenVPN or AWS VPN client to connect.

Step 3: Security Best Practices

? Enable MFA for VPN authentication.

? Restrict access using IAM policies.

? Monitor connections with AWS CloudWatch.

?

Using Terraform to create the required setup:

Creates a VPN endpoint for secure access.

Terraform Configuration

resource "aws_ec2_client_vpn_endpoint" "vpn" {

  server_certificate_arn = "arn:aws:acm:region:account:certificate/your-cert-id"

  client_cidr_block      = "10.10.0.0/22"

  authentication_options {

    type = "certificate-authentication"

    root_certificate_chain_arn = "arn:aws:acm:region:account:certificate/your-root-cert-id"

  }

  connection_log_options {

    enabled = false

  }

}

 

resource "aws_ec2_client_vpn_network_association" "vpn_assoc" {

  client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id

  subnet_id              = "subnet-xxxxxxx"  # Replace with a private subnet ID

}        

Deployment Steps

  1. Replace your-cert-id with a valid ACM certificate ARN.
  2. Save and run:
  3. terraform init
  4. terraform apply -auto-approve
  5. Download the VPN configuration file and connect using OpenVPN.


6. Direct Connect

  • Method: Private dedicated network connection to AWS.
  • Cost: High (~$0.03–$0.06 per GB, plus port charges of $0.30–$2.25 per hour).
  • Security Risks: If on-premises infrastructure is compromised, attackers could gain access to AWS.

Risk Mitigation:

? Use NACLs and firewalls to restrict traffic.

? Monitor logs with AWS CloudTrail.?

Best for: Enterprise-level secure private AWS access.


Step-by-Step Implementation

Step 1: Order a Direct Connect Circuit

  1. Contact an AWS Direct Connect partner for physical connectivity.
  2. Request Direct Connect via the AWS Console.

Step 2: Configure Virtual Interface

  1. AWS Console → Direct Connect → Virtual Interfaces.
  2. Create a Private VIF and associate it with a VPC.

Step 3: Secure the Connection

? Implement Network ACLs to restrict unwanted traffic.

? Monitor traffic using AWS CloudWatch.?

?


Best Approach?

  • For secure, cost-effective access: SSM Session Manager (no open ports, IAM-based access).
  • For traditional SSH needs: Bastion host with strict security policies.
  • For enterprise setups: Direct Connect or VPN for private access.

Final Comparison summary:


要查看或添加评论,请登录

Manish Kumar的更多文章

社区洞察

其他会员也浏览了