Route Tables & Security Groups Are The Real Security - How I Locked Down My NGold VPC

 I built my VPC. I was proud. VPC 10.0.0.0/16 as fenced compound in Port Harcourt, Public Subnet 10.0.1.0/24 as visitor's parlor, Private Subnet 10.0.2.0/24 as inner room where my database sleeps.

I thought I was done. Then I checked AWS GuardDuty. Red alert: `Unprotected port 22 open to internet`. My EC2 SSH was open to `0.0.0.0/0`. Anybody in the world could brute-force my server.

That's when I learned: VPC and Subnet are just walls. *Route Table and Security Group are the doors and gate men.* Without them, your house is open.

Let me break it down the way I finally understood it after 3 YouTube videos and 2 failed labs.


  1. Route Table — The Road Sign (Where Traffic GOES)

Imagine you are in Mile 3 Park. Buses shouting destinations. Without road signs, you enter wrong bus.

Every subnet in AWS MUST be associated to a Route Table. If not, it uses Main Route Table — which is confusion.


Public Route Table — NGold-Public-RT

Destination |   Target | Meaning

10.0.0.0/16 |   Local    | Talk inside compound only

0.0.0.0/0     |    igw-0a1b2c3d (Internet Gateway) | Anything else? Go to main gate, go to internet

This is why my NGold app at 32.196.145.76:3000 is reachable. Internet -> IGW -> Public RT says "yes, go to Public Subnet" -> hits my EC2.


Private Route Table — NGold-Private-RT

Destination | Target | Meaning

10.0.0.0/16 | Local | Inside talk only

0.0.0.0/0     | nat-0xyz (NAT Gateway in Public) | You want internet for `yum update`? Pass through NAT in public

Important: Private subnet NEVER points to IGW directly. If you do, it's not private anymore. It becomes public. That's the mistake I did on Day 6 — I attached IGW to both, so my database was technically public.


How I created it — Step by Step for beginners:

1. VPC Console > Route Tables > Create Route Table

2. Name: `NGold-Public-RT`, VPC: Select `NGold-VPC (10.0.0.0/16)`

3. After create, click Routes > Edit Routes > Add Route: Destination `0.0.0.0/0`, Target -> Internet Gateway -> Select your IGW -> Save

4. Now Subnet Associations > Edit > Select Public Subnet `10.0.1.0/24` > Save

5. Repeat for Private: Create `NGold-Private-RT`, Route `0.0.0.0/0` to NAT Gateway, Associate to Private Subnet `10.0.2.0/24`.

Pro Tip: Always check `Explicit Subnet Association`. If it says 0, you are using Main RT — danger.


  1.  Security Group — The Bouncer (Who is ALLOWED)

Now, Route Table tells WHERE. Security Group tells WHO. 

Security Group is stateful. Means if you allow inbound 80, outbound for that request is automatically allowed. Unlike NACL which is stateless and confusing.

For NGold, I now use 2 SGs. Not one.


A) Public SG — `NGold-Public-SG` — For my EC2 hosting NGold Frontend

Inbound Rules — This is entrance:

  • SSH Port 22: `102.89.XXX.XXX/32` (My Glo IP) — NOT 0.0.0.0/0! I go to http://whatismyip.com every morning because Nigerian ISP changes IP. If I use 0.0.0.0/0, bots from China try login every minute. I saw 400 attempts in `/var/log/auth.log`.
  • HTTP Port 80: `0.0.0.0/0` — Everyone can view website
  •  Custom TCP 3000: `0.0.0.0/0` — For NGold React app, later I will close this and use only 80 via Nginx reverse proxy
  •  HTTPS 443: `0.0.0.0/0` — For future SSL


Outbound: All traffic `0.0.0.0/0` — My app needs to call TMDB API for movies, so it must go out.


B) Private SG — `NGold-Private-SG` — For my RDS / MySQL in private subnet

This is where most juniors fail. They open 3306 to 0.0.0.0/0.


My rule:

  • MySQL Port 3306: Source = `NGold-Public-SG (sg-0abc123)` — Only EC2 that has Public SG can talk to DB. Not IP, but SG ID. So even if someone launches EC2 in my VPC, if it doesn't have that SG, no DB access.
  •  Outbound: None needed.

Result: Even if you know my private DB IP 10.0.2.15, nmap will show filtered. You can't connect. Only my NGold app can.

Parcourt Analogy that helped me:

  •  Route Table = Danfo driver at Rumuokoro shouting "Rumuola, Rumuola!" It directs traffic.
  •  Security Group = Estate gate man at Peter Odili Road estate. He checks ID: "Who you know for here?" If you no know anybody (SG not allowed), you no enter.

You need both.

My Before vs After

Before Day 9:

- One Route Table for all subnets

- One SG: All ports open 0.0.0.0/0

- Cost: GuardDuty alert, $0.50 daily for scanning

After Day 9:

- 2 Route Tables, proper IGW/NAT separation

- 2 SGs, least privilege

- Test: I tried SSH from my friend's laptop (different IP) — Connection timed out. Perfect! Then I added his IP /32 temporarily and it worked. Security is working.


Quick Commands to Debug

# Check which RT your subnet uses

aws ec2 describe-route-tables --filters Name=association.subnet-id,Values=subnet-0abc

# Check SG

aws ec2 describe-security-groups --group-ids sg-0abc123


# On EC2, check if NAT works from private (via SSM)

ping 8.8.8.8  # Should work via NAT

curl ifconfig.me # Should show NAT's public IP, not private IP.

Now that I understand manually, I will destroy this click-ops and write it in Terraform. Because if I click 20 times every time, I will make mistake again. Infrastructure as Code is the way.


Where would you love me to help explain even more for better understanding?

Comment below or contact me.


Read Day 8: What Subnet, IGW & NAT taught me   https://sirvick-deployments.blogspot.com/2026/09/i-was-deploying-wrong-what-subnet.html      

Comments

Popular posts from this blog

How I Fixed GitHub Error: failed to push some refs to github.com in 2026

How I Deployed Netflix Clone (NGold) to AWS EC2 - Live at 32.196.145.76:3000 [Docker + Nginx Guide]

Understanding Linux Environment — The Real Engine Behind My AWS Cloud Deployment