<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrea</title>
    <description>The latest articles on DEV Community by Andrea (@andr_e).</description>
    <link>https://dev.to/andr_e</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3173163%2F1fb181e0-ad4a-463e-87c8-25c93379f595.png</url>
      <title>DEV Community: Andrea</title>
      <link>https://dev.to/andr_e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andr_e"/>
    <language>en</language>
    <item>
      <title>Learning Live Data Systems through an F1 Telemetry Project</title>
      <dc:creator>Andrea</dc:creator>
      <pubDate>Mon, 08 Dec 2025 15:53:50 +0000</pubDate>
      <link>https://dev.to/andr_e/learning-live-data-systems-through-an-f1-telemetry-project-47fg</link>
      <guid>https://dev.to/andr_e/learning-live-data-systems-through-an-f1-telemetry-project-47fg</guid>
      <description>&lt;p&gt;Overview — From Confusion to Clarity&lt;/p&gt;

&lt;p&gt;This project started from a simple desire: I wanted to build something around data collection. At the time, I was deep into observability and monitoring, but somehow I ended up in the world of telemetry — mainly because I was learning a lot about Formula 1 and my interest kept growing every day.&lt;/p&gt;

&lt;p&gt;What began as curiosity quickly turned into a challenge. I was a brand-new F1 fan trying to build a system I barely understood. I got confused many times, but I noticed something important: I kept making small milestones, and each one pushed me forward.&lt;/p&gt;

&lt;p&gt;Slowly, the project gained momentum. The architecture started becoming clear. Pieces that looked impossible at first suddenly made sense. And by the time the system was running, I realized I had learned more than I expected.&lt;/p&gt;

&lt;p&gt;In this blog, I’ll share the core lessons, the architecture decisions, and the parts of the system that were the most fun and surprising to build. So read along on how a simple idea grew into a complete system. The main sections will walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The backend layer in Python &lt;/li&gt;
&lt;li&gt;The FastAPI + WebSocket connection that keeps everything flowing in real-time&lt;/li&gt;
&lt;li&gt;The frontend side where the data is received, visualized, and even sent back to the simulation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  BACKEND
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Designing the Simulation Backend — Where the Real Engineering Began&lt;/strong&gt;&lt;br&gt;
The backend was really the heart of this whole project, because that’s where the &lt;em&gt;car logic&lt;/em&gt; lived. This is the part that actually generated the data I wanted to visualize. And honestly, it didn’t start nicely at all , it began with a few random files where I was just trying ideas, tricks, and anything that came to mind. The architecture looked odd, messy, and unclear.&lt;/p&gt;

&lt;p&gt;Every new day I would wake up and try again. Then one day, everything clicked — not magically, but because all the messy files I’d been building finally connected into a clear picture.&lt;/p&gt;

&lt;p&gt;I realized the simulation needed to start &lt;strong&gt;exactly how a real car starts&lt;/strong&gt; — from RPM ignition, to engine warm-up, to getting on track. That single insight made the structure fall into place. Before that moment, I was just wandering. After that, the direction became obvious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Lesson That Changed Everything: State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The biggest clarity I gained was around &lt;strong&gt;state&lt;/strong&gt;. State is what made the backend truly integrated , it allowed different modules to share data smoothly. Without state, the whole simulation would have collapsed.&lt;/p&gt;

&lt;p&gt;Take the &lt;strong&gt;engine&lt;/strong&gt; for example. This was one of the most fun parts to build.&lt;/p&gt;

&lt;p&gt;I started with the &lt;strong&gt;ECU module&lt;/strong&gt; (Engine Control Unit). Before this project, I didn’t even know what the ECU really did. Then I learned: it’s the “computer” that controls things like the fuel-air ratio and makes combustion efficient. Cool stuff.&lt;/p&gt;

&lt;p&gt;Now, of course, I couldn’t put all combustion logic &lt;em&gt;inside&lt;/em&gt; the ECU file. That would create a monster file. So I modularized:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ecu.py&lt;/code&gt; handles requests and ratios&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fuel.py&lt;/code&gt; handles supply&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;combustion.py&lt;/code&gt; handles burn logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this is where &lt;strong&gt;state&lt;/strong&gt; became the hero.&lt;/p&gt;

&lt;p&gt;The ECU doesn’t produce final fuel burn numbers — it produces a &lt;strong&gt;fuel request&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That request is stored in &lt;code&gt;state&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then the &lt;strong&gt;fuel module&lt;/strong&gt; reads that request, deducts the amount, and updates the state again.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;combustion module&lt;/strong&gt; reads the updated state and performs the burn.&lt;/p&gt;

&lt;p&gt;It became a smooth pipeline because every module could &lt;em&gt;ask from state&lt;/em&gt; and &lt;em&gt;feed into state&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;State became a global dictionary that acted like the car’s shared memory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before using state, I was writing extremely long files. My first engine file had almost all logic mashed together. It was unmanageable. Once I introduced modularization + shared state, the backend finally felt like a real system, not a pile of experiments.&lt;/p&gt;

