Ravindra BagaleCourses & study guides मराठी Track your progress

How to Access a Private EC2 with a Bastion Host and NAT Gateway

The previous chapter ended with a puzzle, Puzzle: PHP on a private server: the app server has no public IP and no route to the internet, so you can't SSH to it and sudo yum install php -y can't download anything. This chapter solves both halves. First you reach the private server through a bastion host (jump server), with the key staying on your laptop. Then you give the private server a one-way door to the internet with a NAT gateway, so it can download packages while nobody outside can reach it. Finally you copy the whole layout into a second Availability Zone, so one AZ going down doesn't take the application with it.

What you'll learn in this class

  • Why an AMI alone doesn't solve the puzzle
  • What a bastion host (jump server) is
  • SSH through a bastion without copying the key
  • Security groups for the bastion and the private server
  • Why yum still fails, and why copying packages by hand doesn't work
  • What a NAT gateway does: out yes, in no
  • The private route table with a NAT gateway
  • Creating the NAT gateway in the console
  • What it costs
  • High availability: the same layout in two AZs

1. The two problems

The app server sits in the private subnet 10.0.16.0/20, for example at 10.0.16.7.

  1. No public IP: your laptop can't SSH to it.
  2. No route to the internet: its route table has only 10.0.0.0/16 → local, so sudo yum install php -y can't reach the package repos.

The server must stay private. So we need a way in for you, and a way out for the server, without opening a way in for everyone else.

2. Idea 1: build it public, copy it as an AMI

  1. Launch a server in the public subnet.
  2. sudo yum install php -y works there, because it has internet.
  3. Make an AMI of it (What an AMI is).
  4. Launch the private app server from that AMI. It starts with PHP already installed.

Why it isn't enough: it works for the first setup only. The day php-fpm stops or you need to read a log on the private server, you still have no way to log in.

Classroom line

If some error comes up on it, how will you resolve it then?

Idea 1: build PHP on a public server, copy it as anAMI Public dev server sudo yum install php -y works: it has internet AMI a copy of the disk Private app server launched from the AMI PHP already installed Works for the first setup The server starts with PHP on it. Fails later No way to log in to read logs or fix php-fpm.

Figure 1. Install PHP on a public dev server, make an AMI, launch the private server from it: PHP is there for the first setup, but later you can't log in to read logs or fix php-fpm.

3. The bastion host (jump server)

What: a small server in the public subnet whose only job is to be the door for SSH. You SSH to it, and from it to the private server. The word comes from castles: a bastion is the strong point of the wall that faces attackers, so it is the one place you guard hardest.

  1. Laptop → SSH → bastion (public IP).
  2. Bastion → SSH → app server (private IP 10.0.16.7).

The private server still has no public IP. The bastion reaches it over the local route, because both are in the same VPC.

Classroom line

Using one server to get to another server is what we call a jump server.

In our setup the public web server can act as the bastion. In production the bastion is a separate small server that runs nothing else.

4. SSH through the bastion without copying the key

Never copy keypair.pem onto the bastion. The bastion faces the internet; if someone breaks into it, they would take the key to every server. The key stays on your laptop.

Way 1: one command with -J (ProxyJump) (Linux, macOS, or Windows PowerShell with OpenSSH)

  1. Load the key into your SSH agent: ssh-add keypair.pem. If it says it can't connect to the agent, start one first: eval "$(ssh-agent -s)" on Linux.
  2. Jump in one command:

    ssh -J ec2-user@<bastion-public-ip> ec2-user@10.0.16.7
    
  3. SSH connects to the bastion, and through it to 10.0.16.7. Both logins use the key from your laptop's agent.

Way 2: a shortcut in ~/.ssh/config on your laptop:

Host bastion
    HostName <bastion-public-ip>
    User ec2-user
    IdentityFile ~/keypair.pem
Host app
    HostName 10.0.16.7
    User ec2-user
    IdentityFile ~/keypair.pem
    ProxyJump bastion

Now ssh app is enough.

