SSH Manager Pro Get the app

AWS setup

Connect to EC2 instances using temporary credentials or a dedicated IAM user with three permissions. None of them can start, stop, modify or read the contents of an instance.

Choose how you sign in

Option A: Browser sign-in (AWS Console) — Recommended

Sign in via your browser with your root or IAM user account. The app performs an OAuth 2.0 PKCE + DPoP exchange to acquire short-lived STS session tokens. No static long-lived credentials sit on your phone.

  1. Open Settings → AWS and open a profile, or tap + Add profile.
  2. Under Sign in with, choose Browser sign-in.
  3. Choose your default AWS region and tap Sign in with browser.
  4. Authenticate in your browser. The app completes sign-in automatically via loopback redirect (or manual callback URL entry).

Option B: Dedicated IAM User with access keys

If you prefer an IAM user with programmatic keys, scope it tightly to the three required actions:

1. Create the policy

IAM → Policies → Create policy → JSON:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VerifyIdentity",
      "Effect": "Allow",
      "Action": "sts:GetCallerIdentity",
      "Resource": "*"
    },
    {
      "Sid": "FindInstanceAddresses",
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    },
    {
      "Sid": "PushEphemeralKey",
      "Effect": "Allow",
      "Action": "ec2-instance-connect:SendSSHPublicKey",
      "Resource": "arn:aws:ec2:*:*:instance/*"
    }
  ]
}

ec2:DescribeInstances does not support resource-level restriction, which is why it is *. It reveals instance metadata — IDs, addresses, tags — so use an account whose inventory you are comfortable with the app being able to list.

Restricting it further

Scope key pushing to tagged instances:

"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
  "StringEquals": { "aws:ResourceTag/MobileAccess": "yes" }
}

Then tag only the instances you want reachable from the phone.

2. Create the user

IAM → Users → Create user. No console access. Attach the policy, then create an access key for "Application running outside AWS".

3. Add it to the app

Settings → AWS → open a profile, or + Add profile. Name it, choose Access keys under Sign in with, enter the access key ID and secret access key, set the Default region, then tap Test. A successful test shows the account ID it authenticated as. The secret is sealed with the device Keystore.

Multiple AWS profiles

Each set of credentials is a named profile. The first is called default; add more under Settings > AWS > Add profile (Pro) to keep personal, work, staging and production accounts apart. Each profile signs in with either Browser sign-in or Access keys, and each host picks its profile by name in the host editor. A first AWS host can set up its profile without leaving the host editor.

4. Add a host

Add a host, choose AWS, and enter the instance ID (i-0123456789abcdef0). Leave the hostname blank — the address is resolved at connect time, so it survives an instance restart. The username prefills as ec2-user; change it to ubuntu for Ubuntu images or admin for Debian.

Set Region to the region the instance is in, unless it matches the account's default region (shown as Same as account). A host in a different region from the account default fails with "instance not found" until its region is set.

Under Connect using, choose Instance Connect (needs port 22 reachable from your phone) or Session Manager (needs no open port; see below).

Requirements on the instance

If your instances are in a private subnet, Instance Connect alone will not reach them. Use Session Manager for those.

Session Manager

Choose Session Manager under Connect using on the host. The app starts a Systems Manager session to the instance and carries SSH over it, so your phone never connects to the instance directly and no inbound rule is needed. Hostname and port are not used and are hidden for this mode.

On the instance:

For your credentials, add ssm:StartSession to the three permissions above, for the instance and for the AWS-StartSSHSession document.

Deploy with Terraform / OpenTofu

If you manage your AWS infrastructure with Terraform or OpenTofu, this complete blueprint stands up a test Amazon Linux 2023 instance, security group, and least-privilege IAM user ready for the app:

# Resolved at plan time: AL2023 ships EC2 Instance Connect preinstalled
data "aws_ssm_parameter" "al2023" {
  name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64"
}

data "aws_vpc" "default" {
  default = true
}

resource "aws_security_group" "ssh" {
  name        = "sshmanager-test"
  description = "Allow SSH from mobile client"
  vpc_id      = data.aws_vpc.default.id

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Narrow to your IP CIDR if on fixed Wi-Fi
  }

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

resource "aws_instance" "test" {
  ami                    = data.aws_ssm_parameter.al2023.value
  instance_type          = "t3.micro"
  vpc_security_group_ids = [aws_security_group.ssh.id]

  tags = {
    Name         = "sshmanager-test"
    MobileAccess = "yes"
  }
}

# IAM user with least privilege: sts, describe, and send public key
resource "aws_iam_user" "app" {
  name          = "sshmanager-app"
  force_destroy = true
}

resource "aws_iam_user_policy" "app" {
  name = "sshmanager-app-policy"
  user = aws_iam_user.app.name

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect   = "Allow"
        Action   = ["sts:GetCallerIdentity", "ec2:DescribeInstances"]
        Resource = "*"
      },
      {
        Effect   = "Allow"
        Action   = "ec2-instance-connect:SendSSHPublicKey"
        Resource = "arn:aws:ec2:*:*:instance/*"
      }
    ]
  })
}

resource "aws_iam_access_key" "app" {
  user = aws_iam_user.app.name
}

output "instance_id" {
  value = aws_instance.test.id
}

output "public_ip" {
  value = aws_instance.test.public_ip
}

output "access_key_id" {
  value = aws_iam_access_key.app.id
}

output "secret_access_key" {
  value     = aws_iam_access_key.app.secret
  sensitive = true
}

After applying, copy the sensitive secret key:

tofu output -raw secret_access_key

How Instance Connect works with network routing

EC2 Instance Connect operates across two distinct planes:

  1. Control Plane (Key Push): The app calls the AWS API endpoint via HTTPS to push an ephemeral public key into instance metadata. This key is valid for 60 seconds.
  2. Data Plane (SSH Connection): The app then opens a standard SSH connection over TCP port 22 directly to the target instance address.

If your instances are located in a private subnet with no public IP, key pushing will succeed, but the subsequent SSH connection will time out unless you route the connection through a Jump host (bastion) or an internal Tailscale / VPN tunnel.

If it fails

"Permission denied (publickey)"
Usually the wrong username. The key push succeeded but the account it was pushed for does not match.
Test fails with an authorisation error
The policy is not attached, or the key belongs to a different account.
Connection times out
A security group or network ACL, not the app. The key push will have succeeded, but TCP port 22 cannot be reached.