&lt;p&gt;This part of the project taught me one of my biggest lessons:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Long simulations only work when every module has memory. Stateless code collapses under complexity.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0w6pqx03m6xsnmegxza5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0w6pqx03m6xsnmegxza5.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqljiqwr8di29qnent7ta.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqljiqwr8di29qnent7ta.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fil5htzrr7086akutbbre.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fil5htzrr7086akutbbre.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How &lt;code&gt;self&lt;/code&gt; Saved the Simulation (Timing, Sectors, and Real F1 Logic)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After state, the next thing that completely changed the backend architecture was &lt;strong&gt;&lt;code&gt;self&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At first it felt confusing, but it became the hidden hero of the whole timing system.&lt;/p&gt;

&lt;p&gt;In real F1 broadcasts, you always hear commentators saying things like &lt;em&gt;“Lando purple in sector 3!”&lt;/em&gt; or &lt;em&gt;“That’s a green sector for Hamilton.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So when I was building the timing module, I wanted to simulate exactly that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Purple&lt;/strong&gt; → best overall&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green&lt;/strong&gt; → driver’s personal best&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yellow&lt;/strong&gt; → anything else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To even do this properly, I had to map the Silverstone track into segments and define sector boundaries.&lt;/p&gt;

&lt;p&gt;But here’s where it gets real:&lt;/p&gt;

&lt;p&gt;you can &lt;strong&gt;only&lt;/strong&gt; calculate “sector 1 time” &lt;em&gt;after&lt;/em&gt; you cross into sector 2.&lt;/p&gt;

&lt;p&gt;And this is exactly where &lt;code&gt;self&lt;/code&gt; shows its power.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;self&lt;/code&gt; is what lets the simulation remember things &lt;em&gt;between ticks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Python runs the simulation in loops (“ticks”), so for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In &lt;strong&gt;tick 1&lt;/strong&gt;, the car is in sector 1.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;tick 2&lt;/strong&gt;, it moves into sector 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the timing file, we must compare:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what sector we were in before → &lt;code&gt;self.previous_sector&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;what sector we are in now → &lt;code&gt;current_sector&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only &lt;strong&gt;self&lt;/strong&gt; can hold that previous-sector info automatically without relying on some huge manual “previous values” text file or awkward global variables.&lt;/p&gt;

&lt;p&gt;Same thing for sector times — we store the timestamp when a sector starts, and when the next sector begins, &lt;code&gt;self&lt;/code&gt; is what lets us compute the exact duration of the previous one.&lt;/p&gt;

&lt;p&gt;So in short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;state&lt;/strong&gt; holds global shared values across all modules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;self&lt;/strong&gt; holds the memory &lt;em&gt;inside&lt;/em&gt; the module across ticks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination basically made the timing system feel like a real F1 sector tracker instead of a random counter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff46a22ftho1v3zn3vq6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff46a22ftho1v3zn3vq6q.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpv3bgo0enblh66k7eb81.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpv3bgo0enblh66k7eb81.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7slbp126sewoa3oyc94h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7slbp126sewoa3oyc94h.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Everything Runs Together (Classes, &lt;code&gt;__init__&lt;/code&gt;, and the Main Loop)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So after understanding &lt;strong&gt;state&lt;/strong&gt; and &lt;strong&gt;self&lt;/strong&gt;, the final piece that made everything make sense was how all these modules actually &lt;em&gt;run together&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Python looks simple, but when you’re combining many modules, classes, and a global state, it becomes wide pretty fast.&lt;/p&gt;

&lt;p&gt;To keep it easy, let’s stick with the timing example again.&lt;/p&gt;

&lt;p&gt;Every module in the backend was built as a &lt;strong&gt;class&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the class has an &lt;code&gt;__init__()&lt;/code&gt; → where all the &lt;code&gt;self&lt;/code&gt; values get initialized&lt;/li&gt;
&lt;li&gt;then it has an &lt;code&gt;update()&lt;/code&gt; → which runs every tick and changes those values&lt;/li&gt;
&lt;li&gt;and the class takes &lt;code&gt;state&lt;/code&gt; so it can read/write shared info&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the idea is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/strong&gt; = sets up all the memory the module needs (sector numbers, lap counters, flags…)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;update&lt;/code&gt;&lt;/strong&gt; = does the logic every tick (detect sector change, update times, apply color logic…)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But none of these modules do anything alone.&lt;/p&gt;

&lt;p&gt;Everything comes to life in &lt;strong&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That’s the file where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;all modules (engine, brakes, tires, timing, weather…) are &lt;strong&gt;imported&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;each one is &lt;strong&gt;instantiated as a class&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;the big &lt;strong&gt;state dictionary&lt;/strong&gt; is created&lt;/li&gt;
&lt;li&gt;a loop runs all classes’ &lt;code&gt;update()&lt;/code&gt; functions in order&lt;/li&gt;
&lt;li&gt;and the final processed data gets streamed out through WebSocket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s basically like assembling the whole car:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;engine module runs&lt;/li&gt;
&lt;li&gt;brakes run&lt;/li&gt;
&lt;li&gt;aero runs&lt;/li&gt;
&lt;li&gt;tires update&lt;/li&gt;
&lt;li&gt;timing calculates&lt;/li&gt;
&lt;li&gt;weather injects external data&lt;/li&gt;
&lt;li&gt;state keeps everyone connected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt; is the “race director” — it calls each module every tick so everything stays synchronized.&lt;/p&gt;