Way 3: hop by hand with agent forwarding

  1. ssh-add keypair.pem
  2. ssh -A ec2-user@<bastion-public-ip> (-A forwards your agent; nothing is copied).
  3. On the bastion: ssh ec2-user@10.0.16.7.

Prefer -J: with -A, an admin of the bastion could use your agent while you are logged in. The basics of the ssh command are in Building the SSH command piece by piece.

Security groups:

  1. Bastion SG: inbound SSH 22 from My IP only.
  2. App server SG: inbound SSH 22 with source = bastion SG only (plus HTTP 80 from the web SG, as before).

Without SSH at all: AWS Systems Manager Session Manager opens a shell on the server from the console, with no port 22 and no bastion; it needs the SSM agent and an IAM role on the server.

Bastion host: SSH to the public server, then hopto the private one Your laptop keypair.pem stays here ssh-add keypair.pem ssh -A ec2-user@<bastion-public-ip> 1. From the laptop to the bastion. -A forwards your key agent; the .pemis not copied. VPC 10.0.0.0/16 Bastion host (public subnet) public IP SG: 22 from My IP only [ec2-user@bastion ~]$ ssh ec2-user@10.0.16.7 2. From the bastion to the private IP. The agent on your laptopanswers for the key. Private app server 10.0.16.7 SG: 22 from the bastion SG only ssh -J ec2-user@<bastion-public-ip> ec2-user@10.0.16.7 3. The same two hops in one command with -J (ProxyJump).

Figure 2. Animation: the laptop keeps the key (ssh-add); ssh -A to the bastion (22 from My IP only), then ssh to 10.0.16.7 (22 from the bastion SG only); or both hops at once with ssh -J.

5. You're in, but yum still fails

On the app server, run sudo yum install php -y. It waits and then fails with a timeout. The reason hasn't changed: the private route table has no route to the internet.

Why not download the package on the laptop and copy it? A package is not one file:

  1. The program goes to /usr/bin, settings to /etc, logs to /var/log, extensions to /usr/lib64.
  2. It needs other packages (dependencies), which need others again.
  3. yum downloads all of them and puts each file in its place. By hand you will miss something.

Classroom line

All of these together make one piece of software.

One package = many files in many folders php package from the repo /usr/bin/php the program /etc/php.ini settings /etc/php-fpm.d/www.conf php-fpm pool /var/log/php-fpm/ logs /usr/lib64/php/modules/ extensions + php-common, php-cli ... dependencies Copying a few files by hand misses the rest and the dependencies, so yummust download it.

Figure 3. One php package spreads files over /usr/bin, /etc, /etc/php-fpm.d, /var/log and /usr/lib64, and pulls in dependencies such as php-common and php-cli. yum has to download it.

What we need: the server stays private, but it can start connections out to the internet.

Classroom line

We don't want to make it public; it will stay private, and it still needs internet.

6. The idea: an office extension phone

Think of a desk phone in an office.

  1. Your extension has no direct number. Nobody outside can dial it.
  2. You can still dial out. The call leaves through the office exchange, which has one outside number.
  3. The person you called answers, and the reply comes back to your desk.
  4. If an outsider wants to start a call to your extension, there is no number to dial.

A NAT gateway is that exchange for private servers.

NAT is like an office extension phone Office Your extension no direct number Office exchange one outside number ↓ you dial out · ↑ the replycomes back Outside world ✗ outsiders cannot call your extension Extension = private server Exchange = NAT gateway Outside number = its Elastic IP

Figure 4. Your extension (private server) dials out through the office exchange (NAT gateway) with its one outside number (Elastic IP); the reply comes back, but outsiders can't call your extension.

7. What a NAT gateway does

NAT = Network Address Translation.

  1. You create the NAT gateway in a public subnet and give it an Elastic IP.
  2. The private server sends a request to the internet. Its route table sends it to the NAT gateway.
  3. The NAT gateway replaces the private source IP (10.0.16.7) with its Elastic IP and sends it out through the internet gateway.
  4. The reply comes back to the Elastic IP. The NAT gateway remembers the connection and hands the reply to 10.0.16.7.
  5. A connection started from the internet towards the NAT gateway is dropped. Nobody can reach the private server through it.

So the private server never needs a public IP of its own.

It is a managed service: AWS runs it and scales it; it is not an EC2 instance you look after, and it has no security group (the NACL of its subnet still applies). The do-it-yourself option is a NAT instance, an EC2 server you configure and patch yourself.

Classroom line

If you need internet in a private subnet, what do you have to do? You have to use a NAT gateway.

NAT gateway: the private server goes out, nobodycomes in VPC 10.0.0.0/16 Private subnet App server 10.0.16.7 · no public IP Public subnet NAT gateway + Elastic IP · AWS-managed 1. sudo yum install php -y: the request leaves from 10.0.16.7 tothe NAT gateway. NAT swaps the private source IP for itsElastic IP and sends it out through the IGW. IGW door of the VPC Package repo on the internet 2. The repo replies to the Elastic IP. NAT remembers the connectionand hands the reply back to 10.0.16.7. The packages download. ✗ 3. Someone on the internet starts a connection to the Elastic IP:the NAT gateway drops it. The app server stays private.

Figure 5. Animation: the request leaves 10.0.16.7, the NAT gateway swaps in its Elastic IP and sends it through the IGW; the reply comes back to 10.0.16.7; a connection started from the internet is dropped.

8. The route tables

  1. Public route table (web, bastion, NAT gateway): 10.0.0.0/16 → local, 0.0.0.0/0 → igw-.... Unchanged.
  2. Private route table (app, DB): 10.0.0.0/16 → local, and add 0.0.0.0/0 → nat-....

Worked example: the app server sends two packets.

  1. To the DB at 10.0.32.10: both routes match, 10.0.0.0/16 is more specific than 0.0.0.0/0, so it stays local.
  2. To a package repo on the internet: only 0.0.0.0/0 matches, so it goes to the NAT gateway.

Remember: the NAT gateway itself sits in the public subnet, because it needs the public route table to reach the internet gateway.

Route tables after adding the NAT gateway Public subnet (web, bastion, NAT gateway) public route table Destination Target 10.0.0.0/16 local 0.0.0.0/0 igw-0abc Unchanged: internet traffic goes to the IGW. Private subnets (app, DB) private route table Destination Target 10.0.0.0/16 local 0.0.0.0/0 nat-0def New: internet traffic goes to the NAT gateway. Traffic to 10.0.x.x matches 10.0.0.0/16 (more specific) and stays local. Everything else matches 0.0.0.0/0.

Figure 6. Public route table unchanged (0.0.0.0/0 to igw-...). Private route table gets 0.0.0.0/0 to nat-...; traffic to 10.0.x.x still matches the more specific local route.

9. Create it in the console

Part A: the NAT gateway

  1. VPC > NAT gateways > Create NAT gateway.
  2. Name my-nat. Subnet: the public subnet web-a.
  3. Connectivity type: Public.
  4. Elastic IP allocation ID: click Allocate Elastic IP.
  5. Create NAT gateway. Wait until State shows Available.

Part B: the private route

  1. Route tables, select the route table of app-b and db-c (in our VPC, the main route table).
  2. Routes > Edit routes > Add route: Destination 0.0.0.0/0, Target NAT Gateway my-nat. Save changes.

Part C: install on the private servers (log in through the bastion with ssh -J)

  1. App server:

    sudo yum install nginx php php-fpm php-mysqlnd -y
    sudo service php-fpm start
    sudo service nginx start
    
  2. DB server:

    sudo yum install mariadb105-server -y
    sudo service mariadb start
    
  3. The downloads now work, and both servers still have no public IP. The setup from the previous chapter, One public server: send .php to the app server, can now be finished.

More on this design in Bastion host (jump server) and NAT gateway.

10. What it costs

  1. A NAT gateway is charged per hour while it exists, and per GB of data it processes.
  2. Its Elastic IP is a public IPv4 address, which is also charged by the hour.
  3. After the lab: delete the NAT gateway, wait until it shows Deleted, then Elastic IPs > Release the address. Remove the 0.0.0.0/0 → NAT route too, or it shows as a blackhole.

11. High availability: two AZs

So far each tier lives in one AZ.