&lt;p&gt;This structure made the backend feel organized, realistic, and easy to extend when new ideas popped up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy3b584qdfci55pkj67s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwy3b584qdfci55pkj67s.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvsx90jsbeq9f1o92crwd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvsx90jsbeq9f1o92crwd.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qeqbrpcu3sbyxo0nsvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qeqbrpcu3sbyxo0nsvv.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FastAPI Server &amp;amp; WebSocket Connection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To stream live telemetry, we needed something more than regular HTTP requests like Axios — we required &lt;strong&gt;persistent connections&lt;/strong&gt;. That’s where FastAPI with WebSockets came in.&lt;/p&gt;

&lt;p&gt;We created a &lt;strong&gt;server file&lt;/strong&gt; that defines the FastAPI app and sets up the WebSocket endpoint. This connection is what &lt;strong&gt;allows data to flow continuously&lt;/strong&gt; from our Python simulation to the frontend.&lt;/p&gt;

&lt;p&gt;Here, the &lt;strong&gt;&lt;code&gt;state&lt;/code&gt; object became central&lt;/strong&gt;. Instead of sending piecemeal data, we just import &lt;code&gt;state&lt;/code&gt; and push it as a &lt;strong&gt;full object&lt;/strong&gt; to the frontend. This keeps the architecture neat and makes the flow predictable.&lt;/p&gt;

&lt;p&gt;In addition, we added some logic around &lt;strong&gt;session duration and modes&lt;/strong&gt;, like RACE or P1. That’s where the timing control sits — it governs how long the simulation runs for each mode, adding a bit of realism and fun to the simulation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gc8wgdecoiv723ep4jw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9gc8wgdecoiv723ep4jw.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xvgzz8qvywtkvytjpk0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xvgzz8qvywtkvytjpk0.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting to the Backend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;On the frontend, to receive live telemetry, we needed a &lt;strong&gt;persistent connection&lt;/strong&gt; to the backend. Using WebSockets, we could &lt;strong&gt;stream data continuously&lt;/strong&gt;, allowing charts, timers, and other UI components to update in real time without reloading or polling.&lt;/p&gt;

&lt;p&gt;To simulate the &lt;strong&gt;strategist role in F1 teams&lt;/strong&gt;, we also sent messages back through the same WebSocket. This included commands like &lt;strong&gt;pit calls, ERS deployment decisions, and other strategy inputs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;With this &lt;strong&gt;bi-directional flow&lt;/strong&gt;, the simulation felt lively. The frontend could now &lt;strong&gt;react to live car data&lt;/strong&gt;, and at the same time, strategy inputs could influence the backend simulation — covering key aspects of real F1 logic in a simplified but interactive way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwnemnml4do9wpmyd3t3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffwnemnml4do9wpmyd3t3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhdgp48iopd1mxrd98bq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhdgp48iopd1mxrd98bq.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuwcnvjs94bkmqfwg230.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuwcnvjs94bkmqfwg230.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the UI Started to Feel Like a Race Weekend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the stage where the project really came to life. Since everything was running on localhost, I couldn’t just dump all the data in at once — my machine would struggle. So the focus shifted to &lt;strong&gt;visualizing live data as it streamed in&lt;/strong&gt;, similar to what you see on real F1 dashboards.&lt;/p&gt;

&lt;p&gt;The first big UI piece was the &lt;strong&gt;live charts&lt;/strong&gt;. After watching a lot of F1 analysis content, I noticed how often wheel-speed and telemetry overlays rely on line charts. That’s why I went with Recharts. I built a reusable chart component that could plot wheel speed or any y axis values over time and also mark sector boundaries. It wasn’t just about seeing the speed — it showed &lt;em&gt;where&lt;/em&gt; on the track each spike or drop happened. That little link between sectors and speed made the simulation feel closer to real F1 telemetry.&lt;/p&gt;

&lt;p&gt;Then there was the &lt;strong&gt;timer counter&lt;/strong&gt; with the proper F1 coloring logic. I implemented the same color system used in actual broadcasts: purple for overall best, green for personal best, and yellow for the rest. Seeing those colors update live made the sessions feel more authentic.&lt;/p&gt;

&lt;p&gt;I also integrated &lt;strong&gt;pit logic&lt;/strong&gt;, so you could simulate in-laps, out-laps, and strategy moments. And on top of that, I added &lt;strong&gt;weather data&lt;/strong&gt; from an API (Meteo), giving the UI small touches like conditions you’d normally see on an F1 dashboard.&lt;br&gt;
Those three—&lt;strong&gt;charts&lt;/strong&gt;, &lt;strong&gt;timers&lt;/strong&gt;, and &lt;strong&gt;weather/pit logic&lt;/strong&gt;—became the main pillars of the frontend&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;br&gt;
This whole project was really just a leap of faith. I didn’t start with a clear picture or some perfect plan — I just started. And day by day, things slowly made more sense. Every file I wrote, every bug I chased, every small breakthrough added up. Looking back, I’m genuinely thankful to God for that rhythm of trying again each day, because that’s what carried the project from confusion to clarity.&lt;/p&gt;