Classroom line

What if this very zone goes down?

If that AZ has an outage, the tier in it is gone, and the whole application is down. High availability means copying the layout into a second AZ:

  1. ap-south-1a: web 10.0.0.0/20, app 10.0.16.0/20, DB 10.0.32.0/20.
  2. ap-south-1b: web 10.0.48.0/20, app 10.0.64.0/20, DB 10.0.80.0/20.
  3. Six subnets in total, still inside 10.0.0.0/16.
  4. One NAT gateway in each AZ's public subnet. Each AZ's private subnets use a private route table that points 0.0.0.0/0 to the NAT gateway in the same AZ, so one AZ failing doesn't cut the other off.

How traffic is spread across the two web servers is the job of a load balancer, in Load balancing.

High availability: the same layout in two AZs VPC 10.0.0.0/16 ap-south-1a web 10.0.0.0/20 · NAT gateway in thisAZ app 10.0.16.0/20 DB 10.0.32.0/20 The app subnet here routes 0.0.0.0/0 to the NAT gateway in thesame AZ. ap-south-1b web 10.0.48.0/20 · NAT gateway in thisAZ app 10.0.64.0/20 DB 10.0.80.0/20 The app subnet here routes 0.0.0.0/0 to the NAT gateway in thesame AZ.

Figure 7. The same web, app and DB subnets in ap-south-1a (10.0.0.0/20, 10.0.16.0/20, 10.0.32.0/20) and ap-south-1b (10.0.48.0/20, 10.0.64.0/20, 10.0.80.0/20), with a NAT gateway in each AZ.

12. When something goes wrong

  1. ssh -J asks for a password or says "Permission denied (publickey)": the key isn't in the agent. Run ssh-add keypair.pem, check with ssh-add -l.
  2. The hop to 10.0.16.7 times out: the app server's SG must allow 22 from the bastion's SG, and you must use the private IP.
  3. yum still times out on the private server: the private route table has no 0.0.0.0/0 → NAT route, or the NAT gateway is in a private subnet, or it isn't Available yet.
  4. The NAT gateway is in the right subnet but nothing goes out: the public subnet's route table must send 0.0.0.0/0 to the internet gateway.
  5. A route shows "Blackhole": its NAT gateway was deleted. Remove the route or point it to a new NAT gateway.
  6. An unexpected bill: a NAT gateway or an unattached Elastic IP left running.

Ravindra Bagale's Tip

Keep the bastion boring: smallest instance type, SSH from your own IP only, nothing else installed, and the key never on it. Use ssh -J or a ProxyJump entry so you never type two hops. And treat the NAT gateway like a meter that is always running: build it for the lab, test, then delete it and release its Elastic IP the same day.

13. The whole class on one page

  1. A private server has two problems: no way in (no public IP) and no way out (no internet route).
  2. An AMI from a public server helps once, but you still can't log in later.
  3. Bastion host: a small public server you SSH through. Bastion SG: 22 from My IP. Private SG: 22 from the bastion SG.
  4. Keep the key on the laptop: ssh-add keypair.pem, then ssh -J ec2-user@<bastion-public-ip> ec2-user@10.0.16.7 or ProxyJump in ~/.ssh/config. Never copy the .pem to the bastion.
  5. Session Manager is the no-SSH option.
  6. Packages spread files over many folders and need dependencies, so yum must download them.
  7. NAT gateway: in a public subnet with an Elastic IP; private servers go out, connections from outside are dropped. AWS-managed, no security group.
  8. Private route table: 0.0.0.0/0 → nat-...; public route table keeps 0.0.0.0/0 → igw-....
  9. Charged per hour and per GB; delete it and release the Elastic IP after the lab.
  10. High availability: the same subnets in two AZs, one NAT gateway per AZ.

14. Try at home

Try at home

Task 1: on paper

  1. Draw the VPC with a bastion, an app server and a DB server. Write the inbound SSH rule of each security group.
  2. Write both route tables after adding a NAT gateway. For 10.0.32.10 and for an internet address, write which route matches.