&lt;p&gt;I ended up learning way more than I expected — not just about Python or telemetry or websockets, but about how to think, how to break problems down, and how to keep going when nothing makes sense yet. And those lessons are things I’m still applying even now.&lt;br&gt;
So yeah… what began as a simple idea turned into something that actually works, something I genuinely enjoyed building, and something that built a stronger mindset in the process.&lt;/p&gt;

&lt;p&gt;I hope you get the same kind of value from reading this as I got from building it — even if it’s just one idea, one perspective, or one spark to start your own project.&lt;/p&gt;

</description>
      <category>python</category>
    </item>
    <item>
      <title>Inside the University Cloud: Solving Real-World Networking Challenges</title>
      <dc:creator>Andrea</dc:creator>
      <pubDate>Tue, 02 Dec 2025 15:46:14 +0000</pubDate>
      <link>https://dev.to/truenorth/inside-the-university-cloud-solving-real-world-networking-challenges-45j0</link>
      <guid>https://dev.to/truenorth/inside-the-university-cloud-solving-real-world-networking-challenges-45j0</guid>
      <description>&lt;p&gt;&lt;strong&gt;Overview&lt;/strong&gt;&lt;br&gt;
The university is launching a new &lt;strong&gt;Student Portal&lt;/strong&gt; to give learners a seamless digital experience. Through this portal, students will be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;View grades&lt;/li&gt;
&lt;li&gt;Access course materials&lt;/li&gt;
&lt;li&gt;Pay tuition fees&lt;/li&gt;
&lt;li&gt;Submit assignments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To support this rollout, &lt;strong&gt;James&lt;/strong&gt;, the Cloud Engineer, and &lt;strong&gt;Sarah&lt;/strong&gt;, the School’s Solutions Architect, were tasked with building a secure and scalable architecture on AWS — the chosen cloud provider for its reliability and customer-obsessed design.&lt;/p&gt;

&lt;p&gt;They began by setting up the core networking environment:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;VPC&lt;/strong&gt; to isolate all university resources&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;public subnet&lt;/strong&gt; for externally reachable components&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;private subnet&lt;/strong&gt; for backend application servers&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;Internet Gateway (IGW)&lt;/strong&gt; attached to the VPC&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;route table&lt;/strong&gt; to enable internet access for the public subnet&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;EC2 instance&lt;/strong&gt; in the private subnet hosting the student portal backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway to&lt;/strong&gt; Allows private servers to reach the internet &lt;em&gt;without being exposed&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Appropriate &lt;strong&gt;security groups&lt;/strong&gt; to regulate inbound and outbound traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network ACLs (NACLs)&lt;/strong&gt; to provide an additional layer of subnet-level security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before finalizing the setup, James added a &lt;strong&gt;bastion host&lt;/strong&gt; in the public subnet to allow secure SSH access into the private EC2 instances for administration, debugging, and deployments.&lt;/p&gt;