Task 2: in the console

  1. Using the VPC from the previous chapter, add ssh-add and reach the app server with ssh -J.
  2. Run sudo yum install php -y and watch it time out.
  3. Create my-nat in web-a, add the route, and run it again.

Task 3: prove it and clean up

  1. From the app server run curl ifconfig.me. It prints the NAT gateway's Elastic IP, not a private IP.
  2. Delete the NAT gateway, release its Elastic IP, and remove the blackhole route.

Recap

In short

  1. A bastion host is the one guarded door for SSH into private servers.
  2. The key stays on your laptop: ssh-add and ssh -J.
  3. A NAT gateway lets private servers go out to the internet; nothing from outside comes in.
  4. Put the NAT gateway in a public subnet and point the private route table's 0.0.0.0/0 at it.
  5. It costs money every hour: delete it after the lab.
  6. For high availability, repeat the subnets and the NAT gateway in a second AZ.

Practice Tasks

About this scenario

Vidarbha Cotton Mills is a textiles business in Nagpur. Ganesh Wankhede looks after its IT and cloud work. They count money in INR and run their AWS resources in the ap-south-1 Region. In the tasks below you do real jobs for them using what this lesson teaches. All names, account IDs, IP addresses and numbers are made up for practice.

0 / 12

Easy Create a route table

Why: A route table decides where traffic from a subnet goes.

What to do: Create a route table in vpc-0a3af54c5adeadbee.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Easy Protect an SSH key file

Why: SSH refuses to use a key file that other users can read.

What to do: Make vidarbhacotton-key.pem readable only by its owner and nobody else (no write).

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Connect to an EC2 server

Why: SSH with a key file is the standard way to log in to a Linux EC2 instance.

What to do: Log in to the server 13.180.20.5 as ec2-user using the key vidarbhacotton-key.pem.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Easy Port for SSH

Why: Security group rules need the right port.

What to do: Which TCP port must the security group open for SSH?

How:
  1. Write down the numbers the task gives you.
  2. Work it out step by step on paper.
  3. Type only the final answer in the box.
  4. Click Check answer.

Medium Link a subnet to a route table

Why: Until you associate it, a subnet uses the main route table.

What to do: Associate subnet subnet-00cb3bc54a5bbb1 with route table rtb-00cb3bc54a5bbb22.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Add the internet route

Why: A subnet is "public" only when its route table sends 0.0.0.0/0 to an internet gateway.

What to do: In route table rtb-00cb3bc54a5bbb22, send all internet traffic (0.0.0.0/0) to igw-0cb3bc54a5baa11.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Medium Copy your public key to another server

Why: After this, you can SSH to that server without a password.

What to do: Copy your public key to user ec2-user on 10.0.5.25.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Create an SSH key pair

Why: Passwordless SSH between servers needs a key pair on the first server.

What to do: Create a new SSH key pair of type ed25519.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Medium Upload a file to the server

Why: scp copies files over SSH, so it uses the same key and user.

What to do: Upload index.html from your laptop to /home/ec2-user/ on 13.180.20.5 using vidarbhacotton-key.pem.

How:
  1. Find the Linux command that does this job.
  2. Add the options (like -r or -h) the task needs.
  3. Add the file names, folders or users in the right order.
  4. Use sudo if the job changes system files or users. Click Check answer.

Hard Create a NAT gateway

Why: Private servers use a NAT gateway (in a public subnet) to download updates.

What to do: Create a NAT gateway in public subnet subnet-00cb3bc54a5bbb1 using Elastic IP allocation eipalloc-0cb3bc54a5bdd44.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Hard Route private traffic through NAT

Why: The private route table must send 0.0.0.0/0 to the NAT gateway, not the internet gateway.

What to do: In private route table rtb-00cb3bc54a5bbb22, send 0.0.0.0/0 to NAT gateway nat-0b3bc54a5bcc33.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Hard Allow SSH only from the office

Why: Opening SSH to everyone invites attacks. Allow only your office IP (/32).

What to do: On sg-00ba3ab5465a4c3b2, allow inbound TCP port 22 only from the office IP 49.36.5.10.