&lt;p&gt;With all foundational components in place, the team prepared to launch the portal and validate that every part of the networking environment worked as intended.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Architecture&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rwbvj2wwu3kjri5e6tw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9rwbvj2wwu3kjri5e6tw.png" alt=" " width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The SetUp&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create a New VPC&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log in to the AWS Management Console&lt;/strong&gt; using your credentials.&lt;br&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; Always create and use an &lt;strong&gt;IAM user&lt;/strong&gt; with the necessary permissions rather than using the root account. This ensures &lt;strong&gt;least-privilege access&lt;/strong&gt; and improves security.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the search bar at the top, type &lt;strong&gt;“VPC”&lt;/strong&gt; and select &lt;strong&gt;Your VPC&lt;/strong&gt; from the sidebar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &lt;strong&gt;VPC Dashboard&lt;/strong&gt;, you will see a &lt;strong&gt;default VPC&lt;/strong&gt;. For this lab, create a &lt;strong&gt;new VPC&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Create VPC&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a name, e.g., web-portal-VPC.&lt;/li&gt;
&lt;li&gt;Assign an &lt;strong&gt;IPv4 CIDR block&lt;/strong&gt; of 10.0.0.0/16.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; Use &lt;strong&gt;tags&lt;/strong&gt; to label your resources — this helps organize and identify VPCs later, especially in environments with multiple resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leave the rest of the options as default and click &lt;strong&gt;Create VPC&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After creation, you will see your new VPC listed in the dashboard.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0d6v1ke8e16bxr77il7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh0d6v1ke8e16bxr77il7.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Create a Public Subnet&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the &lt;strong&gt;VPC dashboard sidebar&lt;/strong&gt;, click &lt;strong&gt;Subnets&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Subnet&lt;/strong&gt; at the top of the page.&lt;/li&gt;
&lt;li&gt;In the creation form:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Select the VPC&lt;/strong&gt; you created in Step 1 webportal`-VPC. You will see its IPv4 CIDR block 10.0.0.0/16.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;CIDR Calculation &amp;amp; Subnet Planning&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- VPC CIDR: 10.0.0.0/16 → 32 is the total bits so it’s ( 32-16) =16 bits fixed, 16 bits available → 2^16 = 65,536 IP addresses.
- Subnet CIDR: 10.0.0.0/24 → 32 is the total bits so it’s ( 32-24)=24 bits fixed, 8 bits available → 2^8 = 256 IP addresses.

How to choose subnet ranges:
- Each subnet’s CIDR must be **within the VPC CIDR**.
- Plan enough IPs for all resources in that subnet (EC2s, NAT, load balancers, etc.).
- Keep future growth in mind — don’t allocate too small a range if you may add more instances later.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Name your subnet&lt;/strong&gt;: e.g., Public-Subnet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select an Availability Zone (AZ)&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;AWS regions (e.g., &lt;strong&gt;Africa (Cape Town)&lt;/strong&gt;) contain multiple &lt;strong&gt;AZs&lt;/strong&gt; — isolated data centers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; Using AZs improves &lt;strong&gt;high availability&lt;/strong&gt;. 
If one AZ goes down, workloads can continue in another AZ. Imagine students all over the world hitting the portal at the same time — if only one AZ handled all traffic, it could overload. Using multiple AZs ensures the system can &lt;strong&gt;distribute load&lt;/strong&gt; and remain operational even under high demand.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Assign a &lt;strong&gt;CIDR block for the subnet&lt;/strong&gt;: e.g., &lt;code&gt;10.0.0.0/24&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; Subnet CIDR must be &lt;strong&gt;within the VPC’s CIDR&lt;/strong&gt;. &lt;code&gt;/24&lt;/code&gt; gives 256 IP addresses (usable for EC2s, NAT, and other resources).&lt;/li&gt;
&lt;li&gt;This allows you to plan for growth without overlapping other subnets.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Leave the rest of the options as default and click &lt;strong&gt;Create subnet&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrt7b44wc17jlcta2hqz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrt7b44wc17jlcta2hqz.png" alt=" " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Launch an EC2 Instance in the Public Subnet&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hint / Analogy for EC2 and Instance Setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2:&lt;/strong&gt; Think of an EC2 instance as your &lt;strong&gt;server in the cloud&lt;/strong&gt; — it’s where your &lt;strong&gt;application actually runs&lt;/strong&gt;, like a web portal server. This is similar to your &lt;strong&gt;laptop&lt;/strong&gt;, which runs applications locally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMI (Amazon Machine Image):&lt;/strong&gt; The AMI is like the &lt;strong&gt;operating system on your laptop&lt;/strong&gt; (e.g., Windows or Linux) along with some pre-installed software. It sets the base environment so your application can run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance Type:&lt;/strong&gt; This is like the &lt;strong&gt;hardware specification of your laptop&lt;/strong&gt; — how much &lt;strong&gt;CPU, memory, and storage&lt;/strong&gt; it has. Choosing the right instance type ensures your application performs well under expected load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Additional Hint for Scaling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This instance hosts the &lt;strong&gt;front-facing portal and all services&lt;/strong&gt; in our lab. For small labs, one EC2 is enough.&lt;/li&gt;
&lt;li&gt;In real-world scenarios, if thousands of students are using the portal simultaneously (e.g., during exams), you can &lt;strong&gt;add more EC2 instances&lt;/strong&gt; or scale up/down dynamically to handle traffic while optimizing costs.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In the &lt;strong&gt;AWS Management Console&lt;/strong&gt;, use the &lt;strong&gt;search bar&lt;/strong&gt; to type &lt;strong&gt;EC2&lt;/strong&gt; and select it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On the sidebar, click &lt;strong&gt;Instances&lt;/strong&gt;, then click &lt;strong&gt;Launch Instances&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the instance basics&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;Web-Portal&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AMI (Amazon Machine Image):&lt;/strong&gt; Select an appropriate AMI, e.g., Amazon Linux 2023.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instance type:&lt;/strong&gt; Choose a small instance like &lt;code&gt;t3.micro&lt;/code&gt;. &lt;br&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; Instance type determines CPU, memory, and network capacity. For a lab web portal, a small instance is enough. In real scenarios, consider traffic load, student activity, and portal features.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Key pair:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new key pair called &lt;code&gt;webportal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; This key allows you to &lt;strong&gt;SSH into your EC2&lt;/strong&gt; — necessary for administrative tasks, checking logs, or debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Networking:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Edit&lt;/strong&gt; under Networking.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;VPC&lt;/strong&gt; you created (&lt;code&gt;Web-Portal&lt;/code&gt; VPC).&lt;/li&gt;
&lt;li&gt;Choose the &lt;strong&gt;Public Subnet&lt;/strong&gt; you created earlier.&lt;/li&gt;
&lt;li&gt;Enable &lt;strong&gt;Auto-assign Public IP&lt;/strong&gt;.
&lt;strong&gt;Hint:&lt;/strong&gt; Assigning a public IP lets the instance be accessible from the internet &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security Group:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new security group called &lt;code&gt;WebPortal-SG&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hint:&lt;/strong&gt; Security groups control inbound and outbound traffic. In production, this is dynamic and carefully managed; for this lab, keep it simple.&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;Inbound Rule&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Type: SSH (22)&lt;/li&gt;
&lt;li&gt;Source: &lt;strong&gt;My IP&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt; This ensures only your machine can SSH into the instance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Review your configuration and click &lt;strong&gt;Launch Instance&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After launch, the instance will appear in the &lt;strong&gt;Instances dashboard&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can see its &lt;strong&gt;Status&lt;/strong&gt;, &lt;strong&gt;Public IPv4&lt;/strong&gt;, and &lt;strong&gt;Private IPv4&lt;/strong&gt; addresses.&lt;/li&gt;
&lt;li&gt;Clicking the instance allows you to explore more details, like network interfaces and security groups.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17308qs4gcmlfysr7tyd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17308qs4gcmlfysr7tyd.png" alt=" " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Create an Internet Gateway&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;strong&gt;AWS Console&lt;/strong&gt;, search for &lt;strong&gt;VPC&lt;/strong&gt; and select it.&lt;/li&gt;
&lt;li&gt;On the sidebar, click &lt;strong&gt;Internet Gateways&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Internet Gateway&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;webportal-IGW&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Leave other settings as default.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;After creation, the IGW is &lt;strong&gt;available&lt;/strong&gt; but not yet attached to the VPC.&lt;/li&gt;
&lt;li&gt;Select the IGW and click &lt;strong&gt;Attach to VPC&lt;/strong&gt;, then choose the &lt;strong&gt;Lab VPC&lt;/strong&gt; you created.
&lt;strong&gt;Hint:&lt;/strong&gt; Now the public subnet can &lt;strong&gt;send and receive traffic from the internet&lt;/strong&gt;, allowing students to reach the portal.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkhsqx90tqh9tfnkhakg0.png" alt=" " width="800" height="346"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 5: to create the route table:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;strong&gt;AWS Console&lt;/strong&gt;, search for &lt;strong&gt;VPC&lt;/strong&gt; and click &lt;strong&gt;Route Tables&lt;/strong&gt; on the sidebar.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create Route Table&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;Public-RT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; Select &lt;code&gt;webportal VPC&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;After creation, select the route table, go to the &lt;strong&gt;Routes&lt;/strong&gt; tab, and click &lt;strong&gt;Edit routes&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;Add a route:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Destination:&lt;/strong&gt; &lt;code&gt;0.0.0.0/0&lt;/code&gt; (this means all traffic to the internet)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target:&lt;/strong&gt; &lt;code&gt;Lab-IGW&lt;/code&gt; (the internet gateway you created in Step 4)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save the route.&lt;br&gt;
&lt;strong&gt;Associate the Route Table with the Public Subnet:&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go to the &lt;strong&gt;Subnet Associations&lt;/strong&gt; tab of the route table.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &lt;strong&gt;Edit subnet associations&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the &lt;strong&gt;public subnet&lt;/strong&gt; you created in Step 2 and save.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now your public subnet knows that &lt;strong&gt;all internet-bound traffic&lt;/strong&gt; should go through the IGW.&lt;/li&gt;
&lt;li&gt;This completes the path for students to reach the portal: browser → ALB (or EC2) → public subnet → IGW → internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yg971wr2apn5g0w7gq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yg971wr2apn5g0w7gq6.png" alt=" " width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Progress so far&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Understanding the Flow: Public Subnet and Internet Gateway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How traffic flows for the student portal:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A student opens a browser and searches for &lt;code&gt;portal.com&lt;/code&gt; (our student portal).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;DNS resolves&lt;/strong&gt; &lt;code&gt;portal.com&lt;/code&gt; to an &lt;strong&gt;AWS resource&lt;/strong&gt;. In a production setup, this would be the &lt;strong&gt;ALB (Application Load Balancer)&lt;/strong&gt; DNS name, e.g., &lt;code&gt;aws-1234fe-alb.amazonaws.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;ALB receives the request&lt;/strong&gt; and forwards it to one of the &lt;strong&gt;EC2 instances&lt;/strong&gt; in the &lt;strong&gt;public subnet&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;At this stage, the request has already reached the EC2 via the &lt;strong&gt;internet and IGW&lt;/strong&gt;; the subnet’s route table is &lt;strong&gt;not involved for incoming traffic&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Note: A subnet is not public until an &lt;strong&gt;Internet Gateway (IGW)&lt;/strong&gt; is attached and the route table sends traffic to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet Gateway (IGW)&lt;/strong&gt; ensures that EC2 in the public subnet can communicate with the internet. Incoming requests from students are received via the IGW.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outbound traffic / responses:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;When the EC2 instance responds to the student’s browser, the &lt;strong&gt;route table associated with the subnet&lt;/strong&gt; checks the destination (the student’s public IP) and directs the response &lt;strong&gt;through the IGW&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Without the route table pointing &lt;code&gt;0.0.0.0/0&lt;/code&gt; to the IGW, responses wouldn’t reach the internet.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;This completes the round-trip: &lt;strong&gt;Student browser → ALB → EC2 → public subnet → IGW → student browser&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route tables primarily control &lt;strong&gt;where traffic leaving the subnet goes&lt;/strong&gt;, and for incoming using the local route&lt;/li&gt;
&lt;li&gt;The IGW + route table combination makes a subnet &lt;strong&gt;“public”&lt;/strong&gt;, meaning its instances can respond to internet requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public subnet&lt;/strong&gt; + &lt;strong&gt;IGW&lt;/strong&gt; = internet-facing resources.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farpf877k4low7jusqha3.jpg" alt=" " width="736" height="552"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Server Side Setup&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Kindly note: On the &lt;strong&gt;server-side (private subnet)&lt;/strong&gt; you won’t see UI screenshots here. The setup steps are mostly the &lt;strong&gt;same as the public-side resources&lt;/strong&gt;, with only a few key differences we’ve already highlighted. Keep reading through — everything follows the same pattern.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Creating a Private Subnet, NAT Gateway, and EC2 for Backend Services&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that the &lt;strong&gt;student-facing application&lt;/strong&gt; is running on a public subnet, we need to handle backend services that should &lt;strong&gt;not be publicly accessible&lt;/strong&gt;, such as the &lt;strong&gt;grading system&lt;/strong&gt; or other sensitive components of the student portal. These services must remain private to prevent direct access from the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a Private Subnet&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;strong&gt;VPC&lt;/strong&gt; dashboard in the AWS console.&lt;/li&gt;
&lt;li&gt;On the sidebar, click &lt;strong&gt;Subnets&lt;/strong&gt; → &lt;strong&gt;Create Subnet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;VPC&lt;/strong&gt; you created earlier (Web portal VPC).&lt;/li&gt;
&lt;li&gt;Name the subnet &lt;strong&gt;Private Subnet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Choose an &lt;strong&gt;Availability Zone (AZ)&lt;/strong&gt;. Hint: Using AZs improves &lt;strong&gt;high availability&lt;/strong&gt;—if one AZ goes down or is overloaded, workloads continue in another AZ. For example, if many students hit the portal simultaneously, multi-AZ ensures traffic is distributed.&lt;/li&gt;
&lt;li&gt;Assign a &lt;strong&gt;CIDR block&lt;/strong&gt; that:

&lt;ul&gt;
&lt;li&gt;Falls within your VPC range (&lt;code&gt;10.0.0.0/16&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Does &lt;strong&gt;not overlap&lt;/strong&gt; with the public subnet.(&lt;code&gt;10.0.0.0/23&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Leaves space for growth (e.g., if you anticipate 254 addresses, choose &lt;code&gt;/25&lt;/code&gt; or &lt;code&gt;/23&lt;/code&gt; depending on needs).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2. Create a NAT Gateway&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the sidebar, click &lt;strong&gt;NAT Gateways&lt;/strong&gt; → &lt;strong&gt;Create NAT Gateway&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;public subnet&lt;/strong&gt; for the NAT Gateway. Hint: NAT must live in a &lt;strong&gt;public subnet&lt;/strong&gt; to access the internet.&lt;/li&gt;
&lt;li&gt;Assign an &lt;strong&gt;Elastic IP (EIP)&lt;/strong&gt;. Hint: EIP ensures a &lt;strong&gt;stable public IP&lt;/strong&gt; so that outbound requests from the private subnet appear consistent to the internet (unlike auto-assigned public IPs that can change).&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create NAT Gateway&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;3. Route Table for the Private Subnet&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Route Tables&lt;/strong&gt; → &lt;strong&gt;Create Route Table&lt;/strong&gt; → name it &lt;strong&gt;Private Subnet RT&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Associate this route table with the &lt;strong&gt;Private Subnet&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add a route:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Destination:&lt;/strong&gt; &lt;code&gt;0.0.0.0/0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target:&lt;/strong&gt; NAT Gateway&lt;/li&gt;
&lt;li&gt;then asscosite it with the private subnet&lt;/li&gt;
&lt;li&gt;Hint: This ensures &lt;strong&gt;outbound internet traffic&lt;/strong&gt; from the private EC2 (e.g., to download updates or call external APIs) passes through the NAT, keeping the EC2 private from inbound internet traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;4. Launch an EC2 in the Private Subnet&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;EC2 → Instances → Launch Instances&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Name it &lt;strong&gt;Server-Portal&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select an &lt;strong&gt;AMI&lt;/strong&gt; (OS) suitable for backend services. Hint: Like before, AMI is the operating system your EC2 will run.&lt;/li&gt;
&lt;li&gt;Choose an &lt;strong&gt;Instance Type&lt;/strong&gt; based on expected workload (CPU, memory). Hint: For backend tasks, adjust based on load (grading computations may require more memory/CPU).&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;Private Subnet&lt;/strong&gt; for networking.&lt;/li&gt;
&lt;li&gt;Assign a &lt;strong&gt;security group&lt;/strong&gt; allowing necessary ports for internal communication (SSH from bastion, application ports from public EC2, etc.).
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv6m1xbv1z1uv9z74u657.jpg" alt=" " width="735" height="606"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;THE PROBLEM&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A few hours after releasing exam results, the university’s IT team started receiving complaints:&lt;/p&gt;

&lt;p&gt;“My grades are not appearing on the portal.”&lt;br&gt;
&lt;strong&gt;“The page keeps loading forever.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;CloudWatch showed increased errors, but the logs were not detailed enough to reveal the root cause.&lt;br&gt;
So the backend engineering team decided to SSH into the private application server (through the bastion host) to investigate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo610v96ck3mohs5dqnj1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo610v96ck3mohs5dqnj1.jpg" alt=" " width="736" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;THE SOLUTION&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So far we have created 2 ec2s try this one alone here are a few instrctions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7: Create the Bastion Host&lt;/strong&gt;&lt;br&gt;
The &lt;strong&gt;Bastion Host&lt;/strong&gt; is simply another &lt;strong&gt;EC2 instance&lt;/strong&gt; that acts as a secure jump server to access private resources.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Subnet Placement:&lt;/strong&gt; Launch it in the &lt;strong&gt;public subnet&lt;/strong&gt; so it can receive SSH connections from your local machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Group:&lt;/strong&gt; Create a &lt;strong&gt;Bastion-SG&lt;/strong&gt; that allows &lt;strong&gt;inbound SSH (port 22) from your IP only&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Note:&lt;/strong&gt; In production, bastion security groups are more restrictive and often integrated with multi-factor authentication or other controls. For the lab, we keep it simple.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 Setup:&lt;/strong&gt; Choose an AMI and instance type similar to other EC2s. Assign the &lt;strong&gt;public IP&lt;/strong&gt; automatically so you can SSH into it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy38b0qde0pcktxobza1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy38b0qde0pcktxobza1g.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;DONE🎉&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 8: SSH into Bastion and Access Private EC2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;strong&gt;Bastion Host&lt;/strong&gt; in place, the team could investigate the issue on the private EC2 instance hosting the backend services.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SSH into Bastion Host:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;On &lt;strong&gt;Git Bash (Linux terminal)&lt;/strong&gt;, use:&lt;/p&gt;

&lt;p&gt;ssh -A -i  ec2-user@&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This connects your local machine securely to the bastion host in the &lt;strong&gt;public subnet&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prepare Access to Private EC2:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Go to the &lt;strong&gt;private EC2 instance&lt;/strong&gt; (“Server Portal”) and &lt;strong&gt;update its security group&lt;/strong&gt; to allow inbound SSH &lt;strong&gt;from the Bastion-SG&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;This ensures the bastion host can reach the private EC2, while the private EC2 remains inaccessible from the internet.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SSH to Private EC2 via Agent Forwarding:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ssh ec2-user@&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives secure access without exposing the private key on the bastion host.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Investigate and Solve the Issue:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Inside the private EC2, the team discovered:

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;grade-processing service&lt;/strong&gt; had crashed.&lt;/li&gt;
&lt;li&gt;A Python script syncing grades had a &lt;strong&gt;file permissions error&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A critical directory had incorrect Linux permissions after a deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;These issues &lt;strong&gt;could not be resolved from CloudWatch or the AWS console&lt;/strong&gt; — direct server access was required.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outcome:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;The backend services were fixed, and students experienced &lt;strong&gt;fast, reliable portal access&lt;/strong&gt; again.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf29hky8ne3zqf7iz5cq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgf29hky8ne3zqf7iz5cq.jpg" alt=" " width="626" height="937"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;ADD ON&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 9: Creating and Using a Network ACL to Deny Traffic to the Private EC2&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Go to the NACL page in AWS:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the AWS console search bar, type &lt;strong&gt;“Network ACLs”&lt;/strong&gt; and select it.&lt;/li&gt;
&lt;li&gt;This is where you manage subnet-level traffic controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Create a new NACL:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;Create Network ACL&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Give it a meaningful name, e.g., &lt;code&gt;PrivateSubnet-Deny-ACL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the &lt;strong&gt;VPC&lt;/strong&gt; where your private subnet resides.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Associate the NACL with the private subnet:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After creating, select the NACL, click &lt;strong&gt;Subnet associations&lt;/strong&gt;, then &lt;strong&gt;Edit subnet associations&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Check the private subnet to associate this NACL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Add a deny rule for the EC2 instance:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Select &lt;strong&gt;Inbound Rules&lt;/strong&gt; and click &lt;strong&gt;Edit inbound rules&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Add a new rule:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rule #:&lt;/strong&gt; pick the next available number (e.g., &lt;code&gt;100&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type:&lt;/strong&gt; &lt;code&gt;All Traffic&lt;/code&gt; (or specify &lt;code&gt;SSH&lt;/code&gt; if you want to limit to port 22)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; &lt;code&gt;All&lt;/code&gt; (or &lt;code&gt;TCP&lt;/code&gt; if limiting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port Range:&lt;/strong&gt; &lt;code&gt;0-65535&lt;/code&gt; (or just 22 for SSH)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; the &lt;strong&gt;private EC2’s IP&lt;/strong&gt; in CIDR &lt;code&gt;/32&lt;/code&gt; format (e.g., &lt;code&gt;10.0.1.181/32&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; Deny&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. (Optional) Outbound rule:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you also want to stop this EC2 from initiating connections to other hosts, add a similar rule under &lt;strong&gt;Outbound Rules&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6. Save changes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once done, the NACL is active.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effect:&lt;/strong&gt; The bastion host (or any other source) can no longer reach the private EC2, because the NACL explicitly denies traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Hint:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NACLs are &lt;strong&gt;stateless&lt;/strong&gt;, so you must create &lt;strong&gt;both inbound and outbound rules&lt;/strong&gt; if you want to block traffic in both directions.&lt;/li&gt;
&lt;li&gt;Rule numbering matters: lower numbers are evaluated first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlacfyuzq6i9ksouxg49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjlacfyuzq6i9ksouxg49.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>networking</category>
      <category>aws</category>
      <category>architecture</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