How:
  1. Read the task and decide which AWS service it is about.
  2. Write aws, then the service name, then the operation (the action word).
  3. Add each needed flag (--name) followed by its value from the task.
  4. Flags can be in any order. Click Check answer.

Revision, comparison and interview cards

Revise this lesson in a few minutes. Save the cards on your phone or download them as PNG files. The QR code on each card opens this lesson.

Revision card: What is a bastion host?. A private EC2 has no public IP. Your laptop cannot SSH to it directly. A bastion (jump server) is a small server in the public subnet. Its only job is to be the SSH door. SSH to the bastion, then from it to the private IP. Allow port 22 on the private server only from the bastion SG. Bastion public IP 13.2.1.9. App server private IP 10.0.16.7. Only the bastion can reach it.
Revision card: What is a bastion host? · Download PNG
Revision card: SSH through a bastion with -J. Copying keypair.pem onto the bastion is risky. If the bastion is hacked, the key is stolen. -J (ProxyJump) makes SSH hop through the bastion. The key stays on your laptop. Load the key in the agent, then jump in one command. Bastion 13.2.1.9, app 10.0.16.7:
Revision card: SSH through a bastion with -J · Download PNG
Revision card: What is a NAT gateway?. A private server still needs the internet to run yum install or get updates. A NAT gateway lets private servers go OUT to the internet. Nobody from outside can come IN. Create it in a PUBLIC subnet with an Elastic IP. Point the private route table to it. App 10.0.16.7 runs sudo yum install php -y. Traffic leaves through the NAT Elastic IP.
Revision card: What is a NAT gateway? · Download PNG
Revision card: Private route table with NAT. A private subnet has only the local route. Packets to the internet have nowhere to go. A route table is a list: destination and target. 0.0.0.0/0 means "everything else". Add 0.0.0.0/0 with target NAT gateway. Never target the internet gateway here. Private route table:
Revision card: Private route table with NAT · Download PNG
Comparison: Bastion host vs NAT gateway. Bastion host: A small public EC2 used as the SSH door to private servers. NAT gateway: A managed AWS service that gives private servers outbound internet.
Comparison: Bastion host vs NAT gateway · Download PNG
Comparison: ssh -J (ProxyJump) vs ssh -A (agent forwarding). ssh -J (ProxyJump): SSH hops through the bastion to the private server in one command. ssh -A (agent forwarding): Your laptop's SSH agent is made usable on the bastion.
Comparison: ssh -J (ProxyJump) vs ssh -A (agent forwarding) · Download PNG
Comparison: One NAT gateway vs One NAT gateway per AZ. One NAT gateway: A single NAT that all private subnets route to. One NAT gateway per AZ: Each AZ has its own NAT in its own public subnet.
Comparison: One NAT gateway vs One NAT gateway per AZ · Download PNG
Interview card: How do you SSH to an EC2 in a private subnet?. Through a bastion host: a small public server that is the only SSH door into the VPC. The private server keeps no public IP, so it is not exposed to the internet. Allow 22 on the app SG only from the bastion SG, then jump with -J.
Interview card: How do you SSH to an EC2 in a private subnet? · Download PNG
Interview card: Why should you not copy the .pem key onto the bastion?. The bastion faces the internet. It is the most attacked server in the VPC. If it is hacked, the key opens every private server. Keep the key on the laptop: ssh-add, then ssh -J. Agent forwarding -A is a weaker option.
Interview card: Why should you not copy the .pem key onto the bastion? · Download PNG
Interview card: Why can a private EC2 not run yum install, and how do you fix it?. Its route table has only the local route, so it cannot reach package repos. It needs outbound internet but must stay unreachable from outside. Create a NAT gateway in a public subnet and route 0.0.0.0/0 to it.
Interview card: Why can a private EC2 not run yum install, and how do you fix it? · Download PNG
Interview card: How do you make NAT highly available across AZs?. One NAT gateway per AZ, each in that AZ's public subnet. If the AZ with the only NAT fails, private servers in other AZs lose internet too. Give each private route table the NAT of its own AZ.
Interview card: How do you make NAT highly available across AZs? · Download PNG

More cards: Ravi Sir's Cards.