<?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: Tejas Shinkar</title>
    <description>The latest articles on DEV Community by Tejas Shinkar (@tejas_shinkar).</description>
    <link>https://dev.to/tejas_shinkar</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3938675%2F6d029035-4f11-4558-aa7f-0b63467d1540.jpg</url>
      <title>DEV Community: Tejas Shinkar</title>
      <link>https://dev.to/tejas_shinkar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tejas_shinkar"/>
    <language>en</language>
    <item>
      <title>So… If AI Can Write Code, What Are Software Engineers Getting Paid For? 👀</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sun, 30 Aug 2026 16:17:23 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/so-if-ai-can-write-code-what-are-software-engineers-getting-paid-for-193m</link>
      <guid>https://dev.to/tejas_shinkar/so-if-ai-can-write-code-what-are-software-engineers-getting-paid-for-193m</guid>
      <description>&lt;p&gt;A few years ago, being the person who could turn a requirement into working code was a pretty valuable skill.&lt;/p&gt;

&lt;p&gt;Now you can give an AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Build me a REST API with authentication, tests and documentation.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…and get something surprisingly usable in minutes.&lt;/p&gt;

&lt;p&gt;So naturally, the uncomfortable question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If AI can write the code, what exactly are software engineers getting paid for?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's where it gets interesting.&lt;/p&gt;

&lt;p&gt;Imagine an AI-generated feature gets merged.&lt;/p&gt;

&lt;p&gt;The tests pass.&lt;/p&gt;

&lt;p&gt;The deployment succeeds.&lt;/p&gt;

&lt;p&gt;And two hours later, production starts throwing errors.&lt;/p&gt;

&lt;p&gt;Now someone has to figure out:&lt;/p&gt;

&lt;p&gt;Why only certain users are affected?&lt;/p&gt;

&lt;p&gt;Why did latency suddenly spike?&lt;/p&gt;

&lt;p&gt;Is the application broken, or is the database struggling?&lt;/p&gt;

&lt;p&gt;Did the new code introduce a race condition?&lt;/p&gt;

&lt;p&gt;Is the fix actually safe to deploy?&lt;/p&gt;

&lt;p&gt;AI can help investigate all of this.&lt;/p&gt;

&lt;p&gt;But somebody still has to &lt;strong&gt;understand the system well enough to know whether the answer makes sense.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And developers themselves seem to know this.&lt;/p&gt;

&lt;p&gt;Stack Overflow's 2025 survey found that &lt;strong&gt;84% of developers use or plan to use AI tools&lt;/strong&gt;, yet 46% said they don't trust the accuracy of AI output. The biggest frustration? AI solutions that are &lt;em&gt;almost&lt;/em&gt; right — with 45% saying debugging AI-generated code can take more time.&lt;/p&gt;

&lt;p&gt;There's another interesting signal: developers are much less willing to hand AI high-responsibility work like deployment and monitoring.&lt;/p&gt;

&lt;p&gt;Maybe that's the shift.&lt;/p&gt;

&lt;p&gt;The valuable engineer isn't necessarily the person who can type the most code anymore.&lt;/p&gt;

&lt;p&gt;It's the person who can:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;understand the problem → design the system → use AI effectively → question its output → debug what breaks → make the right trade-off.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI might be making code cheaper.&lt;/p&gt;

&lt;p&gt;But &lt;strong&gt;good engineering judgment?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's still expensive.&lt;/p&gt;

&lt;p&gt;And maybe that's what we're actually getting paid for. 👀&lt;/p&gt;

</description>
      <category>software</category>
      <category>tech</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Thought Knowing AWS Was The Goal. I Was Wrong.</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Sat, 29 Aug 2026 16:27:31 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/i-thought-knowing-aws-was-the-goal-i-was-wrong-4j6m</link>
      <guid>https://dev.to/tejas_shinkar/i-thought-knowing-aws-was-the-goal-i-was-wrong-4j6m</guid>
      <description>&lt;p&gt;When I started learning AWS, I had this very simple idea in my head: learn the services, finish the modules, build a few projects, and eventually I’d be “good at cloud.” So I started collecting names — EC2, S3, IAM, VPC, EFS, CloudFront, Route 53, and a growing graveyard of AWS console tabs. 😂 At first, it felt productive because every completed service felt like another box checked. But somewhere during the networking labs, that started falling apart. I realized I could tell you what a NAT Gateway does, but still stop and ask myself, &lt;strong&gt;“Wait… why are we creating another subnet for this?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That question actually changed how I started learning. Instead of asking, &lt;em&gt;“What does this AWS service do?”&lt;/em&gt;, I started asking, &lt;em&gt;“What problem are we solving that requires this thing?”&lt;/em&gt; Suddenly VPC started making more sense. A route table wasn't just another AWS resource; it was an answer to &lt;em&gt;“Where should this traffic go?”&lt;/em&gt; A NAT Gateway wasn't something you create because a tutorial told you to; it existed because a private machine needed controlled outbound internet access without becoming publicly reachable. Even NACLs became interesting when I deliberately broke one and watched an otherwise healthy request just… hang. 💀&lt;/p&gt;

&lt;p&gt;And I started noticing the same thing outside networking. IAM isn't really about memorizing policies — it's about deciding &lt;strong&gt;who should be allowed to do what, and from where&lt;/strong&gt;. CloudFront isn't just “AWS CDN” — it's figuring out which responses are safe to reuse, which ones are personalized, and what happens when your cache thinks two requests are the same when they aren't. Docker isn't impressive because you know &lt;code&gt;docker build&lt;/code&gt;; it's useful when you understand what should actually live inside a container, what should stay outside it, and how that behaves when the container gets replaced.&lt;/p&gt;

&lt;p&gt;I'm still nowhere near the point where I'd call myself a production DevOps engineer. Most of what I'm learning right now comes from courses, labs, breaking things, fixing them, and trying to reason about what these systems would look like in a real environment. And honestly, that's probably been more valuable than simply finishing lectures. Because the more I learn, the more I realize that companies probably don't need someone who can recite 100 AWS services from memory. They need someone who can look at a broken system and ask &lt;strong&gt;the right next question&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Maybe that's the difference between learning tools and learning engineering. One teaches you &lt;strong&gt;what exists&lt;/strong&gt;. The other teaches you &lt;strong&gt;why it exists, when to use it, and what happens when it breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm curious about people who already work in Cloud/DevOps/SRE:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What was the first thing you learned in production that made you realize, “Yeah… courses really didn't prepare me for this”?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>I Thought Caching Was Just About Speed. Then I Found the Security Trap. 💀</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 28 Aug 2026 05:20:01 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/i-thought-caching-was-just-about-speed-then-i-found-the-security-trap-gdi</link>
      <guid>https://dev.to/tejas_shinkar/i-thought-caching-was-just-about-speed-then-i-found-the-security-trap-gdi</guid>
      <description>&lt;p&gt;I used to think caching was a pretty simple performance trick: store something closer to the user, serve it faster, reduce the load on the backend, and everyone wins. Then I started digging into CloudFront cache keys and realized caching can become a &lt;strong&gt;data-isolation problem&lt;/strong&gt; if one small decision is wrong. Imagine an application with a &lt;code&gt;GET /profile&lt;/code&gt; endpoint where User A sends &lt;code&gt;Cookie: session=userA&lt;/code&gt; and gets a response like “Hello Alice, your orders: 12, your balance: ₹42,000.” CloudFront can cache that response so future requests don't always have to reach the origin. Sounds great, right? Until another user requests the same URL and the cache doesn't distinguish the thing that actually makes the response personal.&lt;/p&gt;

&lt;p&gt;Now User B sends &lt;code&gt;GET /profile&lt;/code&gt; with &lt;code&gt;Cookie: session=userB&lt;/code&gt;, and the origin would normally return completely different information such as “Hello Bob, your orders: 4, your balance: ₹8,000.” But if the cache key only considers &lt;code&gt;/profile&lt;/code&gt; and ignores the user-specific part of the request, CloudFront can see the request as equivalent to the object it already has. In that situation, User B could receive the response cached for User A. Nothing has crashed, the EC2 instance can be healthy, the database can be healthy, and CloudFront can even be behaving exactly according to its configuration. The actual problem is the &lt;strong&gt;caching strategy&lt;/strong&gt;. That's when caching stops being only a performance optimization and becomes a correctness and security concern.&lt;/p&gt;

&lt;p&gt;Now compare that with a public product catalogue. A request like &lt;code&gt;GET /products?id=101&lt;/code&gt; might return the same product information for every user: &lt;code&gt;iPhone 17&lt;/code&gt;, &lt;code&gt;₹79,999&lt;/code&gt;, &lt;code&gt;In Stock&lt;/code&gt;. That's a fantastic candidate for caching because thousands of users can safely reuse the same response. We want something like &lt;strong&gt;one cache object serving many requests&lt;/strong&gt;, not a separate cache entry for every visitor. But this is where another subtle problem appears: if every request contains an analytics cookie such as &lt;code&gt;analytics_id=83A91&lt;/code&gt;, &lt;code&gt;analytics_id=72B42&lt;/code&gt;, or &lt;code&gt;analytics_id=91K17&lt;/code&gt;, and those values don't change the product response, putting them into the cache key can create thousands or even millions of unnecessary cache variants. The application may still be correct, but cache reuse gets worse, cache efficiency drops, and more requests may end up going back to the origin.&lt;/p&gt;

&lt;p&gt;That leads to one of the most useful CloudFront distinctions I've learned: &lt;strong&gt;Cache Policy and Origin Request Policy are not answering the same question.&lt;/strong&gt; Cache Policy is essentially asking, “What makes this request a different cached object?” while Origin Request Policy is asking, “What information does the origin need to receive?” For example, an analytics cookie might need to reach the origin for logging or processing, while still not needing to create a unique cache entry for every user. The important part is to understand which request values actually change the response and which ones are simply metadata. A good cache design therefore doesn't blindly include every cookie, header, or query string just because it is available; it deliberately chooses only the dimensions that matter.&lt;/p&gt;

&lt;p&gt;The production lesson for me is that &lt;strong&gt;a high cache-hit ratio is not automatically a good outcome&lt;/strong&gt;. A cache that is extremely fast but returns the wrong user's data is a disaster, not an optimization. The real question isn't “How much can I cache?” but “What can I safely reuse, what makes the response different, and how do I make CloudFront understand that difference?” This also changes how I think about CDN architecture: cache design affects performance, origin load, freshness, correctness, and potentially security at the same time. Static assets such as versioned JavaScript, CSS, images, and public product data are usually much easier to cache aggressively, while personalized responses need a much more careful strategy. &lt;strong&gt;Caching is not just about speed — it's about knowing exactly when two requests are allowed to share the same answer.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloudsecurity</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I broke my own AWS infrastructure… without touching the infrastructure.</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 26 Aug 2026 18:02:23 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/i-broke-my-own-aws-infrastructure-without-touching-the-infrastructure-3a26</link>
      <guid>https://dev.to/tejas_shinkar/i-broke-my-own-aws-infrastructure-without-touching-the-infrastructure-3a26</guid>
      <description>&lt;p&gt;I had an EC2 instance running an application.&lt;/p&gt;

&lt;p&gt;Everything looked fine.&lt;/p&gt;

&lt;p&gt;Then I changed &lt;strong&gt;one Security Group rule&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suddenly…&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the application couldn't reach the database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The instances were healthy.&lt;br&gt;&lt;br&gt;
The database was healthy.&lt;br&gt;&lt;br&gt;
CPU was normal.&lt;br&gt;&lt;br&gt;
No deployment happened.&lt;/p&gt;

&lt;p&gt;Just… &lt;strong&gt;timeout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that's when I learned something production-grade:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A Security Group is stateful, but that doesn't mean your architecture is automatically stateful.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The database allowed inbound traffic from the application's Security Group.&lt;/p&gt;

&lt;p&gt;So when the app initiated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 → DB : 3306
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the return traffic didn't need a separate inbound rule on the application side.&lt;/p&gt;

&lt;p&gt;But when we accidentally restricted the &lt;strong&gt;egress&lt;/strong&gt; from the application SG…&lt;/p&gt;

&lt;p&gt;💀 &lt;strong&gt;the connection died.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nothing was "down."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The network policy was.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the scary part about cloud infrastructure:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Your application can be perfectly healthy and still be completely unreachable because the control plane says "no."&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And that's why production debugging isn't just:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Is the server running?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Who is allowed to talk to whom, on which port, in which direction, and through which path?”&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>cloudsecurity</category>
    </item>
    <item>
      <title>AWS CloudFront — Complete Understanding, Production Patterns &amp; Practice</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 26 Aug 2026 17:53:14 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-cloudfront-complete-understanding-production-patterns-practice-1onb</link>
      <guid>https://dev.to/tejas_shinkar/aws-cloudfront-complete-understanding-production-patterns-practice-1onb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Part of my AWS learning journey — exploring AWS hands-on and building a deeper understanding of Cloud &amp;amp; DevOps. This session dives into how Amazon CloudFront works as a CDN, from edge locations and cache hits/misses to cache keys, cache policies, TTLs, and invalidation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📋 Topics Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;What CloudFront Actually Is&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Why Do We Need a CDN?&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;CloudFront Terminology — Distribution, Origin, Edge Location&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Complete Request Flow&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Cache HIT / Cache MISS&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Per-Edge Caching&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Cache Key&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Cache-Key Design &amp;amp; The Golden Rule&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Personalization and Caching&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Cache Policy vs Origin Request Policy&lt;/td&gt;
&lt;td&gt;Concept + Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Query Strings, Cookies &amp;amp; Headers in the Cache Key&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;TTL — Minimum, Default, Maximum&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Cache-Control &amp;amp; Invalidation&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Versioned Assets&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Cache Behaviors&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Real-World Caching Scenarios&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Request Collapsing &amp;amp; Cache Hit Ratio&lt;/td&gt;
&lt;td&gt;Concept + Cert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Production Cache Hierarchy&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;CloudFront + S3 (OAC)&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;CloudFront + ALB + EC2&lt;/td&gt;
&lt;td&gt;Concept + Lab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;CloudFront + WAF&lt;/td&gt;
&lt;td&gt;Concept + DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Production Techniques &amp;amp; Common Mistakes&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;Production Mini Architecture&lt;/td&gt;
&lt;td&gt;DevOps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Practice Labs 1–10&lt;/td&gt;
&lt;td&gt;Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Interview Revision&lt;/td&gt;
&lt;td&gt;Interview&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;Final Mental Model&lt;/td&gt;
&lt;td&gt;Concept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What CloudFront Actually Is
&lt;/h2&gt;

&lt;p&gt;Amazon CloudFront is AWS's &lt;strong&gt;Content Delivery Network (CDN)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The simplest mental model is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CloudFront keeps reusable copies of content closer to users so that the origin does not have to generate or deliver the same response repeatedly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without CloudFront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  |
  v
Origin
(EC2 / ALB / S3 / API Gateway)
  |
  v
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With CloudFront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  |
  v
CloudFront Edge Location
  |
  +---- Cache HIT ----&amp;gt; Response to User
  |
  +---- Cache MISS ---&amp;gt; Origin
                           |
                           v
                        Response
                           |
                           v
                    CloudFront caches it
                           |
                           v
                        User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;origin remains the source of truth&lt;/strong&gt;. CloudFront is a delivery and caching layer in front of it.&lt;/p&gt;

&lt;p&gt;CloudFront can use origins such as Amazon S3, an Application Load Balancer, EC2/custom HTTP servers, API Gateway, and other HTTP origins.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Do We Need a CDN?
&lt;/h2&gt;

&lt;p&gt;Imagine an application hosted in Mumbai. A nearby user may reach it with relatively low network latency, but a user in Europe has a much longer network path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Europe User
     |
     | long network distance
     v
Mumbai Origin
     |
     v
Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With CloudFront:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Europe User
     |
     v
Nearby CloudFront edge
     |
     v
Cached response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the response is already cached at that edge, the origin does not need to be contacted for that request.&lt;/p&gt;

&lt;p&gt;CloudFront can therefore reduce viewer latency, reduce origin load, improve scalability, reduce repeated origin data transfer, provide globally distributed delivery, and integrate with security services such as AWS WAF and Origin Access Control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Important CloudFront Terminology
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Distribution
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;CloudFront distribution&lt;/strong&gt; is the overall configuration that tells CloudFront how to deliver an application. It contains configuration for origins, cache behaviors, cache policies, origin request policies, allowed HTTP methods, viewer protocol settings, TLS/custom domains, security, and logging.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Distribution = the overall CloudFront delivery configuration.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Origin
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;origin&lt;/strong&gt; is where CloudFront obtains the original content — for example &lt;code&gt;CloudFront → S3&lt;/code&gt;, &lt;code&gt;CloudFront → ALB → EC2&lt;/code&gt;, or &lt;code&gt;CloudFront → API Gateway&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CloudFront
    |
    v
ALB
    |
    +--&amp;gt; EC2
    +--&amp;gt; EC2
    +--&amp;gt; EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the ALB is the CloudFront origin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edge Location / Point of Presence (PoP)
&lt;/h3&gt;

&lt;p&gt;An edge location is a CloudFront location close to viewers where CloudFront can serve cached content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  CloudFront
                     |
       +-------------+-------------+
       |             |             |
     India         Europe         USA
       |             |             |
     Users         Users         Users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different edge locations can have their own cached copies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The origin stores the original content; edge locations store temporary cached copies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;CloudFront also has &lt;strong&gt;regional edge caches&lt;/strong&gt; between POPs and origins. These provide another caching layer and can retain less-popular objects longer than individual POP caches.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete Request Flow
&lt;/h2&gt;

&lt;p&gt;Suppose the origin contains &lt;code&gt;logo.png&lt;/code&gt; and a user requests &lt;code&gt;GET /logo.png&lt;/code&gt;. The conceptual flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. User requests /logo.png
              |
              v
2. DNS routes the viewer to an appropriate CloudFront edge
              |
              v
3. CloudFront determines the cache key
              |
              v
4. Cache lookup
          /             \
        HIT              MISS
         |                |
         v                v
 Return object        Request origin
                           |
                           v
                     Origin response
                           |
                           v
                    CloudFront caches
                           |
                           v
                         User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the foundation of CloudFront.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache HIT / Cache MISS
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;cache hit&lt;/strong&gt; means CloudFront found a valid cached object matching the viewer's request, so the origin does not need to generate the object for that request. Benefits: lower latency, lower origin load, better scalability, better cache hit ratio.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;cache miss&lt;/strong&gt; means CloudFront cannot find a valid cached object matching the request at that edge, so it must fetch the response from the origin and cache it. A later matching request can then become a cache hit while the object remains valid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User                          User
 |                              |
 v                              v
CloudFront                CloudFront
 |                              |
 v                              | MISS
Cache HIT                       v
 |                          Origin
 v                              |
Cached object              response, then cached, then returned to User
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Per-Edge Caching
&lt;/h2&gt;

&lt;p&gt;Suppose a user in Delhi requests &lt;code&gt;/logo.png&lt;/code&gt;. One edge location may cache it, while a user elsewhere may reach another edge location where that object is not yet cached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Origin
                  |
        +---------+---------+
        |         |         |
      Edge A    Edge B    Edge C
      logo      logo       logo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same object can exist in multiple edge caches. A cache miss at one edge does not mean the entire CloudFront distribution has no copy anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache Key
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;cache key&lt;/strong&gt; determines which viewer requests are considered equivalent for caching.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CloudFront's identity/checklist for deciding whether this request can reuse an existing cached object.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For &lt;code&gt;GET /products?id=10&lt;/code&gt;, the cache identity could contain the path (&lt;code&gt;/products&lt;/code&gt;) and query string (&lt;code&gt;id=10&lt;/code&gt;). If another user sends the same relevant request, it can map to the same cached object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Cache-Key Design Matters
&lt;/h3&gt;

&lt;p&gt;If users A, B, and C all request &lt;code&gt;/products?id=10&lt;/code&gt; and receive the same product information, we want a single shared cache object serving all three — good cache reuse.&lt;/p&gt;

&lt;p&gt;But if the cache key unnecessarily includes a unique cookie (e.g. &lt;code&gt;user=A&lt;/code&gt; vs &lt;code&gt;user=B&lt;/code&gt;), CloudFront now treats &lt;code&gt;/products?id=10 + user=A&lt;/code&gt; and &lt;code&gt;/products?id=10 + user=B&lt;/code&gt; as separate cache variants. The content may be identical, but cache reuse drops and origin traffic can increase.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Golden Cache-Key Rule
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can this request value change the response?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If yes, it may need to participate in the cache key. If no, including it may unnecessarily fragment the cache.&lt;/p&gt;

&lt;p&gt;For example, an &lt;code&gt;analytics_id&lt;/code&gt; cookie that's only used for analytics and doesn't change the response usually shouldn't become a cache-key dimension. But a &lt;code&gt;session_id&lt;/code&gt; cookie that determines whether the response is "Hello Alice" or "Hello Bob" is a personalized response and must not be accidentally shared.&lt;/p&gt;




&lt;h2&gt;
  
  
  Personalization and Caching
&lt;/h2&gt;

&lt;p&gt;This is a major production concern. If &lt;code&gt;GET /profile&lt;/code&gt; with &lt;code&gt;Cookie: session=A&lt;/code&gt; returns "Hello Alice" and the same path with &lt;code&gt;Cookie: session=B&lt;/code&gt; returns "Hello Bob", but CloudFront creates one shared cache object using only the path &lt;code&gt;/profile&lt;/code&gt;, the response could be incorrectly reused. That is not merely a performance problem — it can become a &lt;strong&gt;data-isolation/security problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Common strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 1 — Don't shared-cache private responses.&lt;/strong&gt; For highly dynamic/private endpoints, route straight through: &lt;code&gt;User → CloudFront → Origin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 2 — Vary the cache identity when appropriate.&lt;/strong&gt; For example, &lt;code&gt;/profile + user=A → Cache A&lt;/code&gt; and &lt;code&gt;/profile + user=B → Cache B&lt;/code&gt;. This can become extremely expensive and fragmented when there are millions of users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy 3 — Separate static and dynamic content.&lt;/strong&gt; A common production architecture: &lt;code&gt;/static/*&lt;/code&gt; is highly cacheable, &lt;code&gt;/api/*&lt;/code&gt; is dynamic/private.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache Policy vs Origin Request Policy
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Cache Policy&lt;/strong&gt; controls which request values participate in the cache key, TTL settings, and compression-related cache behavior. The cache key can be influenced by path, query strings, headers, and cookies.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Policy answers: "What makes this request a different cache object, and how long should the object remain cached?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An &lt;strong&gt;Origin Request Policy&lt;/strong&gt; answers a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What information should CloudFront send to the origin?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They are related, but not the same — this is one of the most important CloudFront interview concepts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example — origin needs information but cache doesn't
&lt;/h3&gt;

&lt;p&gt;For a request like &lt;code&gt;GET /products?id=10&lt;/code&gt; with &lt;code&gt;Cookie: analytics_id=ABC123&lt;/code&gt;, where &lt;code&gt;id=10&lt;/code&gt; determines the product but &lt;code&gt;analytics_id&lt;/code&gt; is only needed by the origin for analytics and doesn't change the response, we want the cache key to stay just &lt;code&gt;/products?id=10&lt;/code&gt; while the origin request still forwards &lt;code&gt;analytics_id=ABC123&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So: the Cache Policy does &lt;strong&gt;not&lt;/strong&gt; include &lt;code&gt;analytics_id&lt;/code&gt; in the cache key, while the Origin Request Policy forwards it to the origin. This allows cache reuse while still giving the origin the information it needs.&lt;/p&gt;

&lt;p&gt;AWS documents this separation explicitly: values included in the cache key are also sent to the origin, while an Origin Request Policy can add additional headers, cookies, and query strings to origin requests without putting them into the cache key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warning with Authorization headers
&lt;/h3&gt;

&lt;p&gt;If a request's &lt;code&gt;Authorization&lt;/code&gt; header changes what response the user receives, you cannot simply forward the token and assume shared caching is safe. Always ask: &lt;strong&gt;does this request information change the response?&lt;/strong&gt; If yes, either make the relevant dimension part of the cache design or avoid shared caching for that response.&lt;/p&gt;




&lt;h2&gt;
  
  
  Query Strings, Cookies &amp;amp; Headers in the Cache Key
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Query strings:&lt;/strong&gt; if &lt;code&gt;id&lt;/code&gt; in &lt;code&gt;/products?id=10&lt;/code&gt; vs &lt;code&gt;/products?id=20&lt;/code&gt; changes the response, it must be represented in the cache identity — otherwise different products could map to the wrong cached response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cookies:&lt;/strong&gt; if &lt;code&gt;GET /products?id=10&lt;/code&gt; returns the same response regardless of a &lt;code&gt;user=A&lt;/code&gt; vs &lt;code&gt;user=B&lt;/code&gt; cookie, don't automatically include the cookie in the cache key. If the response &lt;em&gt;does&lt;/em&gt; differ because of the cookie, you need a design that prevents incorrect sharing. The answer is &lt;strong&gt;not&lt;/strong&gt; always "include every cookie" — that can create millions of cache variants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headers:&lt;/strong&gt; an &lt;code&gt;Accept-Language: en&lt;/code&gt; request returning "Welcome" vs &lt;code&gt;Accept-Language: fr&lt;/code&gt; returning "Bienvenue" means language may need to influence the cache identity if it genuinely changes the response.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Vary the cache only on dimensions that genuinely affect the response.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  TTL — Minimum, Default, Maximum
&lt;/h2&gt;

&lt;p&gt;TTL means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How long a cached object remains fresh according to the CloudFront caching configuration.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With a TTL of 3600 seconds (one hour), an object cached at 12:00 stays a hit through 12:59, expires at 13:00, and needs fresh/validated origin content on the next request after that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long TTL&lt;/strong&gt; (e.g. for &lt;code&gt;app.js&lt;/code&gt;) gives high cache reuse, low origin traffic, low latency, and better scalability — at the cost of changes remaining stale longer unless you use versioned assets or invalidation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short TTL&lt;/strong&gt; (e.g. for &lt;code&gt;price.json&lt;/code&gt;) makes changes visible sooner, but increases origin requests, lowers the cache hit ratio, raises origin load, and can increase latency.&lt;/p&gt;

&lt;p&gt;CloudFront cache policies have a &lt;strong&gt;Minimum TTL&lt;/strong&gt;, &lt;strong&gt;Default TTL&lt;/strong&gt;, and &lt;strong&gt;Maximum TTL&lt;/strong&gt;, which work together with origin caching headers such as &lt;code&gt;Cache-Control: max-age=3600&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default TTL&lt;/strong&gt; is used when the origin doesn't provide appropriate caching information.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum TTL&lt;/strong&gt; sets a lower bound on how long CloudFront caches. Important production warning: if Minimum TTL is greater than zero, CloudFront can cache for at least that duration even if origin headers contain directives such as &lt;code&gt;no-cache&lt;/code&gt;, &lt;code&gt;no-store&lt;/code&gt;, or &lt;code&gt;private&lt;/code&gt; — so be careful using positive Minimum TTLs with private/dynamic content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum TTL&lt;/strong&gt; places an upper bound on how long an object can remain fresh based on the relevant origin caching headers.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Cache-Control &amp;amp; Invalidation
&lt;/h2&gt;

&lt;p&gt;The origin can send headers like &lt;code&gt;Cache-Control: max-age=3600&lt;/code&gt;, &lt;code&gt;Cache-Control: no-store&lt;/code&gt;, or &lt;code&gt;Cache-Control: private&lt;/code&gt; to communicate caching requirements. CloudFront's cache policy and TTL configuration determine how those instructions interact with CloudFront caching.&lt;/p&gt;

&lt;p&gt;If the origin's &lt;code&gt;logo.png&lt;/code&gt; changes but CloudFront still has the old version cached with a 24-hour TTL, the old object can persist until it naturally expires. To remove it sooner, use &lt;strong&gt;CloudFront invalidation&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Origin = NEW

CloudFront Cache = OLD
        |
        v
   Invalidation
        |
        v
Cached object invalidated
        |
        v
Next request -&amp;gt; MISS
        |
        v
Origin -&amp;gt; NEW
        |
        v
CloudFront caches NEW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;strong&gt;TTL&lt;/strong&gt; when &lt;em&gt;"this content can safely remain cached for this long."&lt;/em&gt; Use &lt;strong&gt;invalidation&lt;/strong&gt; when &lt;em&gt;"I changed the content and need the cached version invalidated before its normal lifetime."&lt;/em&gt; Production systems commonly use both.&lt;/p&gt;




&lt;h2&gt;
  
  
  Versioned Assets
&lt;/h2&gt;

&lt;p&gt;Instead of constantly replacing &lt;code&gt;/app.js&lt;/code&gt; in place, use a content-hashed filename like &lt;code&gt;/app.a82f91.js&lt;/code&gt;. When the application changes, &lt;code&gt;/app.b73c21.js&lt;/code&gt; is generated — CloudFront sees a different cache key entirely, which makes long TTLs practical for build artifacts.&lt;/p&gt;

&lt;p&gt;A common pattern: HTML gets a shorter TTL or controlled invalidation, while JS/CSS/images get a long TTL plus content-hashed filenames. This reduces the need for broad invalidations and is one of the most common production CDN techniques — using &lt;code&gt;app.v1.js&lt;/code&gt;/&lt;code&gt;app.v2.js&lt;/code&gt; or hash-based names avoids users being stuck on stale cached versions after a deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cache Behaviors
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Cache Behavior&lt;/strong&gt; is a set of CloudFront rules applied to requests matching a URL path pattern (e.g. &lt;code&gt;/static/*&lt;/code&gt;, &lt;code&gt;/images/*&lt;/code&gt;, &lt;code&gt;/api/*&lt;/code&gt;). Different behaviors can use different origins, cache policies, origin request policies, allowed methods, TTL/caching behavior, and viewer/security settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  CloudFront
                      |
          +-----------+-----------+
          |                       |
       /static/*                /api/*
          |                       |
          v                       v
         S3                      ALB
                                  |
                               EC2/ECS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A distribution has a default behavior (e.g. &lt;code&gt;/*&lt;/code&gt;), and you can add more specific behaviors (e.g. &lt;code&gt;/api/*&lt;/code&gt;, &lt;code&gt;/images/*&lt;/code&gt;). The more specific matching behavior is used according to CloudFront's path matching rules — &lt;code&gt;/images/logo.png&lt;/code&gt; uses the &lt;code&gt;/images/*&lt;/code&gt; behavior, &lt;code&gt;/api/products&lt;/code&gt; uses the &lt;code&gt;/api/*&lt;/code&gt; behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-World Caching Scenarios
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Public product catalogue:&lt;/strong&gt; &lt;code&gt;GET /products?id=10&lt;/code&gt; returning identical data to every user (&lt;code&gt;User A → MISS → Origin → Cache&lt;/code&gt;, &lt;code&gt;User B/C/D → HIT&lt;/code&gt;) is excellent cache reuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Personalized profile:&lt;/strong&gt; &lt;code&gt;GET /profile&lt;/code&gt; with different cookies returning "Hello Alice" vs "Hello Bob" must not become a shared cache object — a common fix is &lt;code&gt;/static/*&lt;/code&gt; cached by CloudFront, &lt;code&gt;/profile&lt;/code&gt; routed to a dynamic/private origin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analytics cookie:&lt;/strong&gt; if &lt;code&gt;GET /products?id=10&lt;/code&gt; returns the same response regardless of an &lt;code&gt;analytics_id&lt;/code&gt; cookie, that cookie shouldn't unnecessarily create separate cache objects — if the origin needs it, forward it via the origin request configuration without adding it to the cache key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Language-specific content:&lt;/strong&gt; &lt;code&gt;Accept-Language: en&lt;/code&gt; vs &lt;code&gt;fr&lt;/code&gt; returning "Welcome" vs "Bienvenue" means the caching design must distinguish the language variants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rapidly changing stock price:&lt;/strong&gt; &lt;code&gt;GET /stock/AAPL&lt;/code&gt; changing every second makes a long TTL inappropriate — options include a very short TTL, no caching, or a streaming/WebSocket mechanism for genuinely real-time requirements. CloudFront is not a "cache everything" service; caching is a business and correctness decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;News website:&lt;/strong&gt; &lt;code&gt;/articles/*&lt;/code&gt; can use a longer TTL, &lt;code&gt;/home&lt;/code&gt; a shorter TTL, and &lt;code&gt;/api/personalized/*&lt;/code&gt; stays dynamic/private — different content has different freshness requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment:&lt;/strong&gt; if &lt;code&gt;app.js&lt;/code&gt; keeps the same filename across versions and the cached object is still valid, some users may keep receiving the old version. Using &lt;code&gt;app.v1.js&lt;/code&gt;/&lt;code&gt;app.v2.js&lt;/code&gt; or content hashes (&lt;code&gt;app.abc123.js&lt;/code&gt;) avoids this — one of the most common production CDN techniques.&lt;/p&gt;




&lt;h2&gt;
  
  
  Request Collapsing &amp;amp; Cache Hit Ratio
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Request collapsing:&lt;/strong&gt; CloudFront can reduce duplicate origin requests when simultaneous requests for the same object and same cache key arrive at an edge while the object isn't available — 1,000 simultaneous requests can become a single origin fetch, with the response shared across all waiting requests. This only helps when requests share the same cache key; unnecessarily fragmenting the cache key (e.g. by user) makes the requests no longer equivalent for this purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache hit ratio&lt;/strong&gt; = cache hits ÷ total cacheable requests. For example, 800 hits out of 1,000 requests gives an 80% hit ratio. A higher hit ratio generally means more requests are served from edge cache and fewer reach the origin.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Do not optimize hit ratio at the expense of correctness or security.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A 99.9% hit ratio with users receiving incorrect/private data is a terrible architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Cache Hierarchy
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Viewer
   |
   v
CloudFront POP / Edge
   |
   v
Regional Edge Cache
   |
   v
Origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact internal routing is managed by AWS, but conceptually CloudFront provides geographically distributed caching layers between viewers and the origin. Your application does not need to manually manage these edge caches.&lt;/p&gt;




&lt;h2&gt;
  
  
  CloudFront + S3 (OAC)
&lt;/h2&gt;

&lt;p&gt;A classic architecture serves &lt;code&gt;index.html&lt;/code&gt;, &lt;code&gt;app.js&lt;/code&gt;, &lt;code&gt;styles.css&lt;/code&gt;, and &lt;code&gt;images/&lt;/code&gt; out of S3 through CloudFront. For production, keep the S3 bucket private and let CloudFront access it using &lt;strong&gt;Origin Access Control (OAC)&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
   |
   v
CloudFront
   |
   | OAC
   v
Private S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids making the S3 bucket itself the public application entry point.&lt;/p&gt;




&lt;h2&gt;
  
  
  CloudFront + ALB + EC2
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
   |
   v
CloudFront
   |
   v
ALB
   |
   +--&amp;gt; EC2-1
   +--&amp;gt; EC2-2
   +--&amp;gt; EC2-3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Responsibilities are kept separate: &lt;strong&gt;CloudFront&lt;/strong&gt; handles CDN/edge caching, global delivery, viewer-facing TLS, WAF integration, and routing by cache behavior. &lt;strong&gt;ALB&lt;/strong&gt; handles load balancing, health checks, and distribution across instances. &lt;strong&gt;EC2&lt;/strong&gt; handles application execution and business logic. Keeping these responsibilities separate is important when designing AWS architectures.&lt;/p&gt;




&lt;h2&gt;
  
  
  CloudFront + WAF
&lt;/h2&gt;

&lt;p&gt;AWS WAF can inspect requests before they reach the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 v
CloudFront
 |
 v
WAF rules
 |
 +---- malicious -&amp;gt; BLOCK
 |
 +---- valid ----&amp;gt; cache/origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible protections include SQL injection rules, XSS-related rules, rate limiting, IP restrictions, AWS managed rule groups, and custom rules. WAF does not replace application authentication and authorization.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Techniques &amp;amp; Common Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Production techniques:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Version static assets (&lt;code&gt;app.abc123.js&lt;/code&gt;, &lt;code&gt;styles.72fd12.css&lt;/code&gt;) and use long TTLs where appropriate.&lt;/li&gt;
&lt;li&gt;Separate static and dynamic paths (&lt;code&gt;/static/*&lt;/code&gt;, &lt;code&gt;/images/*&lt;/code&gt;, &lt;code&gt;/api/*&lt;/code&gt;) with a suitable behavior for each.&lt;/li&gt;
&lt;li&gt;Minimize cache-key dimensions — avoid unnecessary cookies, headers, query strings, and tracking IDs.&lt;/li&gt;
&lt;li&gt;Use Origin Request Policy appropriately — forward what the origin needs without automatically turning every value into a cache-key dimension.&lt;/li&gt;
&lt;li&gt;Choose TTL by freshness requirements: stable static → long, frequently changing → short, private → usually not shared cached.&lt;/li&gt;
&lt;li&gt;Use OAC for private S3 origins (&lt;code&gt;CloudFront → OAC → private S3&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Use WAF to protect public CloudFront endpoints.&lt;/li&gt;
&lt;li&gt;Monitor cache hit ratio, request count, cache misses, origin request volume, origin latency, 4xx/5xx errors, and WAF blocked requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common mistakes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Including every cookie in the cache key, creating huge cache fragmentation.&lt;/li&gt;
&lt;li&gt;Ignoring personalization, causing incorrect or unsafe response reuse.&lt;/li&gt;
&lt;li&gt;Using huge TTLs everywhere, creating stale-content problems.&lt;/li&gt;
&lt;li&gt;Using tiny TTLs everywhere, destroying much of the CDN benefit and increasing origin load.&lt;/li&gt;
&lt;li&gt;Confusing Cache Policy (what affects cache identity) with Origin Request Policy (what additional information goes to the origin).&lt;/li&gt;
&lt;li&gt;Relying only on invalidation — versioned assets are often cleaner for deployments.&lt;/li&gt;
&lt;li&gt;Making S3 public unnecessarily instead of using OAC for a private CloudFront-backed origin.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Production Mini Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Internet
                            |
                            v
                       CloudFront
                            |
              +-------------+-------------+
              |                           |
          /static/*                    /api/*
              |                           |
              v                           v
             S3                         ALB
                                          |
                                     EC2/ECS
                                          |
                                      Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security: CloudFront routes through &lt;strong&gt;AWS WAF&lt;/strong&gt;, uses &lt;strong&gt;OAC for S3&lt;/strong&gt;, and enforces &lt;strong&gt;HTTPS/TLS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Caching: &lt;code&gt;/static/*&lt;/code&gt; gets a long TTL with versioned assets; &lt;code&gt;/api/public/*&lt;/code&gt; gets carefully configured caching; &lt;code&gt;/api/cart/*&lt;/code&gt; and &lt;code&gt;/api/profile/*&lt;/code&gt; get no/shared cache.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practice Labs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Practice Lab 1 — Basic CloudFront + S3
&lt;/h3&gt;

&lt;p&gt;Set up a CloudFront distribution in front of an S3 bucket, update the underlying S3 object, and observe how caching and invalidation affect whether the updated content is served immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 2 — Observe HIT/MISS
&lt;/h3&gt;

&lt;p&gt;Request a static object through CloudFront twice and identify the cache HIT/MISS behavior between the first and subsequent requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 3 — Query-String Cache Key
&lt;/h3&gt;

&lt;p&gt;Configure a cache policy to include a specific query string, then verify that requests with different query-string values produce independent cache entries while repeated identical requests result in a cache hit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 4 — Personalized Response Safety
&lt;/h3&gt;

&lt;p&gt;Build an endpoint that returns different content based on a cookie, evaluate the risk of the cache key not distinguishing between users, and redesign the caching approach safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 5 — Cache Policy vs Origin Request Policy
&lt;/h3&gt;

&lt;p&gt;Configure a request so an analytics cookie is forwarded to the origin without being included in the cache key, and confirm that cache reuse is preserved while the origin still receives the value it needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 6 — TTL Experiment
&lt;/h3&gt;

&lt;p&gt;Set a short TTL on an object, change the origin content, and compare the object's behavior before and after TTL expiry against manually invalidating it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 7 — Cache Behaviors
&lt;/h3&gt;

&lt;p&gt;Configure separate cache behaviors for &lt;code&gt;/static/*&lt;/code&gt; and &lt;code&gt;/api/*&lt;/code&gt; with different origins and caching rules, connecting CloudFront to existing EC2/ALB infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 8 — Private S3 + OAC
&lt;/h3&gt;

&lt;p&gt;Configure CloudFront to access a private S3 bucket using Origin Access Control, and confirm that direct public access to the bucket remains blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 9 — CloudFront + ALB + EC2
&lt;/h3&gt;

&lt;p&gt;Route CloudFront traffic through an ALB to multiple EC2 instances, separate &lt;code&gt;/static/*&lt;/code&gt; and &lt;code&gt;/api/*&lt;/code&gt; paths, and determine which paths should be cached versus served dynamically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practice Lab 10 — Production-Style Mini Project
&lt;/h3&gt;

&lt;p&gt;Build a production-style CloudFront architecture combining an S3 origin for static assets and an ALB/EC2 origin for API traffic, incorporating OAC, cache/origin request policies, TTL, invalidation, WAF, and monitoring — then validate the design against a range of caching, security, and failure scenarios.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interview Revision
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is CloudFront?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CloudFront is AWS's CDN. It delivers content from geographically distributed edge locations and caches reusable origin responses closer to users, reducing latency and origin load.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What is a cache hit?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A cache hit occurs when the viewer request maps to a valid cached object, so CloudFront can return it without fetching that object from the origin.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What is a cache miss?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A cache miss occurs when CloudFront cannot find a valid matching cached object, so it obtains the object from the origin and can cache the response.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;What is a cache key?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The cache key identifies a cached object. It can include the path and, when configured, selected query strings, headers, and cookies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Cache Policy vs Origin Request Policy?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cache Policy controls what contributes to the cache key and TTL behavior. Origin Request Policy controls additional headers, cookies, and query strings that CloudFront sends to the origin without necessarily making them part of the cache key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why can including too many cookies be bad?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It creates many cache variants, reduces cache reuse and can lower the cache hit ratio.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why can excluding a response-changing cookie be dangerous?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Different users can map to the same cache object and potentially receive the wrong personalized response.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Final Mental Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                VIEWER REQUEST
                       |
                       v
                  CloudFront
                       |
                Build cache key
                       |
                       v
                 Cache lookup
                  /         \
               HIT           MISS
                |              |
                v              v
             Cached         Origin
             object            |
                |              v
                |          Response
                |              |
                |              v
                |          Cache it
                |              |
                +------&amp;lt;--------+
                       |
                       v
                    Viewer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Policy&lt;/strong&gt; → what makes requests different? how long can objects be cached?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Origin Request Policy&lt;/strong&gt; → what additional information does the origin need?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The single most useful rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache only what can safely be reused, make the cache key vary only on information that changes the response, and choose TTL according to the required freshness.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Another step in the AWS learning journey — understanding not just what CloudFront does, but why caching, routing, and security decisions matter in real-world systems.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>AWS EFS Hands-On Lab</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Wed, 26 Aug 2026 04:49:37 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/amazon-efs-hands-on-lab-1l2l</link>
      <guid>https://dev.to/tejas_shinkar/amazon-efs-hands-on-lab-1l2l</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;A practical EFS exercise covering shared file access, NFS security, lifecycle management, persistent mounts, and an EBS-vs-EFS copy experiment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;Build and validate a shared Amazon EFS filesystem that can be mounted by multiple EC2 instances in the same VPC.&lt;/p&gt;

&lt;p&gt;The lab was designed to verify the setup through actual hands-on tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mount the same EFS filesystem on two EC2 instances.&lt;/li&gt;
&lt;li&gt;Prove that a file written by one instance is visible from the other.&lt;/li&gt;
&lt;li&gt;Deliberately remove NFS access and observe the failure.&lt;/li&gt;
&lt;li&gt;Configure EFS Lifecycle Management for a 7-day transition to Infrequent Access.&lt;/li&gt;
&lt;li&gt;Make the EFS mount persistent across an EC2 reboot using &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compare a copy to local EBS-backed storage with a copy to EFS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lab Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Default VPC
        ┌─────────────────────────────────────────────┐
        │                                             │
        │   EC2-1                 EC2-2              │
        │   /mnt/efs              /mnt/efs            │
        │      │                     │                │
        │      └────── NFS :2049 ────┘                │
        │                     │                       │
        │              EFS Mount Targets              │
        │                     │                       │
        │                Amazon EFS                   │
        │                                             │
        └─────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Security Groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ec2-sg&lt;/code&gt; — SSH (22) from My IP&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;efs-sg&lt;/code&gt; — NFS (2049) from &lt;code&gt;ec2-sg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EFS used (&lt;code&gt;EFS_LAB&lt;/code&gt;, File System ID &lt;code&gt;fs-083c15edb9f9076c7&lt;/code&gt;) was created in the same VPC as the EC2 instances, with mount targets available across the VPC's Availability Zones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lab Setup — Two EC2 Instances + One Shared EFS
&lt;/h2&gt;

&lt;p&gt;Two Amazon Linux 2023 EC2 instances (&lt;code&gt;EC2-1&lt;/code&gt;, &lt;code&gt;EC2-2&lt;/code&gt;) were used. Both were associated with &lt;code&gt;ec2-sg&lt;/code&gt;. The EFS filesystem used &lt;code&gt;efs-sg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Security rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ec2-sg&lt;/code&gt; inbound: SSH/TCP 22 from My IP&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;efs-sg&lt;/code&gt; inbound: NFS/TCP 2049 from &lt;code&gt;ec2-sg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The EFS NFS rule was intentionally restricted to the EC2 security group instead of opening port 2049 broadly.&lt;/p&gt;

&lt;p&gt;On both instances:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; amazon-efs-utils
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EFS was mounted with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-t&lt;/span&gt; efs fs-083c15edb9f9076c7:/ /mnt/efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mount was verified with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Shared-File Test — EC2-1 Writes, EC2-2 Reads
&lt;/h2&gt;

&lt;p&gt;On EC2-1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello from EC2-1"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /mnt/efs/shared-file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On EC2-2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/efs/shared-file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello from EC2-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proved that both instances were using the same EFS filesystem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2-1 writes → EFS (same filesystem) → EC2-2 reads&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No manual file transfer between the EC2 instances was performed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Test — Remove NFS Access and Break the EFS Write
&lt;/h2&gt;

&lt;p&gt;The NFS &lt;code&gt;2049&lt;/code&gt; inbound rule was deliberately removed from &lt;code&gt;efs-sg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A new file write was attempted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Dummy text check from EC2-1"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /mnt/efs/shared-file2.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operation could not complete while NFS access was blocked.&lt;/p&gt;

&lt;p&gt;The NFS rule was restored (&lt;code&gt;NFS TCP 2049&lt;/code&gt; from &lt;code&gt;ec2-sg&lt;/code&gt;). The same write was repeated successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dummy text check from EC2-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This demonstrated that the mount directory can remain present while network access to the EFS service is blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lifecycle Management — Configure 7-Day IA Transition
&lt;/h2&gt;

&lt;p&gt;EFS Lifecycle Management was configured as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Transition into Infrequent Access (IA): 7 days since last access&lt;/li&gt;
&lt;li&gt;Transition into Archive: 90 days since last access &lt;em&gt;(existing setting, unchanged)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Transition into Standard: None &lt;em&gt;(existing setting, unchanged)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The 7-day IA policy was successfully configured.&lt;/p&gt;

&lt;p&gt;The actual movement of a file into IA was &lt;strong&gt;not observed&lt;/strong&gt;, because the lab did not wait seven days. The documentation therefore records the configuration rather than claiming an observed storage-class transition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistent Mount Test — &lt;code&gt;/etc/fstab&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Two different behaviors were demonstrated.&lt;/p&gt;

&lt;h3&gt;
  
  
  EC2-1 — Manual Mount Only
&lt;/h3&gt;

&lt;p&gt;EC2-1 was manually unmounted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;umount /mnt/efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mount was checked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mount | &lt;span class="nb"&gt;grep &lt;/span&gt;efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No EFS mount remained.&lt;/p&gt;

&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the EFS filesystem did not automatically return because no EFS entry existed in &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  EC2-2 — Persistent Mount
&lt;/h3&gt;

&lt;p&gt;The existing &lt;code&gt;/etc/fstab&lt;/code&gt; was inspected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There was no EFS entry initially. The following entry was added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'echo "fs-083c15edb9f9076c7:/ /mnt/efs efs _netdev,tls 0 0" &amp;gt;&amp;gt; /etc/fstab'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before rebooting, the configuration was tested safely. The existing mount was removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;umount /mnt/efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;/etc/fstab&lt;/code&gt; was applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mount &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mount was verified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mount | &lt;span class="nb"&gt;grep &lt;/span&gt;efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the test succeeded, EC2-2 was rebooted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reconnecting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mount | &lt;span class="nb"&gt;grep &lt;/span&gt;efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;showed that EFS was automatically mounted.&lt;/p&gt;

&lt;p&gt;The shared file was also still accessible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /mnt/efs/shared-file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello from EC2-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final proof was therefore:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/etc/fstab&lt;/code&gt; → EC2 reboot → EFS automatically mounted → existing shared data available&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  EBS vs EFS Copy Experiment
&lt;/h2&gt;

&lt;p&gt;Before the benchmark:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /
&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /mnt/efs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root EBS filesystem had approximately 8.0 GB total, 1.8 GB used, and 6.3 GB available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial &lt;code&gt;/tmp&lt;/code&gt; Problem
&lt;/h3&gt;

&lt;p&gt;The first attempt created the test file under &lt;code&gt;/tmp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/efs-ebs-test-1GB.img &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024 &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No space left on device
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only about 479 MB was written.&lt;/p&gt;

&lt;p&gt;The cause was identified with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tmpfs   457M   457M   0   100%   /tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;/tmp&lt;/code&gt; was a 457 MB &lt;code&gt;tmpfs&lt;/code&gt;, not the EBS-backed root filesystem.&lt;/p&gt;

&lt;p&gt;The incomplete file was removed and the test moved to &lt;code&gt;/home/ec2-user&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the 1 GiB Test File on EBS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;dd &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/zero &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/ec2-user/efs-ebs-test-1GB.img &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1M &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting file was approximately 1.0 GiB.&lt;/p&gt;

&lt;h3&gt;
  
  
  EBS → EBS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time cp&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /home/ec2-user/efs-ebs-test-1GB.img &lt;span class="se"&gt;\&lt;/span&gt;
  /home/ec2-user/efs-ebs-test-1GB-copy.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;real    0m0.005s
user    0m0.000s
sys     0m0.002s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The copy succeeded.&lt;/p&gt;

&lt;p&gt;This was &lt;strong&gt;not treated as a physical 1 GiB EBS throughput benchmark&lt;/strong&gt;, because filesystem/page caching can make a local repeated copy appear extremely fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  EBS → EFS
&lt;/h3&gt;

&lt;p&gt;The EBS copy was removed.&lt;/p&gt;

&lt;p&gt;The first EFS copy attempt without elevated privileges failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cp: cannot create regular file ... Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EFS mount itself was working; the issue was filesystem write permission.&lt;/p&gt;

&lt;p&gt;The copy was repeated with &lt;code&gt;sudo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;time sudo cp&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  /home/ec2-user/efs-ebs-test-1GB.img &lt;span class="se"&gt;\&lt;/span&gt;
  /mnt/efs/efs-ebs-test-1GB-copy.img
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Observed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;real    0m7.506s
user    0m0.037s
sys     0m0.010s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The destination file was verified successfully on EFS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Interpretation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;EBS → EBS:&lt;/strong&gt; ~0.005 seconds&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EBS → EFS:&lt;/strong&gt; ~7.506 seconds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These numbers should &lt;strong&gt;not&lt;/strong&gt; be presented as a universal EBS-vs-EFS performance ratio.&lt;/p&gt;

&lt;p&gt;The local EBS result was affected by filesystem/page caching and is not equivalent to a cold physical-storage benchmark.&lt;/p&gt;

&lt;p&gt;The useful architectural observation is that EFS introduces network filesystem behavior, while EBS provides block storage directly associated with the EC2 workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems Encountered and Fixes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;What Happened&lt;/th&gt;
&lt;th&gt;Resolution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;EFS write stopped after NFS rule removal&lt;/td&gt;
&lt;td&gt;Network access to EFS was blocked&lt;/td&gt;
&lt;td&gt;Restored TCP 2049 from &lt;code&gt;ec2-sg&lt;/code&gt; to &lt;code&gt;efs-sg&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFS did not persist after manual unmount/reboot&lt;/td&gt;
&lt;td&gt;No EFS entry existed in &lt;code&gt;/etc/fstab&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Added the EFS mount configuration to &lt;code&gt;/etc/fstab&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;grep&lt;/code&gt; failed after reboot&lt;/td&gt;
&lt;td&gt;Verification commands were entered in Windows PowerShell rather than Linux&lt;/td&gt;
&lt;td&gt;Reconnected to EC2 through SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 GiB test file failed under &lt;code&gt;/tmp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/tmp&lt;/code&gt; was a 457 MB &lt;code&gt;tmpfs&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Created the test file under &lt;code&gt;/home/ec2-user&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFS copy returned &lt;code&gt;Permission denied&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Current user lacked write permission at the EFS root&lt;/td&gt;
&lt;td&gt;Repeated the copy using &lt;code&gt;sudo&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EBS copy timing looked unrealistically fast&lt;/td&gt;
&lt;td&gt;Local filesystem/page caching affected &lt;code&gt;cp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Treated it as an observed lab result, not a production benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Evidence Collected
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Two EC2 instances successfully mounted the same EFS filesystem.&lt;/li&gt;
&lt;li&gt;EC2-1 wrote &lt;code&gt;shared-file.txt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;EC2-2 read the same file and returned &lt;code&gt;Hello from EC2-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Removing NFS &lt;code&gt;2049&lt;/code&gt; access prevented the EFS write from completing.&lt;/li&gt;
&lt;li&gt;Restoring the NFS rule allowed the write again.&lt;/li&gt;
&lt;li&gt;EFS Lifecycle Management was configured for a 7-day IA transition.&lt;/li&gt;
&lt;li&gt;EC2-1 demonstrated that a manual mount does not persist after reboot.&lt;/li&gt;
&lt;li&gt;EC2-2 automatically remounted EFS after reboot through &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The shared file remained accessible after reboot.&lt;/li&gt;
&lt;li&gt;A 1 GiB file was successfully copied to EFS.&lt;/li&gt;
&lt;li&gt;The EBS-to-EFS copy completed in approximately 7.5 seconds in this lab environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production-Relevant Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Restrict EFS NFS access by security group instead of exposing TCP 2049 broadly.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;mount -a&lt;/code&gt; to validate an &lt;code&gt;/etc/fstab&lt;/code&gt; change before rebooting an instance.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;_netdev&lt;/code&gt; for persistent network filesystem mounts.&lt;/li&gt;
&lt;li&gt;Separate &lt;strong&gt;network connectivity&lt;/strong&gt; from &lt;strong&gt;filesystem permissions&lt;/strong&gt; when troubleshooting EFS.&lt;/li&gt;
&lt;li&gt;Use Lifecycle Management according to actual access patterns rather than assuming every file belongs in Standard storage.&lt;/li&gt;
&lt;li&gt;EFS is appropriate when multiple compute resources need concurrent access to the same file system; EBS is appropriate for block-storage workloads attached to EC2.&lt;/li&gt;
&lt;li&gt;Do not use a simple &lt;code&gt;cp&lt;/code&gt; test as a production storage benchmark. Caching, EC2 instance type, EBS configuration, EFS throughput mode, network conditions, file size, and workload characteristics all affect the result.&lt;/li&gt;
&lt;li&gt;Check what filesystem actually backs a directory before using it for storage tests; &lt;code&gt;/tmp&lt;/code&gt; in this lab was a &lt;code&gt;tmpfs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Clean up EFS, EC2, security groups, and test files after the lab to avoid unnecessary charges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lab Completion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Full EC2 + EFS shared filesystem lab ✅&lt;/li&gt;
&lt;li&gt;Break and restore NFS access ✅&lt;/li&gt;
&lt;li&gt;Configure 7-day EFS IA lifecycle policy ✅&lt;/li&gt;
&lt;li&gt;Persist EFS mount through EC2 reboot ✅&lt;/li&gt;
&lt;li&gt;EBS vs EFS copy experiment ✅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The lab went beyond a successful service setup: it included deliberate failure, troubleshooting, permissions, persistence testing, filesystem identification, lifecycle configuration, and a controlled storage experiment.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>Your Torrent Isn't Downloading From "The Internet"</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Tue, 25 Aug 2026 13:40:47 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/your-torrent-isnt-downloading-from-the-internet-42bp</link>
      <guid>https://dev.to/tejas_shinkar/your-torrent-isnt-downloading-from-the-internet-42bp</guid>
      <description>&lt;h1&gt;
  
  
  We've All Used Torrents. But Who's Actually Sending You the File? 👀
&lt;/h1&gt;

&lt;p&gt;You've downloaded a 5GB movie/game/software through a torrent.&lt;/p&gt;

&lt;p&gt;But there's no giant server sitting somewhere going:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Yep, I'll upload 5GB to this guy."&lt;/em&gt; 😂&lt;/p&gt;

&lt;p&gt;Instead, that file is split into tiny pieces.&lt;/p&gt;

&lt;p&gt;You download different pieces from different people at the same time — and while doing that, you upload the pieces you already have to others.&lt;/p&gt;

&lt;p&gt;So instead of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Server → You&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;it's more like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You ↔  You ↔ You ↔ You ↔ You&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everyone is both a downloader and an uploader.&lt;/p&gt;

&lt;p&gt;Basically:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"I'll give you my piece if you give me yours."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And suddenly that torrent client you opened 10 years ago is looking suspiciously like a distributed systems lecture. 💀&lt;/p&gt;

</description>
      <category>technology</category>
      <category>programming</category>
      <category>distributedsystems</category>
      <category>networking</category>
    </item>
    <item>
      <title>AWS VPC Hands-On Lab</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Tue, 25 Aug 2026 11:52:10 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-vpc-hands-on-36mp</link>
      <guid>https://dev.to/tejas_shinkar/aws-vpc-hands-on-36mp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Focus:&lt;/strong&gt; A practical record of the VPC networking work completed in AWS.&lt;br&gt;
This document records what was built, tested, observed, troubleshot, and learned during the hands-on sessions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  📚 VPC Theory &amp;amp; Concepts
&lt;/h2&gt;

&lt;p&gt;The concepts behind these hands-on practicals are covered in the following VPC articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPC Part 1 — Networking Fundamentals:&lt;/strong&gt; VPC, Subnets, Internet Gateway, NAT Gateway, Security Groups vs NACLs&lt;br&gt;
&lt;a href="https://dev.to/tejas_shinkar/aws-networking-fundamentals-vpc-subnets-igw-nat-the-sg-vs-nacl-battle-eel"&gt;https://dev.to/tejas_shinkar/aws-networking-fundamentals-vpc-subnets-igw-nat-the-sg-vs-nacl-battle-eel&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPC Part 2 — Extended Networking Concepts:&lt;/strong&gt; Route Tables, Blackhole Routes, VPC Flow Logs, VPC Endpoints, Transit Gateway&lt;br&gt;
&lt;a href="https://dev.to/tejas_shinkar/aws-vpc-extended-route-tables-blackhole-routes-flow-logs-vpc-endpoints-transit-gateway-og4"&gt;https://dev.to/tejas_shinkar/aws-vpc-extended-route-tables-blackhole-routes-flow-logs-vpc-endpoints-transit-gateway-og4&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;These articles cover the underlying concepts in detail, while this document focuses specifically on the &lt;strong&gt;hands-on implementation, testing, troubleshooting, and observations&lt;/strong&gt; from the VPC practicals.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1.Building a VPC from Scratch with Public and Private Subnets
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Build the core VPC networking architecture manually instead of using the AWS &lt;strong&gt;VPC and more&lt;/strong&gt; wizard. The goal was to understand how VPC, subnets, route tables, an Internet Gateway, a NAT Gateway, and EC2 instances fit together to provide public and private connectivity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC: 10.200.0.0/16

                         Internet
                            |
                           IGW
                            |
              +-------------+-------------+
              |                           |
       Public Subnet                Private Subnet
       10.200.0.0/24                10.200.1.0/24
              |                           |
         Public EC2                   Private EC2
              |                           |
              +---- NAT Gateway &amp;lt;---------+
                         |
                  Public Subnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;VPC: &lt;code&gt;lab-vpc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;CIDR: &lt;code&gt;10.200.0.0/16&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Public subnet: &lt;code&gt;10.200.0.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Private subnet: &lt;code&gt;10.200.1.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Internet Gateway: &lt;code&gt;lab-igw&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Public route table&lt;/li&gt;
&lt;li&gt;Private route table&lt;/li&gt;
&lt;li&gt;NAT Gateway: &lt;code&gt;lab-nat-gw&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Elastic IP for NAT Gateway&lt;/li&gt;
&lt;li&gt;Public EC2&lt;/li&gt;
&lt;li&gt;Private EC2&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;h4&gt;
  
  
  VPC
&lt;/h4&gt;

&lt;p&gt;Created VPC &lt;code&gt;lab-vpc&lt;/code&gt; with CIDR &lt;code&gt;10.200.0.0/16&lt;/code&gt;, using &lt;strong&gt;VPC only&lt;/strong&gt; so the networking components could be built manually.&lt;/p&gt;

&lt;h4&gt;
  
  
  Subnets
&lt;/h4&gt;

&lt;p&gt;Created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;public-subnet-1&lt;/code&gt; — &lt;code&gt;10.200.0.0/24&lt;/code&gt; — &lt;code&gt;ap-south-1a&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;private-subnet-1&lt;/code&gt; — &lt;code&gt;10.200.1.0/24&lt;/code&gt; — &lt;code&gt;ap-south-1b&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The subnets were placed in different Availability Zones to introduce a basic multi-AZ layout.&lt;/p&gt;

&lt;h4&gt;
  
  
  Internet Gateway
&lt;/h4&gt;

&lt;p&gt;Created and attached &lt;code&gt;lab-igw&lt;/code&gt;. The IGW alone was not enough to make the subnet public; the public subnet also needed a route to the IGW.&lt;/p&gt;

&lt;h4&gt;
  
  
  Public Route Table
&lt;/h4&gt;

&lt;p&gt;Configured &lt;code&gt;0.0.0.0/0 → Internet Gateway&lt;/code&gt; and associated it with &lt;code&gt;public-subnet-1&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  NAT Gateway
&lt;/h4&gt;

&lt;p&gt;Created &lt;code&gt;lab-nat-gw&lt;/code&gt;. The NAT Gateway was placed in the &lt;strong&gt;public subnet&lt;/strong&gt; and given an Elastic IP.&lt;/p&gt;

&lt;p&gt;The private route table was then configured with &lt;code&gt;0.0.0.0/0 → NAT Gateway&lt;/code&gt;. This produced the outbound path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Private EC2 → Private Route Table → NAT Gateway → Internet Gateway → Internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  EC2 Testing
&lt;/h4&gt;

&lt;p&gt;Created &lt;code&gt;public-ec2&lt;/code&gt; and &lt;code&gt;private-ec2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The public EC2 had a public IP. The private EC2 had no public IP.&lt;/p&gt;

&lt;p&gt;From the public EC2, we connected to the private EC2 using its private IP.&lt;/p&gt;

&lt;p&gt;From the private EC2, we tested outbound Internet access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://checkip.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The returned public IP corresponded to the NAT Gateway's Elastic IP rather than the private EC2's private address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence / Result
&lt;/h3&gt;

&lt;p&gt;The lab successfully demonstrated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Public EC2 → Internet&lt;/p&gt;

&lt;p&gt;Private EC2 → NAT Gateway → Internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The private EC2 could reach the Internet without having a public IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems / Troubleshooting
&lt;/h3&gt;

&lt;h4&gt;
  
  
  EC2 Instance Connect
&lt;/h4&gt;

&lt;p&gt;Browser-based EC2 Instance Connect caused connectivity issues during the lab. The SSH Security Group rule was temporarily broadened to &lt;code&gt;0.0.0.0/0&lt;/code&gt; to avoid making SSH access itself the blocker.&lt;/p&gt;

&lt;p&gt;This was a temporary lab workaround, not a production configuration.&lt;/p&gt;

&lt;h4&gt;
  
  
  NAT Gateway placement
&lt;/h4&gt;

&lt;p&gt;The placement initially raised the question of why the NAT Gateway belongs in a public subnet. The final working architecture made the reason clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NAT Gateway → Public Subnet → Public Route Table → IGW → Internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Hands-On Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A subnet uses the route table associated with it.&lt;/li&gt;
&lt;li&gt;The public route table pointed to the IGW.&lt;/li&gt;
&lt;li&gt;The private route table pointed to the NAT Gateway.&lt;/li&gt;
&lt;li&gt;The NAT Gateway provided outbound Internet access for the private EC2.&lt;/li&gt;
&lt;li&gt;A private EC2 does not need a public IP to initiate outbound Internet traffic.&lt;/li&gt;
&lt;li&gt;NAT Gateway placement and routing must be considered together.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2.Blocking HTTP While Allowing HTTPS with a Custom NACL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Create a custom Network ACL for a public subnet and use an Apache web server to prove that NACL rules can selectively allow or reject traffic.&lt;/p&gt;

&lt;p&gt;The practical goal was simple: &lt;strong&gt;HTTP should fail, while HTTPS should work&lt;/strong&gt;, allowing the NACL decision to be observed through an actual application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
   |
   v
Internet Gateway
   |
   v
Custom NACL
   |
   v
Public Subnet
   |
   v
EC2
 |
Apache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Custom VPC for the lab&lt;/li&gt;
&lt;li&gt;Public subnet&lt;/li&gt;
&lt;li&gt;Custom Network ACL&lt;/li&gt;
&lt;li&gt;Security Group&lt;/li&gt;
&lt;li&gt;Public EC2&lt;/li&gt;
&lt;li&gt;Apache HTTP server&lt;/li&gt;
&lt;li&gt;HTTPS configuration using a temporary self-signed certificate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NACL Configuration
&lt;/h3&gt;

&lt;p&gt;The important rules were:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;DENY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;ALLOW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;ALLOW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return traffic&lt;/td&gt;
&lt;td&gt;1024–65535 (TCP)&lt;/td&gt;
&lt;td&gt;ALLOW&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The SSH rule was temporarily broadened during connectivity troubleshooting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apache Setup
&lt;/h3&gt;

&lt;p&gt;Installed Apache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;httpd &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Created a test page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;h1&amp;gt;NACL Lab - HTTP/HTTPS Test&amp;lt;/h1&amp;gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /var/www/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enabled HTTPS using &lt;code&gt;mod_ssl&lt;/code&gt; and a temporary self-signed certificate. Apache was verified to be listening on port 443.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP Test
&lt;/h3&gt;

&lt;p&gt;Opened &lt;code&gt;http://&amp;lt;EC2-public-IP&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Result: &lt;code&gt;ERR_CONNECTION_TIMED_OUT&lt;/code&gt; — the request was blocked by the NACL.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTPS Test
&lt;/h3&gt;

&lt;p&gt;Opened &lt;code&gt;https://&amp;lt;EC2-public-IP&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The browser displayed the expected self-signed certificate warning. After proceeding through the warning, the Apache page loaded successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence / Result
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;HTTP :80 → BLOCKED&lt;/p&gt;

&lt;p&gt;HTTPS :443 → WORKED&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This gave us a direct application-level demonstration of the NACL rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems / Troubleshooting
&lt;/h3&gt;

&lt;p&gt;During Apache installation, the package manager initially timed out.&lt;/p&gt;

&lt;p&gt;The problem turned out to be the NACL's stateless return traffic. The EC2 could send &lt;code&gt;EC2:ephemeral-port → Internet:443&lt;/code&gt;, but the response returned to the EC2's ephemeral port and was not initially allowed.&lt;/p&gt;

&lt;p&gt;Adding an inbound rule (&lt;code&gt;1024-65535 → ALLOW&lt;/code&gt;) resolved the package installation.&lt;/p&gt;

&lt;p&gt;This was an important real failure rather than a theoretical example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hands-On Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;NACL rules apply at the subnet level.&lt;/li&gt;
&lt;li&gt;Security Group and NACL rules both affect whether traffic reaches the instance.&lt;/li&gt;
&lt;li&gt;A restrictive NACL must account for the return path.&lt;/li&gt;
&lt;li&gt;Ephemeral ports become important when working with stateless NACLs.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3.Observing Network Traffic with VPC Flow Logs and CloudWatch
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Enable VPC Flow Logs and use the resulting CloudWatch records to inspect real network traffic.&lt;/p&gt;

&lt;p&gt;The goal was to move from simply configuring networking to &lt;strong&gt;observing what actually happened on the network&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Configured
&lt;/h3&gt;

&lt;p&gt;Flow Logs were configured for &lt;strong&gt;all&lt;/strong&gt; traffic, sent to &lt;strong&gt;CloudWatch Logs&lt;/strong&gt;, with &lt;strong&gt;1-minute aggregation&lt;/strong&gt;, in log group &lt;code&gt;/vpc/flow-logs/nacl-lab&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;AWS created/used the required IAM service role for publishing Flow Logs to CloudWatch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traffic Generated
&lt;/h3&gt;

&lt;p&gt;From the EC2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://checkip.amazonaws.com
curl https://checkip.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSH traffic had also been generated by connecting to the instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  CloudWatch Investigation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;CloudWatch → Logs → Log groups → &lt;code&gt;/vpc/flow-logs/nacl-lab&lt;/code&gt; → Log stream&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The records exposed fields such as Source IP, Destination IP, Source Port, Destination Port, Protocol, Action, Bytes, and Packets.&lt;/p&gt;

&lt;p&gt;One observed HTTPS-related flow contained the important values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source IP:         13.213.253.85
Destination IP:    10.50.1.165
Source Port:       443
Destination Port:  32798
Protocol:          6 (TCP)
Action:            ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reverse direction showed the corresponding flow back to the EC2's ephemeral port. We also observed &lt;code&gt;REJECT&lt;/code&gt; records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence / Result
&lt;/h3&gt;

&lt;p&gt;The Flow Logs successfully showed both &lt;code&gt;ACCEPT&lt;/code&gt; and &lt;code&gt;REJECT&lt;/code&gt; traffic, and allowed us to identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who was communicating&lt;/li&gt;
&lt;li&gt;where traffic was going&lt;/li&gt;
&lt;li&gt;which ports were involved&lt;/li&gt;
&lt;li&gt;which protocol was used&lt;/li&gt;
&lt;li&gt;whether the network flow was accepted or rejected&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hands-On Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Flow Logs are useful for troubleshooting network behavior.&lt;/li&gt;
&lt;li&gt;They can expose rejected traffic that is otherwise difficult to understand.&lt;/li&gt;
&lt;li&gt;They provide network-level evidence but do not replace application or authentication logs.&lt;/li&gt;
&lt;li&gt;Flow Log records may take some time to appear because of aggregation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4.Proving the Stateless NACL Gotcha with a Private EC2
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Build a fresh private-network scenario specifically to demonstrate why a restrictive NACL can allow an outbound HTTPS request but still break the connection when the response returns.&lt;/p&gt;

&lt;p&gt;The experiment was deliberately designed to fail first and then be fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Internet
                            |
                           IGW
                            |
                    Public Route Table
                            |
                      NAT Gateway
                            |
                    Private Route Table
                            |
                     Private Subnet
                            |
                       Private EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lab CIDRs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC: &lt;code&gt;10.60.0.0/16&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Public subnet: &lt;code&gt;10.60.0.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Private subnet: &lt;code&gt;10.60.1.0/24&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Routing Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Public route table: &lt;code&gt;0.0.0.0/0 → Internet Gateway&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Private route table: &lt;code&gt;0.0.0.0/0 → NAT Gateway&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The NAT Gateway was placed in the public subnet. The EC2 was placed in the private subnet with &lt;strong&gt;Auto-assign Public IP: Disabled&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial NACL
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Outbound:&lt;/strong&gt; &lt;code&gt;100 → HTTPS 443 → ALLOW&lt;/code&gt;, &lt;code&gt;* → ALL → DENY&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Inbound:&lt;/strong&gt; &lt;code&gt;* → ALL → DENY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The deliberate design was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Allow the HTTPS request out, but do not allow the return traffic back in.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  First Test — Failure
&lt;/h3&gt;

&lt;p&gt;From the private EC2:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://checkip.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command hung. The request path was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 → NACL outbound → HTTPS 443 ALLOW → NAT Gateway → Internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The response path returned to an ephemeral port:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Internet:443 → EC2:ephemeral-port → NACL inbound → DENY&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Fix
&lt;/h3&gt;

&lt;p&gt;Added the missing inbound rule: &lt;code&gt;100 → TCP 1024-65535 → ALLOW&lt;/code&gt;. The catch-all deny remained below it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Second Test — Success
&lt;/h3&gt;

&lt;p&gt;Ran again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://checkip.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: &lt;code&gt;13.202.19.75&lt;/code&gt; — the HTTPS request now completed successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evidence / Result
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Before:&lt;/strong&gt; Outbound 443 → ALLOW. Return traffic → BLOCKED. &lt;code&gt;curl&lt;/code&gt; → timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After:&lt;/strong&gt; Outbound 443 → ALLOW. Inbound ephemeral ports → ALLOW. &lt;code&gt;curl&lt;/code&gt; → public IP returned.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Problems / Troubleshooting
&lt;/h3&gt;

&lt;p&gt;The main issue was initially easy to misinterpret as a NAT or Internet problem.&lt;/p&gt;

&lt;p&gt;Following the traffic path isolated the failure to the inbound NACL rule. The lab therefore became a useful troubleshooting exercise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Request works? → check return path → NACL is stateless → check ephemeral ports&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Hands-On Takeaways
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A private EC2 can reach the Internet through a NAT Gateway without a public IP.&lt;/li&gt;
&lt;li&gt;The private route table must point to the NAT Gateway.&lt;/li&gt;
&lt;li&gt;The public route table used by the NAT Gateway must point to the IGW.&lt;/li&gt;
&lt;li&gt;Restrictive NACLs must explicitly permit both sides of a connection.&lt;/li&gt;
&lt;li&gt;Ephemeral ports are not an abstract networking detail; they directly affected the lab's outcome.&lt;/li&gt;
&lt;li&gt;Following the packet path was more useful than randomly changing AWS resources.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practical Reasoning Drills
&lt;/h2&gt;

&lt;h3&gt;
  
  
  P1.Longest Prefix Match — Choosing the Most Specific Route
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Objective
&lt;/h4&gt;

&lt;p&gt;Use a route table with overlapping routes and determine which route AWS would select for different destination IPs.&lt;/p&gt;

&lt;p&gt;Given:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10.0.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;10.0.5.0/24&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pcx-abc&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0.0.0.0/0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;igw-xyz&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Results
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;10.0.5.10&lt;/code&gt; → &lt;code&gt;10.0.5.0/24&lt;/code&gt; → &lt;code&gt;pcx-abc&lt;/code&gt; (reason: &lt;code&gt;/24&lt;/code&gt; is more specific than &lt;code&gt;/16&lt;/code&gt; and &lt;code&gt;/0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;10.0.9.10&lt;/code&gt; → &lt;code&gt;10.0.0.0/16&lt;/code&gt; → Local (reason: it does not belong to &lt;code&gt;10.0.5.0/24&lt;/code&gt;, but it does belong to &lt;code&gt;10.0.0.0/16&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;52.1.1.1&lt;/code&gt; → &lt;code&gt;0.0.0.0/0&lt;/code&gt; → &lt;code&gt;igw-xyz&lt;/code&gt; (reason: it does not match either &lt;code&gt;10.x.x.x&lt;/code&gt; route, so the default route is used)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Takeaway
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;More specific prefix wins: &lt;code&gt;/24 &amp;gt; /16 &amp;gt; /0&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  p2.NAT Gateway Deletion — Diagnosing a Blackhole Route
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Objective
&lt;/h4&gt;

&lt;p&gt;Work through a realistic failure where a NAT Gateway is deleted but private EC2 instances still depend on it for outbound Internet access.&lt;/p&gt;

&lt;h4&gt;
  
  
  Scenario
&lt;/h4&gt;

&lt;p&gt;Private route table: &lt;code&gt;0.0.0.0/0 → NAT Gateway&lt;/code&gt;. NAT Gateway is deleted. The route can become &lt;code&gt;0.0.0.0/0 → NAT Gateway (BLACKHOLE)&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Troubleshooting Path
&lt;/h4&gt;

&lt;p&gt;Follow the traffic path:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 → Security Group → NACL → Private Route Table → NAT Gateway → Public Route Table → IGW → Internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this exact scenario, the private route table is the high-value first check.&lt;/p&gt;

&lt;h4&gt;
  
  
  Takeaway
&lt;/h4&gt;

&lt;p&gt;Don't immediately recreate the NAT Gateway. First identify where the traffic path breaks and check whether the route points to a valid target.&lt;/p&gt;




&lt;h3&gt;
  
  
  P3.Choosing Gateway vs Interface VPC Endpoints
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Objective
&lt;/h4&gt;

&lt;p&gt;Decide which endpoint type should be used when private workloads need AWS service access without relying on a public Internet path.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Endpoint Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;Gateway Endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;td&gt;Gateway Endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets Manager&lt;/td&gt;
&lt;td&gt;Interface Endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Takeaway
&lt;/h4&gt;

&lt;p&gt;Gateway endpoints integrate with route tables. Interface endpoints use private network interfaces/PrivateLink.&lt;/p&gt;




&lt;h3&gt;
  
  
  P4.Transit Gateway vs VPC Peering — Scaling the Connectivity Model
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Objective
&lt;/h4&gt;

&lt;p&gt;Choose an architecture for an environment that has 3 VPCs today, expects 12 additional VPCs, and also needs VPN connectivity to the head office.&lt;/p&gt;

&lt;h4&gt;
  
  
  Peering Math
&lt;/h4&gt;

&lt;p&gt;Full-mesh peering connections: &lt;code&gt;N × (N - 1) / 2&lt;/code&gt;. For 3 VPCs → 3 connections. For 15 VPCs → 105 connections.&lt;/p&gt;

&lt;h4&gt;
  
  
  Decision
&lt;/h4&gt;

&lt;p&gt;For this growth scenario, &lt;strong&gt;Transit Gateway&lt;/strong&gt; is the more scalable direction. The model becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC-A ─┐
VPC-B ─┤
VPC-C ─┼── Transit Gateway ── VPN ── Head Office
VPC-D ─┤
...    ┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Takeaway
&lt;/h4&gt;

&lt;p&gt;The decision was based on &lt;strong&gt;connectivity scale and centralized routing&lt;/strong&gt;, not simply on the number of VPCs that exist today.&lt;/p&gt;




&lt;h3&gt;
  
  
  VPC Flow Logs — Investigating Suspicious SSH Traffic
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Objective
&lt;/h4&gt;

&lt;p&gt;Use Flow Logs as network-level evidence when investigating a suspected unauthorized SSH attempt against an EC2 instance.&lt;/p&gt;

&lt;h4&gt;
  
  
  Investigation
&lt;/h4&gt;

&lt;p&gt;Enable Flow Logs for the relevant VPC/subnet and send records to CloudWatch. Look for TCP traffic on destination port 22, matching the target EC2's destination IP, from a suspicious source IP, and check whether the action was &lt;code&gt;ACCEPT&lt;/code&gt; or &lt;code&gt;REJECT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The investigation can establish that a source IP attempted network-level SSH traffic.&lt;/p&gt;

&lt;h4&gt;
  
  
  Important distinction
&lt;/h4&gt;

&lt;p&gt;An &lt;code&gt;ACCEPT&lt;/code&gt; Flow Log does &lt;strong&gt;not&lt;/strong&gt; prove that SSH authentication succeeded. If network traffic was accepted, OS-level SSH/authentication logs must be checked to determine whether a login actually occurred.&lt;/p&gt;




&lt;h2&gt;
  
  
  Problems and Lessons Across the Labs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EC2 Instance Connect vs Network Configuration
&lt;/h3&gt;

&lt;p&gt;We encountered multiple browser-based EC2 Instance Connect issues.&lt;/p&gt;

&lt;p&gt;The important lesson was to distinguish &lt;em&gt;"can I manage/connect to the instance?"&lt;/em&gt; from &lt;em&gt;"is the network experiment itself working?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Temporary broad SSH rules were used only to remove management connectivity as a variable during the labs.&lt;/p&gt;

&lt;h3&gt;
  
  
  NAT Gateway and Private Subnet Design
&lt;/h3&gt;

&lt;p&gt;The NAT Gateway placement initially required clarification. The working architecture established the relationship:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Private EC2 → Private Route Table → NAT Gateway → Public Route Table → IGW → Internet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The NAT Gateway therefore belongs in a subnet with a working path to the IGW.&lt;/p&gt;

&lt;h3&gt;
  
  
  Route Table Associations
&lt;/h3&gt;

&lt;p&gt;A subnet association means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The subnet uses this route table for its routing decisions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This distinction became important when building separate public and private routing paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Duplicate Default Route
&lt;/h3&gt;

&lt;p&gt;A private route table already containing &lt;code&gt;0.0.0.0/0 → IGW&lt;/code&gt; could not also contain another &lt;code&gt;0.0.0.0/0 → NAT Gateway&lt;/code&gt;. The existing default route had to be replaced with the correct target.&lt;/p&gt;

&lt;h3&gt;
  
  
  NACL Return Traffic
&lt;/h3&gt;

&lt;p&gt;The most valuable troubleshooting lesson was that a restrictive NACL can allow the outbound request but still break the connection because the response arrives on an ephemeral port.&lt;/p&gt;

&lt;p&gt;This was observed directly through failed commands and then fixed by adding &lt;code&gt;TCP 1024-65535 → ALLOW&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flow Logs as Evidence
&lt;/h3&gt;

&lt;p&gt;Flow Logs provided actual network records instead of relying only on assumptions about what the network was doing. They were useful for identifying source, destination, ports, protocol, and &lt;code&gt;ACCEPT&lt;/code&gt;/&lt;code&gt;REJECT&lt;/code&gt; status.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production-Relevant Takeaways from the Hands-On Work
&lt;/h2&gt;

&lt;p&gt;These are intentionally limited to lessons that came directly from the practical work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate public and private traffic paths
&lt;/h3&gt;

&lt;p&gt;A production design should not expose application/database resources unnecessarily. A common model is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Internet → ALB / Public tier → Private application tier → Private database tier&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Plan NAT for both availability and cost
&lt;/h3&gt;

&lt;p&gt;For multi-AZ workloads, a single NAT Gateway can create a cross-AZ dependency. NAT Gateway hourly and data-processing costs also make unnecessary outbound Internet traffic worth considering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use least-privilege Security Groups
&lt;/h3&gt;

&lt;p&gt;The labs temporarily used broad SSH access to troubleshoot EC2 Instance Connect. Production should instead restrict management access to known sources or use an appropriate private management path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat NACLs as an explicit network boundary
&lt;/h3&gt;

&lt;p&gt;If restrictive NACLs are used, both directions of a connection need to be considered because NACLs are stateless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use VPC Flow Logs for visibility
&lt;/h3&gt;

&lt;p&gt;Flow Logs can be useful for troubleshooting, rejected-traffic analysis, security investigations, and validating routing/security changes. But they provide network-level evidence, not application-level authentication evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design from the traffic requirement
&lt;/h3&gt;

&lt;p&gt;A useful sequence is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What traffic is required? → Where does it originate? → Where does it need to go? → What route provides that path? → What security controls permit it? → How will we observe/troubleshoot it?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Remaining VPC Practicals
&lt;/h2&gt;

&lt;p&gt;The following two practicals remain to be completed:&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting VPCs with Peering, Then Scaling the Design with Transit Gateway
&lt;/h3&gt;

&lt;p&gt;Build two VPCs, establish private connectivity using VPC Peering, then introduce a third VPC and move toward a Transit Gateway-based connectivity model.&lt;/p&gt;

&lt;p&gt;The focus will be on the route changes and traffic paths required for VPC-to-VPC communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Private S3 Access with a Gateway Endpoint and Flow Log Verification
&lt;/h3&gt;

&lt;p&gt;Add an S3 Gateway Endpoint to a VPC and verify the resulting traffic path using VPC Flow Logs.&lt;/p&gt;

&lt;p&gt;The goal is to confirm how the endpoint changes the path used for S3 access and to distinguish that traffic from an Internet/NAT-based path.&lt;/p&gt;




</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>AWS S3 Hands-On Lab</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Fri, 21 Aug 2026 12:36:02 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/amazon-s3-hands-on-practicals-5f72</link>
      <guid>https://dev.to/tejas_shinkar/amazon-s3-hands-on-practicals-5f72</guid>
      <description>&lt;p&gt;I recently worked through a hands-on Amazon S3 practical series covering the features I would actually expect to use while working with AWS storage.&lt;/p&gt;

&lt;p&gt;Instead of only documenting definitions, this post focuses on &lt;strong&gt;what I configured, the commands I used, how I verified the behavior, and what I observed when something went wrong&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For the concepts behind these practicals, I have already covered S3 in two detailed sessions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session 1:&lt;/strong&gt; &lt;a href="https://dev.to/tejas_shinkar/aws-s3-deep-dive-objects-encryption-bucket-policies-everything-in-between-3k8k"&gt;AWS S3 Deep Dive — Objects, Encryption, Bucket Policies &amp;amp; Everything In Between&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session 2:&lt;/strong&gt; &lt;a href="https://dev.to/tejas_shinkar/aws-s3-versioning-static-hosting-cors-object-lock-more-4m5"&gt;AWS S3 — Versioning, Static Hosting, CORS, Object Lock &amp;amp; More&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the &lt;strong&gt;practical companion&lt;/strong&gt; to those two sessions. The concepts are covered there; here I focus on actually building, testing, verifying, and troubleshooting the S3 features.&lt;/p&gt;

&lt;p&gt;The labs covered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;S3 bucket configuration and lifecycle management&lt;/li&gt;
&lt;li&gt;Bucket policies with IAM, EC2 and HTTPS-only access&lt;/li&gt;
&lt;li&gt;SSE-KMS encryption with CloudTrail verification&lt;/li&gt;
&lt;li&gt;Pre-signed URLs&lt;/li&gt;
&lt;li&gt;AWS CLI &lt;code&gt;s3 sync&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;S3 Versioning and version recovery&lt;/li&gt;
&lt;li&gt;Static website hosting&lt;/li&gt;
&lt;li&gt;S3 CORS&lt;/li&gt;
&lt;li&gt;S3 Object Lock&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This is a practical write-up, so I have intentionally kept the focus on implementation and verification rather than turning it into a generic S3 theory article.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. S3 Bucket Configuration and Lifecycle Management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Create an S3 bucket with a secure baseline and configure a lifecycle rule that automatically transitions objects to lower-cost storage classes over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;For the lab:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block Public Access remained enabled.&lt;/li&gt;
&lt;li&gt;Bucket Versioning was enabled.&lt;/li&gt;
&lt;li&gt;Lifecycle rule: &lt;code&gt;s3-lab-lifecycle&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The rule applied to all objects.&lt;/li&gt;
&lt;li&gt;Current objects transition to:

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Standard-IA after 30 days&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Glacier Flexible Retrieval after 90 days&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lifecycle flow was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Day 0 → object uploaded → Day 30 → Standard-IA → Day 90 → Glacier Flexible Retrieval&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The lifecycle rule was successfully created and enabled, and the S3 console confirmed the configured transition periods.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Demonstrates
&lt;/h3&gt;

&lt;p&gt;Instead of manually moving old objects, S3 Lifecycle can automatically transition objects between storage classes based on age and the rules we define.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. S3 Bucket Policy — IAM Read, EC2 Upload and HTTPS-only Access
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Configure a bucket policy that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allows a specific IAM user to read objects.&lt;/li&gt;
&lt;li&gt;Allows an EC2 role to upload objects.&lt;/li&gt;
&lt;li&gt;Denies S3 access when the request is not using HTTPS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bucket:&lt;/strong&gt; &lt;code&gt;policytesting-bucket-ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM User:&lt;/strong&gt; &lt;code&gt;Tejas-IAM&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EC2 Role:&lt;/strong&gt; &lt;code&gt;EC2-S3-ReadOnly-Role&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; &lt;code&gt;ap-south-1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Policy Design
&lt;/h3&gt;

&lt;p&gt;The policy contained three logical statements:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IAM User → &lt;code&gt;s3:GetObject&lt;/code&gt; → S3 bucket&lt;/p&gt;

&lt;p&gt;EC2 IAM Role → &lt;code&gt;s3:PutObject&lt;/code&gt; → S3 bucket&lt;/p&gt;

&lt;p&gt;HTTP request → Explicit Deny → S3 bucket&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important security condition was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"Bool"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aws:SecureTransport"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"false"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This denies requests that are not using HTTPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;The IAM user successfully downloaded an object from S3 using AWS CLI.&lt;/p&gt;

&lt;p&gt;An EC2 instance was launched with &lt;code&gt;EC2-S3-ReadOnly-Role&lt;/code&gt; attached, and the role was verified from the instance. The EC2 instance was then able to upload an object to S3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;Both permission paths worked as intended:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IAM user → object read&lt;/li&gt;
&lt;li&gt;EC2 role → object upload&lt;/li&gt;
&lt;li&gt;Non-HTTPS access → explicitly denied&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical Takeaway
&lt;/h3&gt;

&lt;p&gt;A useful S3 security pattern is to combine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;identity-based permissions,&lt;/li&gt;
&lt;li&gt;resource-based bucket policies,&lt;/li&gt;
&lt;li&gt;and explicit security-condition denies.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. SSE-KMS Encryption with CloudTrail Verification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Configure S3 default encryption using a customer-managed KMS key and verify the resulting cryptographic operations through CloudTrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  KMS Configuration
&lt;/h3&gt;

&lt;p&gt;The lab used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key alias:&lt;/strong&gt; &lt;code&gt;s3-lab-key&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key type:&lt;/strong&gt; Symmetric&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key usage:&lt;/strong&gt; Encrypt and decrypt&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; &lt;code&gt;ap-south-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key administrator:&lt;/strong&gt; &lt;code&gt;Tejas-IAM&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key user:&lt;/strong&gt; &lt;code&gt;Tejas-IAM&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configure S3 Encryption
&lt;/h3&gt;

&lt;p&gt;The bucket &lt;code&gt;sse-kms-bucket-tejas&lt;/code&gt; was configured for server-side encryption using the customer-managed KMS key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;After uploading and downloading an object, I checked CloudTrail Event History. The relevant KMS operations were &lt;code&gt;GenerateDataKey&lt;/code&gt; and &lt;code&gt;Decrypt&lt;/code&gt; — &lt;code&gt;GenerateDataKey&lt;/code&gt; appeared during the encryption flow, while &lt;code&gt;Decrypt&lt;/code&gt; events were associated with accessing encrypted data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;Initially, the expected cryptographic events were not obvious in CloudTrail. Instead of relying only on the initial event list, I changed the lookup to search by &lt;strong&gt;Event name&lt;/strong&gt;. That exposed the expected &lt;code&gt;GenerateDataKey&lt;/code&gt; and &lt;code&gt;Decrypt&lt;/code&gt; events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;SSE-KMS was successfully configured and the related KMS operations were visible through CloudTrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Takeaway
&lt;/h3&gt;

&lt;p&gt;Encryption configuration alone is not always enough when troubleshooting. CloudTrail can provide the audit trail needed to understand which AWS services and cryptographic operations were involved.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. S3 Pre-signed URL
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Keep an S3 object private while providing temporary access through a pre-signed URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Object
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bucket:&lt;/strong&gt; &lt;code&gt;sse-kms-bucket-tejas&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object:&lt;/strong&gt; &lt;code&gt;presigned-test.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The object itself remained private. No public permissions were added.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generate the URL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 presign s3://sse-kms-bucket-tejas/presigned-test.txt &lt;span class="nt"&gt;--expires-in&lt;/span&gt; 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;300&lt;/code&gt; seconds = &lt;strong&gt;5 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;I opened the generated URL in an Incognito browser window. The private object was accessible successfully.&lt;/p&gt;

&lt;p&gt;After the five-minute validity period expired, I refreshed the same URL. S3 returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AccessDenied
Request has expired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The test demonstrated the main behavior of a pre-signed URL:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Private S3 object + temporary signed URL → temporary access → URL expires → access denied&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Practical Takeaway
&lt;/h3&gt;

&lt;p&gt;A pre-signed URL is useful when an application needs to provide temporary access to a private S3 object without making the object itself public.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. AWS CLI &lt;code&gt;s3 sync&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;aws s3 sync&lt;/code&gt; to synchronize a local directory with S3 and verify that only changed files are uploaded during a subsequent sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the Test Directory
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;s3-sync-lab
&lt;span class="nb"&gt;cd &lt;/span&gt;s3-sync-lab

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File 1 - original content"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file1.txt
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File 2 - original content"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file2.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Initial Sync
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; s3://sse-kms-bucket-tejas/s3-sync-lab/
aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://sse-kms-bucket-tejas/s3-sync-lab/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first synchronization uploaded both files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modify One File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"File 1 - UPDATED content"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; file1.txt
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; s3://sse-kms-bucket-tejas/s3-sync-lab/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This time, only &lt;code&gt;file1.txt&lt;/code&gt; was uploaded.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The practical demonstrated the differential behavior of &lt;code&gt;aws s3 sync&lt;/code&gt; — unchanged content did not need to be uploaded again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical Takeaway
&lt;/h3&gt;

&lt;p&gt;This is useful for scripts, backups and deployment workflows where repeatedly uploading an entire directory would be unnecessary.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. S3 Versioning and Version Recovery
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Enable Versioning, upload the same object key multiple times, identify the generated Version IDs, retrieve an older version, and remove the current version to demonstrate recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enable Versioning
&lt;/h3&gt;

&lt;p&gt;Versioning was enabled on &lt;code&gt;sse-kms-bucket-tejas&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create Multiple Versions
&lt;/h3&gt;

&lt;p&gt;The same object key (&lt;code&gt;version-test-tejas.txt&lt;/code&gt;) was reused across three uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Version 1 - Created by Tejas"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; version-test-tejas.txt
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;version-test-tejas.txt s3://sse-kms-bucket-tejas/version-test-tejas.txt

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Version 2 - Updated by Tejas"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; version-test-tejas.txt
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;version-test-tejas.txt s3://sse-kms-bucket-tejas/version-test-tejas.txt

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Version 3 - Final update by Tejas"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; version-test-tejas.txt
aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;version-test-tejas.txt s3://sse-kms-bucket-tejas/version-test-tejas.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List Versions
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api list-object-versions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt; version-test-tejas.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The practical produced multiple retained versions for the same object key. The latest version was marked as current, while previous versions remained available.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retrieve an Older Version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api get-object &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; version-test-tejas.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &amp;lt;VERSION_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  version-1-retrieved.txt

&lt;span class="nb"&gt;cat &lt;/span&gt;version-1-retrieved.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The historical content was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Version 1 - Created by Tejas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete the Current Version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api delete-object &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; version-test-tejas.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &amp;lt;CURRENT_VERSION_ID&amp;gt;

aws s3api list-object-versions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt; version-test-tejas.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous version became the latest available version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;The version listing output opened in the terminal's &lt;code&gt;less&lt;/code&gt; viewer — pressing &lt;code&gt;q&lt;/code&gt; returns to the shell.&lt;/p&gt;

&lt;p&gt;There was also an initial retrieval mistake where &lt;code&gt;cat version-1-retrieved.txt&lt;/code&gt; failed because the file had not yet been created. After running &lt;code&gt;get-object&lt;/code&gt; successfully, the historical content could be read.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;This lab demonstrated that Versioning can preserve historical object states and allow an older version to be retrieved even after a newer version becomes current.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. S3 Static Website Hosting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Host a simple static website directly from an S3 bucket and configure a custom error page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bucket
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bucket:&lt;/strong&gt; &lt;code&gt;s3-static-website-tejas&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Region:&lt;/strong&gt; &lt;code&gt;ap-south-1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this lab, Block Public Access was disabled because the website content needed to be publicly readable through the S3 website endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Website Configuration
&lt;/h3&gt;

&lt;p&gt;Static website hosting was enabled with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Index document:&lt;/strong&gt; &lt;code&gt;cors-test.html&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error document:&lt;/strong&gt; &lt;code&gt;error.html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;The S3 region-specific website endpoint was opened in a browser. The website loaded successfully.&lt;/p&gt;

&lt;p&gt;A non-existent page was then requested to trigger the configured error document. The custom 404 page was returned successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;Both paths were verified:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Valid request → index/website content&lt;/p&gt;

&lt;p&gt;Invalid request → custom &lt;code&gt;error.html&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Practical Takeaway
&lt;/h3&gt;

&lt;p&gt;S3 website hosting is useful for simple static content, although real production architectures may place additional services such as CloudFront in front of the content.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. S3 CORS
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Demonstrate a browser cross-origin request failure and then allow the request using an S3 CORS configuration.&lt;/p&gt;

&lt;p&gt;This was one of the more useful troubleshooting labs because the browser behavior made the difference between &lt;strong&gt;S3 access&lt;/strong&gt; and &lt;strong&gt;browser CORS permission&lt;/strong&gt; very visible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Origin website bucket:&lt;/strong&gt; &lt;code&gt;s3-cors-origin-tejas&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object bucket:&lt;/strong&gt; &lt;code&gt;s3-cors-lab-tejas&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Object:&lt;/strong&gt; &lt;code&gt;cat.jpg&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The webpage was hosted from one S3 website origin and attempted to fetch the image from another S3 origin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Before CORS
&lt;/h3&gt;

&lt;p&gt;The webpage attempted a JavaScript &lt;code&gt;fetch()&lt;/code&gt; request for &lt;code&gt;cat.jpg&lt;/code&gt;. The browser blocked the response because the required &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header was not present. The browser console showed the CORS failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure CORS
&lt;/h3&gt;

&lt;p&gt;For the practical, the object bucket was configured with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedMethods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"AllowedOrigins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ExposeHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Again
&lt;/h3&gt;

&lt;p&gt;A fresh pre-signed URL was generated and the browser test was repeated. The final response showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Response type: cors
Status: 200
Access-Control-Allow-Origin: *
Blob: image/jpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The image was successfully returned as a Blob.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Troubleshooting Observation
&lt;/h3&gt;

&lt;p&gt;During the lab, an expired pre-signed URL initially produced &lt;code&gt;HTTP 403&lt;/code&gt; with an XML error response — separate from the CORS configuration itself. After generating a fresh pre-signed URL, the request returned &lt;code&gt;HTTP 200&lt;/code&gt; and included the CORS response header.&lt;/p&gt;

&lt;p&gt;Another useful observation was that &lt;code&gt;fetch()&lt;/code&gt; does not automatically reject its promise just because the HTTP status is 403. The response status should be checked explicitly, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Status:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CORS header:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Access-Control-Allow-Origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blob:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The practical demonstrated both sides of CORS:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No CORS header → browser blocks cross-origin access&lt;/p&gt;

&lt;p&gt;CORS configured → HTTP 200 + CORS header → JavaScript receives the object&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. S3 Object Lock
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Objective
&lt;/h3&gt;

&lt;p&gt;Protect an S3 object version using &lt;strong&gt;Governance Mode&lt;/strong&gt; and verify that a normal delete request is blocked while retention is active.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bucket Configuration
&lt;/h3&gt;

&lt;p&gt;A new bucket was created: &lt;code&gt;s3-object-lock-tejas&lt;/code&gt;. Versioning and Object Lock were enabled.&lt;/p&gt;

&lt;p&gt;Default retention was left disabled because retention would be applied to the specific test object version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create and Upload the Test Object
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Object Lock test created by Tejas"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; tejas-object-lock.txt

aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
s3://s3-object-lock-tejas/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Find the Version ID
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api list-object-versions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt; tejas-object-lock.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response provided the Version ID for the uploaded object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Apply Governance Retention
&lt;/h3&gt;

&lt;p&gt;The object version was configured with Governance Mode and a one-day retention period:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api put-object-retention &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;VERSION_ID&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retention&lt;/span&gt; &lt;span class="s2"&gt;"Mode=GOVERNANCE,RetainUntilDate=&amp;lt;RETENTION_DATE&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify Retention
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api get-object-retention &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;VERSION_ID&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Retention"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GOVERNANCE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"RetainUntilDate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;RETENTION_DATE&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Attempt Deletion
&lt;/h3&gt;

&lt;p&gt;A normal delete was attempted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api delete-object &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;VERSION_ID&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S3 rejected the operation with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AccessDenied
Access Denied because object protected by object lock.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;The retention worked as expected. The object version could not be normally deleted while the Object Lock retention period was active.&lt;/p&gt;

&lt;h3&gt;
  
  
  Governance vs Compliance
&lt;/h3&gt;

&lt;p&gt;The practical used &lt;strong&gt;Governance Mode&lt;/strong&gt;. The important distinction is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Governance Mode&lt;/strong&gt; provides retention protection while allowing appropriately authorized bypass operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance Mode&lt;/strong&gt; is stricter and is designed for scenarios where even privileged administrators should not be able to bypass the retention.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What These Practicals Demonstrated
&lt;/h2&gt;

&lt;p&gt;After completing the labs, the S3 concepts were no longer just console options or documentation terms.&lt;/p&gt;

&lt;p&gt;The practical sequence covered:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Practical verification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lifecycle&lt;/td&gt;
&lt;td&gt;Automatic storage-class transition rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bucket Policy&lt;/td&gt;
&lt;td&gt;IAM read + EC2 upload + HTTPS-only deny&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSE-KMS&lt;/td&gt;
&lt;td&gt;S3 encryption using customer-managed KMS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudTrail&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GenerateDataKey&lt;/code&gt; and &lt;code&gt;Decrypt&lt;/code&gt; events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-signed URL&lt;/td&gt;
&lt;td&gt;Temporary private-object access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLI Sync&lt;/td&gt;
&lt;td&gt;Differential synchronization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Versioning&lt;/td&gt;
&lt;td&gt;Historical versions and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static Website&lt;/td&gt;
&lt;td&gt;Website endpoint + custom 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CORS&lt;/td&gt;
&lt;td&gt;Browser block → CORS-enabled success&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object Lock&lt;/td&gt;
&lt;td&gt;Governance retention → delete blocked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Troubleshooting Lessons
&lt;/h2&gt;

&lt;p&gt;A few issues during the labs were particularly useful because they showed how AWS behaves in real situations.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. CloudTrail Events Were Not Immediately Obvious
&lt;/h3&gt;

&lt;p&gt;Searching by event name exposed the expected KMS operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GenerateDataKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Decrypt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Long AWS CLI Output Can Open in &lt;code&gt;less&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When a command such as &lt;code&gt;list-object-versions&lt;/code&gt; produces a long response, pressing &lt;code&gt;q&lt;/code&gt; returns to the shell.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Generate the Output File Before Reading It
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;cat version-1-retrieved.txt&lt;/code&gt; before running &lt;code&gt;get-object&lt;/code&gt; results in &lt;code&gt;No such file or directory&lt;/code&gt;. The correct sequence is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;S3 version → &lt;code&gt;get-object&lt;/code&gt; → local file created → &lt;code&gt;cat&lt;/code&gt; local file&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4. CORS and HTTP Errors Are Different Problems
&lt;/h3&gt;

&lt;p&gt;A request can fail because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the object URL expired,&lt;/li&gt;
&lt;li&gt;S3 returned 403,&lt;/li&gt;
&lt;li&gt;the object does not exist,&lt;/li&gt;
&lt;li&gt;or the browser blocks a successful cross-origin response because the CORS headers are missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Testing with a fresh pre-signed URL helped separate the authentication/expiry issue from the CORS issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Object Lock Actually Blocks Deletion
&lt;/h3&gt;

&lt;p&gt;The Object Lock test was especially clear because S3 returned an explicit &lt;code&gt;AccessDenied&lt;/code&gt; when a protected object version was deleted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The biggest value of these S3 labs was not memorizing the S3 console. It was seeing how the different controls interact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Amazon S3
                     |
       +-------------+-------------+
       |             |             |
   Security       Storage        Access
       |             |             |
  IAM / Policy   Lifecycle     Pre-signed URL
  HTTPS-only     Versioning    CORS
  SSE-KMS        Object Lock
       |
   CloudTrail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S3 becomes much easier to reason about once these features are connected to actual operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Who can access the object?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How is the object encrypted?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How long should it remain in a storage class?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What happens when the object is overwritten?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can an old version be recovered?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can the object be accessed temporarily without making it public?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can a browser access it cross-origin?&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can the object be deleted during a retention period?&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These practicals gave me hands-on verification of each of those questions rather than only theoretical answers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Commands Used in the Labs
&lt;/h2&gt;

&lt;p&gt;A compact list of the most important commands from the practicals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Pre-signed URL&lt;/span&gt;
aws s3 presign s3://sse-kms-bucket-tejas/presigned-test.txt &lt;span class="nt"&gt;--expires-in&lt;/span&gt; 300

&lt;span class="c"&gt;# Sync local directory to S3&lt;/span&gt;
aws s3 &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; s3://sse-kms-bucket-tejas/s3-sync-lab/

&lt;span class="c"&gt;# List object versions&lt;/span&gt;
aws s3api list-object-versions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt; version-test-tejas.txt

&lt;span class="c"&gt;# Retrieve a specific version&lt;/span&gt;
aws s3api get-object &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; version-test-tejas.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &amp;lt;VERSION_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  version-1-retrieved.txt

&lt;span class="c"&gt;# Delete a specific version&lt;/span&gt;
aws s3api delete-object &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; sse-kms-bucket-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; version-test-tejas.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &amp;lt;VERSION_ID&amp;gt;

&lt;span class="c"&gt;# List Object Lock versions&lt;/span&gt;
aws s3api list-object-versions &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--prefix&lt;/span&gt; tejas-object-lock.txt

&lt;span class="c"&gt;# Apply Object Lock retention&lt;/span&gt;
aws s3api put-object-retention &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;VERSION_ID&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--retention&lt;/span&gt; &lt;span class="s2"&gt;"Mode=GOVERNANCE,RetainUntilDate=&amp;lt;RETENTION_DATE&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Verify Object Lock retention&lt;/span&gt;
aws s3api get-object-retention &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;VERSION_ID&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Attempt deletion of a protected version&lt;/span&gt;
aws s3api delete-object &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; s3-object-lock-tejas &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; tejas-object-lock.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version-id&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;VERSION_ID&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This S3 practical series covered the service from basic bucket configuration through security, encryption, access control, synchronization, version recovery, static hosting, browser CORS behavior and immutable-object protection.&lt;/p&gt;

&lt;p&gt;The most useful part was not simply getting a &lt;strong&gt;"Success"&lt;/strong&gt; message from AWS. It was deliberately testing the expected behavior — including expired URLs, historical versions, browser CORS failures and an Object Lock deletion failure.&lt;/p&gt;

&lt;p&gt;That is the kind of hands-on understanding I want to build as I continue learning AWS, Cloud and DevOps.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>AWS EC2 Hands-On Lab</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Thu, 20 Aug 2026 17:08:22 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-ec2-hands-on-practicals-1jh</link>
      <guid>https://dev.to/tejas_shinkar/aws-ec2-hands-on-practicals-1jh</guid>
      <description>&lt;h3&gt;
  
  
  EC2, IAM Roles, EBS, AMIs, Snapshots, CLI and Boto3
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Part of my AWS hands-on practical journey. This post documents the EC2 labs and practical tasks I completed, including what I built, how I verified it, and the problems I encountered along the way. For the concepts behind these EC2 practicals, see &lt;a href="https://dev.to/tejas_shinkar/aws-ec2-essentials-everything-you-need-to-know-with-real-life-analogies-45c8"&gt;EC2 Session&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Practicals Covered
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Practical&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;EC2 Web Server with User Data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;EC2 + S3 Access via IAM Role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;EC2 Instance &amp;amp; EBS Volume Resize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Custom AMI Creation &amp;amp; Validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;EBS Snapshot Cross-Region Disaster Recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Elastic IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;EC2 Operations using AWS CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;EC2 Cost Estimation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;EC2 Automation with Boto3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Lab 1 — EC2 Web Server with User Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Did
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Launched an Amazon Linux 2023 EC2 instance using &lt;code&gt;t2.micro&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Created a security group:

&lt;ul&gt;
&lt;li&gt;HTTP (80) → &lt;code&gt;0.0.0.0/0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;SSH (22) → My IP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added a User Data script during launch.&lt;/li&gt;
&lt;li&gt;The script installed Apache (&lt;code&gt;httpd&lt;/code&gt;), started and enabled the service, retrieved the Instance ID and Availability Zone from EC2 metadata, and created a custom &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Opened the instance's public IP in a browser.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;The instance automatically configured itself during first boot and served a custom webpage containing its Instance ID and Availability Zone.&lt;/p&gt;

&lt;p&gt;Apache was verified with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service was active, enabled, and listening on port 80.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Failed
&lt;/h3&gt;

&lt;p&gt;The first instance did not work because of setup issues, so it was recreated cleanly. SSH key-access problems were also encountered on Windows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting Notes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;User Data was added before launching because it normally runs during first boot.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.pem&lt;/code&gt; file needed to be locally accessible and readable by SSH.&lt;/li&gt;
&lt;li&gt;SSH keys stored in cloud-synced folders such as OneDrive can cause file-access or permission issues.&lt;/li&gt;
&lt;li&gt;The correct Security Group needed to be attached.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The troubleshooting order that worked was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Security Group → EC2 status → SSH → Apache → User Data&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Lab 2 — EC2 + S3 Access via IAM Role
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Did
&lt;/h3&gt;

&lt;p&gt;The goal was to allow EC2 to access S3 without storing AWS access keys on the instance.&lt;/p&gt;

&lt;p&gt;Created:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;S3 bucket: &lt;code&gt;lab-bucket-tejasshinkar&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test object: &lt;code&gt;test.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;IAM Role: &lt;code&gt;EC2-S3-ReadOnly-Role&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Trusted entity: AWS Service → EC2&lt;/li&gt;
&lt;li&gt;Permission: &lt;code&gt;AmazonS3ReadOnlyAccess&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The role was attached to the EC2 instance through its IAM Instance Profile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Listed the S3 bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Downloaded the test object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;s3://lab-bucket-tejasshinkar/test.txt &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both operations succeeded.&lt;/p&gt;

&lt;p&gt;Then write access was tested:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;cp &lt;/span&gt;test.txt s3://lab-bucket-tejasshinkar/write-test.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AccessDenied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error confirmed that &lt;code&gt;s3:PutObject&lt;/code&gt; was not allowed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What This Proved
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2
 ↓
Instance Profile
 ↓
IAM Role
 ↓
AmazonS3ReadOnlyAccess
 ↓
S3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instance could list and read S3 objects, but could not write to S3.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;aws configure&lt;/code&gt; was used and no hardcoded AWS access keys were stored on the instance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lab 3 — EC2 Instance &amp;amp; EBS Volume Resize
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Part A — EC2 Instance Resize
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stopped the running &lt;code&gt;t3.micro&lt;/code&gt; instance.&lt;/li&gt;
&lt;li&gt;Changed the instance type to &lt;code&gt;t3.small&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Started the instance again.&lt;/li&gt;
&lt;li&gt;SSH'd into the instance and verified it was operational.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The instance type had to be changed while the instance was stopped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part B — EBS Volume Resize
&lt;/h3&gt;

&lt;p&gt;The root EBS volume was increased:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8 GiB → 16 GiB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was done through:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;EC2 → Volumes → Modify Volume&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AWS Console showed 16 GiB, but the operating system initially reported only about 8 GiB.&lt;/p&gt;

&lt;p&gt;Checked the disk layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsblk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nvme0n1 16G
└─nvme0n1p1 8G /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EBS volume had been expanded, but the partition/filesystem had not yet used the additional space.&lt;/p&gt;

&lt;p&gt;Because Amazon Linux 2023 was using XFS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;xfs_growfs /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;confirmed approximately 16 GB available to the root filesystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Troubleshooting
&lt;/h3&gt;

&lt;p&gt;Before running disk commands, the actual device name and filesystem type were checked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EBS Volume
     ↓
   16 GB
     ↓
Partition / Filesystem
     ↓
16 GB usable by OS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EBS volume expansion and filesystem expansion are separate operations.&lt;/p&gt;

&lt;p&gt;For filesystem expansion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;XFS   → xfs_growfs
ext4  → resize2fs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EBS volume expansion can be performed while the instance is running, unlike changing the EC2 instance type.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lab 4 — Custom AMI Creation &amp;amp; Validation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Did
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Launched an Amazon Linux 2023 EC2 instance.&lt;/li&gt;
&lt;li&gt;Installed and configured Nginx and Git.&lt;/li&gt;
&lt;li&gt;Created a custom webpage:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;h1&amp;gt;Custom AMI - &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/h1&amp;gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/share/nginx/html/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Verified Nginx locally:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Created &lt;code&gt;custom-nginx-ami&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Waited for the AMI to become Available.&lt;/li&gt;
&lt;li&gt;Launched a new EC2 instance using the custom AMI.&lt;/li&gt;
&lt;li&gt;SSH'd into the new instance.&lt;/li&gt;
&lt;li&gt;Verified Nginx:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status nginx
curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Opened the new instance's public IP and confirmed the webpage was served.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I Proved
&lt;/h3&gt;

&lt;p&gt;The new EC2 inherited:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nginx installation&lt;/li&gt;
&lt;li&gt;Nginx configuration/service state&lt;/li&gt;
&lt;li&gt;Custom &lt;code&gt;index.html&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No User Data script was required on the new instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Configured EC2
      ↓
   Create AMI
      ↓
Reusable EC2 Template
      ↓
    New EC2
      ↓
Same configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mistakes &amp;amp; Troubleshooting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Browser SSH failed&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Security Group allowed SSH only from my IP, so browser-based EC2 Instance Connect did not work. I used the SSH client from PowerShell instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Tried SSH as root&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Amazon Linux required the default user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ec2-user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Website initially did not load&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The problem was using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;public-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://&amp;lt;public-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx was serving HTTP on port 80, not HTTPS on port 443.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lab 5 — EBS Snapshot Cross-Region Disaster Recovery
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Did
&lt;/h3&gt;

&lt;p&gt;The objective was to create an EBS snapshot in Mumbai, copy it to Virginia, restore it as a new EBS volume, and verify the original data.&lt;/p&gt;

&lt;p&gt;Source Region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ap-south-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Destination Region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Created test data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Mumbai DR test - original data"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test-dr.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Created an EBS Snapshot.&lt;/li&gt;
&lt;li&gt;Copied the snapshot from Mumbai to Virginia.&lt;/li&gt;
&lt;li&gt;Created a new EBS volume from the copied snapshot in &lt;code&gt;us-east-1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Launched an EC2 instance in the destination Region.&lt;/li&gt;
&lt;li&gt;Attached the restored EBS volume as a secondary volume.&lt;/li&gt;
&lt;li&gt;Mounted the restored volume.&lt;/li&gt;
&lt;li&gt;Verified the original file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /data
&lt;span class="nb"&gt;cat&lt;/span&gt; /data/test-dr.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original data was successfully restored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mumbai (ap-south-1)
        │
       EC2
        │
   EBS Volume
        │
        ▼
     Snapshot
        │
        │ Cross-Region Copy
        ▼
Virginia (us-east-1)
        │
Copied Snapshot
        │
        ▼
 New EBS Volume
        │
        ▼
       EC2
        │
        ▼
 Original Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mistakes &amp;amp; Troubleshooting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Initially created a snapshot before adding the test file, so another snapshot had to be created with the required data.&lt;/li&gt;
&lt;li&gt;Multiple snapshots and volumes made identification confusing, so Snapshot IDs needed to be checked carefully.&lt;/li&gt;
&lt;li&gt;The destination EC2 already had its own root EBS volume; the restored volume was attached separately as a secondary disk.&lt;/li&gt;
&lt;li&gt;When attaching an EBS volume, the EC2 instance and volume must be in the same Availability Zone.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Additional Practical Tasks
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Practical Task 1 — Elastic IP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What I Did
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Launched an EC2 instance and recorded its initial dynamic public IP.&lt;/li&gt;
&lt;li&gt;Stopped and started the instance.&lt;/li&gt;
&lt;li&gt;Confirmed that the public IP changed.&lt;/li&gt;
&lt;li&gt;Allocated an Elastic IP from &lt;strong&gt;EC2 → Elastic IPs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Associated the Elastic IP with the instance.&lt;/li&gt;
&lt;li&gt;Stopped and started the instance again.&lt;/li&gt;
&lt;li&gt;Confirmed that the Elastic IP remained unchanged.&lt;/li&gt;
&lt;li&gt;Released the Elastic IP after completing the practical.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before Elastic IP
EC2 → Stop → Start → Different Public IP ❌

After Elastic IP
EC2 → Stop → Start → Same Public IP ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Practical Task 2 — EC2 Operations Using AWS CLI
&lt;/h2&gt;

&lt;p&gt;The objective was to manage EC2 using only AWS CLI rather than the AWS Console.&lt;/p&gt;

&lt;p&gt;AWS CloudShell was used because it provides a pre-configured AWS CLI environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find Running Instances
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="s2"&gt;"Name=instance-state-name,Values=running"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s2"&gt;"Reservations[].Instances[]. [InstanceId,InstanceType,State.Name,PublicIpAddress]"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; table
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returned the running instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instance ID: i-0548517fe09f6eeeb
Type: t3.micro
State: running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stop the Instance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 stop-instances &lt;span class="nt"&gt;--instance-ids&lt;/span&gt; i-0548517fe09f6eeeb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instance state was then verified using &lt;code&gt;describe-instances&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The practical demonstrated the flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CloudShell
   ↓
AWS CLI
   ↓
EC2 API
   ↓
Describe / Stop / Start / Snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Practical Task 3 — EC2 Cost Estimation
&lt;/h2&gt;

&lt;p&gt;Used AWS Pricing Calculator to estimate the monthly cost of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 × &lt;code&gt;t3.micro&lt;/code&gt; EC2&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ap-south-1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Running 24/7&lt;/li&gt;
&lt;li&gt;20 GB gp3 EBS&lt;/li&gt;
&lt;li&gt;100 GB data transfer out&lt;/li&gt;
&lt;li&gt;Comparison with a 1-year Reserved Instance, No Upfront&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The configuration was compared as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 t3.micro
+ 20 GB gp3 EBS
+ 100 GB Data Transfer Out

        ↓

On-Demand Monthly Cost
        VS
1-Year Reserved Instance Monthly Cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Reserved Instance estimate was cheaper for the continuous workload.&lt;/p&gt;

&lt;p&gt;The EBS and data-transfer costs were considered separately rather than assuming the compute discount applies to the entire AWS bill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Task 4 — EC2 Automation with Python + Boto3
&lt;/h2&gt;

&lt;p&gt;The task was to list EC2 instances in &lt;code&gt;ap-south-1&lt;/code&gt;, display their Instance ID, Instance Type and State, and automatically stop running instances tagged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Environment: dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ap-south-1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe_instances&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reservations&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reservation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Instances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;instance_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InstanceId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;instance_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;InstanceType&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;State&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance_type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;State: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stopping dev instance: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;instance_id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automation Logic
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Boto3
  ↓
Connect to EC2 (ap-south-1)
  ↓
List instances
  ↓
Print ID + Type + State
  ↓
Check Environment tag
  ↓
Environment = dev?
  ↓
Running?
  ↓
STOP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important condition was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;environment&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the script does not stop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production instances&lt;/li&gt;
&lt;li&gt;Stopped dev instances&lt;/li&gt;
&lt;li&gt;Testing instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only &lt;strong&gt;running instances tagged &lt;code&gt;Environment=dev&lt;/code&gt;&lt;/strong&gt; are stopped.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Summary
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;EC2 + User Data:&lt;/strong&gt; Automated first-boot configuration and Apache setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2 + IAM Role:&lt;/strong&gt; Accessed S3 without storing AWS credentials on the instance and verified least-privilege read-only access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EC2 + EBS:&lt;/strong&gt; Resized compute and storage, including expanding the filesystem after increasing the EBS volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom AMI:&lt;/strong&gt; Created a reusable configured EC2 template and launched another instance from it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EBS Snapshot + Cross-Region Copy:&lt;/strong&gt; Restored data in another AWS Region as a basic disaster-recovery workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Elastic IP:&lt;/strong&gt; Verified the difference between dynamic public IPs and a persistent Elastic IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS CLI:&lt;/strong&gt; Performed EC2 operations from CloudShell without using the Console.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boto3:&lt;/strong&gt; Used Python to inspect EC2 instances and automate stopping only running development instances.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;AWS Hands-On Practicals — EC2 | Cloud + DevOps learning journey&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>AWS Highly Available Web Application — ALB, Auto Scaling &amp; Self-Healing</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Thu, 20 Aug 2026 11:50:56 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/aws-highly-available-web-application-alb-auto-scaling-self-healing-2je7</link>
      <guid>https://dev.to/tejas_shinkar/aws-highly-available-web-application-alb-auto-scaling-self-healing-2je7</guid>
      <description>&lt;h1&gt;
  
  
  AWS Highly Available Web Application
&lt;/h1&gt;

&lt;h3&gt;
  
  
  ALB + EC2 + Auto Scaling + Multi-AZ + Self-Healing
&lt;/h3&gt;

&lt;p&gt;A hands-on AWS project that deploys an Apache-based web application across multiple Availability Zones and demonstrates load balancing, health checks, dynamic scaling, and automatic instance replacement.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Project Overview
&lt;/h2&gt;

&lt;p&gt;The application runs on Amazon EC2 instances managed by an Auto Scaling Group.&lt;/p&gt;

&lt;p&gt;An internet-facing Application Load Balancer receives HTTP traffic and forwards requests to healthy EC2 instances through a Target Group.&lt;/p&gt;

&lt;p&gt;The EC2 instances are created from a Launch Template, which uses User Data to automatically install and configure Apache and generate a webpage containing instance-specific information.&lt;/p&gt;

&lt;p&gt;The environment was tested under normal operation, scale-in, and deliberate instance termination to verify automatic replacement and recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Internet
                            │
                         HTTP :80
                            │
                            ▼
                  ┌───────────────────┐
                  │ Application Load  │
                  │ Balancer (ALB)    │
                  │   web-app-alb     │
                  └─────────┬─────────┘
                            │
                            ▼
                  ┌───────────────────┐
                  │   Target Group    │
                  │   web-server-tg   │
                  │   HTTP Health     │
                  │      Checks       │
                  └─────────┬─────────┘
                            │
                  ┌─────────┴─────────┐
                  ▼                   ▼
          ┌─────────────┐     ┌─────────────┐
          │    EC2      │     │    EC2      │
          │ ap-south-1a │     │ ap-south-1b │
          └─────────────┘     └─────────────┘
                  ▲                   ▲
                  └─────────┬─────────┘
                            │
                  ┌───────────────────┐
                  │ Auto Scaling Group │
                  │    web-app-asg     │
                  └─────────┬─────────┘
                            │
                  ┌───────────────────┐
                  │  Launch Template  │
                  │   web-server-lt   │
                  └───────────────────┘
                            │
                         User Data
                            │
                      Apache + HTML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. AWS Components
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Configuration / Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Amazon EC2&lt;/td&gt;
&lt;td&gt;Runs Apache and the web application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch Template&lt;/td&gt;
&lt;td&gt;Reusable EC2 configuration blueprint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User Data&lt;/td&gt;
&lt;td&gt;Installs/configures Apache and generates the application page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Groups&lt;/td&gt;
&lt;td&gt;Controls ALB and EC2 traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target Group&lt;/td&gt;
&lt;td&gt;Maintains EC2 targets and performs HTTP health checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;Public HTTP entry point and traffic distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto Scaling Group&lt;/td&gt;
&lt;td&gt;Maintains and replaces EC2 capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch&lt;/td&gt;
&lt;td&gt;Supplies CPU utilization metrics for Target Tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability Zones&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ap-south-1a&lt;/code&gt; and &lt;code&gt;ap-south-1b&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  3. Network &amp;amp; Security Configuration
&lt;/h2&gt;

&lt;p&gt;Two Security Groups were used.&lt;/p&gt;

&lt;h3&gt;
  
  
  SG-ALB
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inbound:
HTTP :80 → 0.0.0.0/0

Outbound:
All traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SG-EC2
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inbound:
HTTP :80 → SG-ALB

Outbound:
All traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Application traffic therefore follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
    │
    │ HTTP :80
    ▼
 SG-ALB
    │
    │ HTTP :80
    ▼
 SG-EC2
    │
    ▼
   EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps HTTP access to the EC2 instances restricted to traffic originating from the ALB.&lt;/p&gt;

&lt;p&gt;For instance-level testing, SSH was temporarily allowed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSH :22
Source: &amp;lt;public-IP&amp;gt;/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SSH rule was removed during cleanup.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Launch Template &amp;amp; User Data
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Launch Template
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web-server-lt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMI&lt;/td&gt;
&lt;td&gt;Amazon Linux 2023&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;64-bit x86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instance type&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.micro&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key pair&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lab-ec2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SG-EC2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The initial Launch Template version had an AMI configuration issue. Version 2 was created with the correct Amazon Linux 2023 AMI and made the default version used by the ASG.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Data
&lt;/h3&gt;

&lt;p&gt;User Data performs the initial EC2 configuration automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch EC2
    │
    ├── Update packages
    ├── Install httpd
    ├── Start httpd
    ├── Enable httpd
    ├── Retrieve instance metadata
    │      ├── Instance ID
    │      ├── Availability Zone
    │      └── Region
    │
    └── Generate application HTML
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application displays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS Highly Available Web Application

Built by Tejas Shinkar

ELB + Auto Scaling + EC2 + CloudWatch

Instance ID: ...
Availability Zone: ...
Region: ap-south-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The instance-specific information makes backend changes visible when requests are served through the ALB.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Target Group &amp;amp; Health Checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Target Group
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web-server-tg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target type&lt;/td&gt;
&lt;td&gt;Instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health check protocol&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;Traffic port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interval&lt;/td&gt;
&lt;td&gt;30 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeout&lt;/td&gt;
&lt;td&gt;5 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Healthy threshold&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unhealthy threshold&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Success code&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Target Group was initially created with &lt;strong&gt;0 targets intentionally&lt;/strong&gt;. Targets were not manually registered because the Auto Scaling Group was configured to register its instances automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Health check flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Target Group
      │
      │ HTTP GET /
      ▼
   EC2 :80
      │
      │ HTTP 200
      ▼
   Healthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An EC2 instance being in a &lt;code&gt;running&lt;/code&gt; state does not by itself make it a healthy application target. The Target Group checks the HTTP application response.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Application Load Balancer
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ALB Configuration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web-app-alb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheme&lt;/td&gt;
&lt;td&gt;Internet-facing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IP address type&lt;/td&gt;
&lt;td&gt;IPv4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC&lt;/td&gt;
&lt;td&gt;Default VPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability Zones&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ap-south-1a&lt;/code&gt;, &lt;code&gt;ap-south-1b&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SG-ALB&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Listener
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Protocol: HTTP
Port: 80
Default action: Forward to web-server-tg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Request flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   │
   ▼
ALB :80
   │
   ▼
web-server-tg
   │
   ▼
Healthy EC2 :80
   │
   ▼
Apache
   │
   ▼
Application response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With two healthy instances, repeated requests to the ALB endpoint displayed different Instance IDs and Availability Zones, confirming that requests were reaching different healthy backends.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Auto Scaling Group
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web-app-asg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch Template&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web-server-lt&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desired capacity&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimum capacity&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maximum capacity&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Availability Zones&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ap-south-1a&lt;/code&gt;, &lt;code&gt;ap-south-1b&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target Group&lt;/td&gt;
&lt;td&gt;&lt;code&gt;web-server-tg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health check grace period&lt;/td&gt;
&lt;td&gt;300 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ASG launches instances using the Launch Template and automatically registers them with the Target Group.&lt;/p&gt;

&lt;p&gt;The 300-second grace period allows a new instance to boot, execute User Data, install/start Apache, and become ready for application health checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Auto Scaling Group
       │
       ├──────────────┐
       ▼              ▼
     EC2 #1         EC2 #2
   ap-south-1a    ap-south-1b
       │              │
       └──────┬───────┘
              ▼
        Target Group
              │
              ▼
             ALB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Target Tracking &amp;amp; Scale-In
&lt;/h2&gt;

&lt;p&gt;A Target Tracking policy was configured after the initial deployment was verified.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Policy&lt;/td&gt;
&lt;td&gt;Target Tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metric&lt;/td&gt;
&lt;td&gt;Average CPU Utilization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Instance warmup&lt;/td&gt;
&lt;td&gt;300 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale in&lt;/td&gt;
&lt;td&gt;Enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale out&lt;/td&gt;
&lt;td&gt;Enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EC2 CPU Utilization
        │
        ▼
    CloudWatch
        │
        ▼
Target Tracking
        │
        ▼
Auto Scaling Group
      /   \
     ▼     ▼
Scale Out  Scale In
   │          │
 Add EC2    Remove EC2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Observed scale-in
&lt;/h3&gt;

&lt;p&gt;The workload remained low after the policy was enabled, so the ASG scaled in.&lt;/p&gt;

&lt;p&gt;The instance being removed entered the Target Group's &lt;strong&gt;Draining&lt;/strong&gt; state before termination.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Healthy EC2
     │
     ▼
ASG decides to scale in
     │
     ▼
Target Group → Draining
     │
     ▼
Instance terminates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment eventually reached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Desired capacity: 1
Healthy targets: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, repeated ALB refreshes naturally showed the same Instance ID because only one healthy backend remained.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Application Verification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  EC2 verification
&lt;/h3&gt;

&lt;p&gt;SSH was temporarily enabled for instance-level verification.&lt;/p&gt;

&lt;p&gt;Apache was checked with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status httpd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;active (running)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application was then verified locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command returned the custom HTML page.&lt;/p&gt;

&lt;h3&gt;
  
  
  ALB verification
&lt;/h3&gt;

&lt;p&gt;The application was accessed through the ALB DNS endpoint.&lt;/p&gt;

&lt;p&gt;The page displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instance ID
Availability Zone
Region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This verified the complete path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  ↓
ALB
  ↓
Target Group
  ↓
EC2
  ↓
Apache
  ↓
Application
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Load-balancing verification
&lt;/h3&gt;

&lt;p&gt;When two backend instances were healthy, repeated ALB requests displayed different Instance IDs/AZs.&lt;/p&gt;

&lt;p&gt;This provided a simple visual verification that the ALB was distributing traffic across healthy targets.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Failure Simulation &amp;amp; Self-Healing
&lt;/h2&gt;

&lt;p&gt;An active EC2 instance managed by the ASG was deliberately terminated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recovery flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active EC2
    │
    ▼
Manual termination
    │
    ▼
ASG detects capacity loss
    │
    ▼
Replacement EC2 launched
    │
    ▼
Launch Template applied
    │
    ▼
User Data executes
    │
    ▼
Apache configured
    │
    ▼
Target Group registration
    │
    ▼
HTTP health check
    │
    ▼
Healthy
    │
    ▼
ALB can route traffic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The replacement instance was launched automatically using the Launch Template.&lt;/p&gt;

&lt;p&gt;Because User Data was part of the launch configuration, the replacement instance automatically received the Apache configuration and application page.&lt;/p&gt;

&lt;p&gt;The replacement was then automatically registered with &lt;code&gt;web-server-tg&lt;/code&gt; and became &lt;strong&gt;Healthy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This verified the self-healing behavior of the architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Launch Template AMI configuration
&lt;/h3&gt;

&lt;p&gt;The initial Launch Template version did not contain the expected AMI configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Created Version 2 with Amazon Linux 2023 and made it the default version.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSH connectivity
&lt;/h3&gt;

&lt;p&gt;The initial SSH connection failed because port 22 was not allowed by &lt;code&gt;SG-EC2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Temporarily added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSH :22
Source: &amp;lt;public-IP&amp;gt;/32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SSH then worked and allowed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status httpd
curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The temporary SSH rule was removed during cleanup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Same Instance ID after refresh
&lt;/h3&gt;

&lt;p&gt;After Target Tracking was enabled, low workload caused the ASG to scale from 2 instances down to 1.&lt;/p&gt;

&lt;p&gt;Therefore, subsequent ALB refreshes showed the same Instance ID.&lt;/p&gt;

&lt;p&gt;The correct troubleshooting path was to check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ASG capacity
      +
Target Group health
      +
Registered targets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before assuming the ALB was not distributing traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Production Considerations
&lt;/h2&gt;

&lt;p&gt;This project intentionally used a simplified setup for demonstration.&lt;/p&gt;

&lt;p&gt;For a production deployment, the architecture would typically be extended with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTPS/TLS on the ALB instead of HTTP.&lt;/li&gt;
&lt;li&gt;A deliberately designed VPC and subnet architecture instead of the Default VPC.&lt;/li&gt;
&lt;li&gt;IAM roles for EC2 rather than relying on credentials.&lt;/li&gt;
&lt;li&gt;CloudWatch alarms and operational monitoring.&lt;/li&gt;
&lt;li&gt;More deliberate access controls and administrative access patterns.&lt;/li&gt;
&lt;li&gt;A real application instead of the demonstration HTML page.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  13. Cleanup
&lt;/h2&gt;

&lt;p&gt;After testing, all project-specific resources were removed.&lt;/p&gt;

&lt;p&gt;The cleanup sequence was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Auto Scaling Group
        ↓
2. EC2 instances
        ↓
3. Application Load Balancer
        ↓
4. Target Group
        ↓
5. Launch Template
        ↓
6. Security Groups
        ↓
7. Final resource verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final project resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web-app-asg  → Deleted
EC2          → Terminated
web-app-alb  → Deleted
web-server-tg → Deleted
web-server-lt → Deleted
SG-ALB       → Deleted
SG-EC2       → Deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No project-specific running resources were left behind after cleanup.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Project Result
&lt;/h2&gt;

&lt;p&gt;The completed project demonstrated the following end-to-end behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ┌─────────────────────┐
                         │      Internet       │
                         └──────────┬──────────┘
                                    │
                                    ▼
                         ┌─────────────────────┐
                         │        ALB          │
                         └──────────┬──────────┘
                                    │
                                    ▼
                         ┌─────────────────────┐
                         │    Target Group     │
                         │   Health Checks     │
                         └──────────┬──────────┘
                                    │
                         ┌──────────┴──────────┐
                         ▼                     ▼
                    ┌──────────┐          ┌──────────┐
                    │   EC2    │          │   EC2    │
                    │   AZ-A   │          │   AZ-B   │
                    └────┬─────┘          └────┬─────┘
                         ▲                     ▲
                         └──────────┬──────────┘
                                    │
                         ┌─────────────────────┐
                         │    Auto Scaling     │
                         │       Group         │
                         └──────────┬──────────┘
                                    │
                         ┌─────────────────────┐
                         │  Launch Template    │
                         │  + User Data        │
                         └─────────────────────┘

                  CloudWatch → Target Tracking
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verified
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Multi-AZ EC2 deployment.&lt;/li&gt;
&lt;li&gt;ALB-based application access.&lt;/li&gt;
&lt;li&gt;Target Group health checks.&lt;/li&gt;
&lt;li&gt;Automatic target registration through ASG.&lt;/li&gt;
&lt;li&gt;User Data-based server configuration.&lt;/li&gt;
&lt;li&gt;CPU Target Tracking.&lt;/li&gt;
&lt;li&gt;Scale-in and target draining.&lt;/li&gt;
&lt;li&gt;Automatic replacement after EC2 termination.&lt;/li&gt;
&lt;li&gt;Replacement target becoming healthy.&lt;/li&gt;
&lt;li&gt;ALB routing to the recovered backend.&lt;/li&gt;
&lt;li&gt;Complete resource cleanup.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Github repository: &lt;a href="https://github.com/tejasshinkar/aws-highly-available-web-app" rel="noopener noreferrer"&gt;https://github.com/tejasshinkar/aws-highly-available-web-app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AWS Cloud + DevOps — Project: Highly Available Web Application&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>cloud</category>
      <category>devops</category>
      <category>projects</category>
    </item>
    <item>
      <title>Network Protocols — ARP, FTP, SMTP, HTTP, HTTPS, DNS and DHCP</title>
      <dc:creator>Tejas Shinkar</dc:creator>
      <pubDate>Tue, 18 Aug 2026 03:05:10 +0000</pubDate>
      <link>https://dev.to/tejas_shinkar/network-protocols-arp-ftp-smtp-http-https-dns-and-dhcp-2ei0</link>
      <guid>https://dev.to/tejas_shinkar/network-protocols-arp-ftp-smtp-http-https-dns-and-dhcp-2ei0</guid>
      <description>&lt;h1&gt;
  
  
  🌐 Network Protocols
&lt;/h1&gt;

&lt;h3&gt;
  
  
  ARP, FTP, SMTP, HTTP, HTTPS, DNS and DHCP
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Networking Fundamentals for Cloud &amp;amp; DevOps — Part 6 of 6&lt;/p&gt;

&lt;p&gt;We have already seen how hosts, switches, routers, MAC addresses and IP addresses work together. Now we move one level higher and look at the &lt;strong&gt;protocols&lt;/strong&gt; that define how different types of communication happen.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  1. Protocol
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;protocol is a set of rules/messages that devices follow to communicate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of a protocol as a common language with defined rules.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If two devices need to resolve an IP address to a MAC address → &lt;strong&gt;ARP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If a client needs to transfer a file → &lt;strong&gt;FTP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If a client needs to send an email → &lt;strong&gt;SMTP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If a browser needs web communication → &lt;strong&gt;HTTP/HTTPS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If a host needs to convert a domain name into an IP address → &lt;strong&gt;DNS&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If a host needs its network configuration automatically → &lt;strong&gt;DHCP&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A quick overview:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Full Form&lt;/th&gt;
&lt;th&gt;Main Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ARP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Address Resolution Protocol&lt;/td&gt;
&lt;td&gt;IP → MAC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;File Transfer Protocol&lt;/td&gt;
&lt;td&gt;Transfer files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SMTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple Mail Transfer Protocol&lt;/td&gt;
&lt;td&gt;Send email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HyperText Transfer Protocol&lt;/td&gt;
&lt;td&gt;Web communication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP Secure&lt;/td&gt;
&lt;td&gt;Secure HTTP using TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DNS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Domain Name System&lt;/td&gt;
&lt;td&gt;Domain name → IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DHCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Dynamic Host Configuration Protocol&lt;/td&gt;
&lt;td&gt;Automatically gives network configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important thing is not just memorizing the names.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Each protocol solves a different communication problem.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. ARP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;ARP = Address Resolution Protocol.&lt;/strong&gt; ARP solves this problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I know the IP address, but what is the MAC address?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Suppose a host knows &lt;code&gt;9.1.1.22&lt;/code&gt; but needs the corresponding MAC address. It broadcasts an ARP Request:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Host &lt;code&gt;9.1.1.11&lt;/code&gt;: "Who has &lt;code&gt;9.1.1.22&lt;/code&gt;?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The host with that IP responds:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Host &lt;code&gt;9.1.1.22&lt;/code&gt;: "My MAC is &lt;code&gt;b3b3&lt;/code&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the mapping becomes &lt;code&gt;9.1.1.22 → b3b3&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ARP = IP → MAC mapping&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This connects directly to what we learned earlier:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IP address → ARP → MAC address → Ethernet frame&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;ARP is mainly concerned with finding the &lt;strong&gt;Layer 2 address needed for communication on the local network&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. FTP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;FTP = File Transfer Protocol.&lt;/strong&gt; FTP is used for &lt;strong&gt;file transfer&lt;/strong&gt; — a client sends a request to the FTP server for a file, e.g. &lt;code&gt;file.pdf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client ── RETR file.pdf ──→ FTP Server
Client ←──── file.pdf ───── FTP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;RETR&lt;/code&gt; essentially means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Retrieve/download this file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FTP → File transfer&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  4. SMTP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SMTP = Simple Mail Transfer Protocol.&lt;/strong&gt; SMTP is used for &lt;strong&gt;sending email&lt;/strong&gt;. Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client ── HELLO ───────→ SMTP Server
Client ── email ───────→ SMTP Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protocol defines how the email-sending communication takes place between the client and mail server, and between mail servers.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;SMTP = sending mail&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5. HTTP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTTP = HyperText Transfer Protocol.&lt;/strong&gt; HTTP is used for &lt;strong&gt;web communication&lt;/strong&gt;. A browser/client can request a resource from a web server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client ── GET /index.html ──→ Web Server
Client ←────── 200 OK ─────── Web Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client asks for a resource, and the web server responds. This is the basic protocol underneath the request/response model used by the web.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP → Web communication&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  6. HTTPS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTTPS = HTTP + TLS security.&lt;/strong&gt; HTTPS is HTTP communication protected using &lt;strong&gt;TLS&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;HTTP + TLS security → &lt;strong&gt;HTTPS&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TLS provides security mechanisms such as encryption and authentication for the communication. Conceptually, the connection between client and server is encrypted end-to-end (&lt;code&gt;Client ═══ encrypted HTTPS connection ═══→ Web Server&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;HTTP&lt;/strong&gt; = Web communication. &lt;strong&gt;HTTPS&lt;/strong&gt; = Secure web communication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;HTTPS is what allows a browser to communicate with a web server while protecting the connection against unauthorized observation or modification.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. DNS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DNS = Domain Name System.&lt;/strong&gt; DNS solves a different problem from ARP.&lt;/p&gt;

&lt;p&gt;ARP asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I know the IP. What is the MAC?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DNS asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"I know the website/domain name. What is its IP?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Suppose you want to access &lt;code&gt;site.com&lt;/code&gt;. Your computer needs the server's IP address, so it asks a DNS server:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What is the IP address of &lt;code&gt;site.com&lt;/code&gt;?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The DNS server responds: &lt;code&gt;site.com → 160.8.23.154&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DNS = Domain name → IP&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key distinction is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ARP:&lt;/strong&gt; IP → MAC. &lt;strong&gt;DNS:&lt;/strong&gt; Name → IP.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  8. Four Things a Host Needs for Internet Connectivity
&lt;/h2&gt;

&lt;p&gt;A host generally needs four important pieces of network configuration:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IP Address&lt;/li&gt;
&lt;li&gt;Subnet Mask&lt;/li&gt;
&lt;li&gt;Default Gateway&lt;/li&gt;
&lt;li&gt;DNS Server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP:&lt;/strong&gt; &lt;code&gt;9.1.1.11&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mask:&lt;/strong&gt; &lt;code&gt;255.255.255.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway:&lt;/strong&gt; &lt;code&gt;9.1.1.1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS:&lt;/strong&gt; &lt;code&gt;8.8.8.8&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one has a different job.&lt;/p&gt;

&lt;h3&gt;
  
  
  IP Address
&lt;/h3&gt;

&lt;p&gt;Identifies the host on the network.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IP → Which host am I?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Subnet Mask
&lt;/h3&gt;

&lt;p&gt;Determines which addresses belong to the local network.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Subnet Mask → Which network am I in?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Default Gateway
&lt;/h3&gt;

&lt;p&gt;Provides the router used to reach other networks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Default Gateway → How do I reach other networks?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  DNS
&lt;/h3&gt;

&lt;p&gt;Converts domain names into IP addresses.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DNS → What IP belongs to this domain name?&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IP&lt;/strong&gt; → identifies the host&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet mask&lt;/strong&gt; → determines the local network&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Default gateway&lt;/strong&gt; → router used to reach other networks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS&lt;/strong&gt; → converts names into IP addresses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These four pieces work together whenever a host needs to communicate beyond its immediate network.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. DHCP
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;DHCP = Dynamic Host Configuration Protocol.&lt;/strong&gt; DHCP automatically provides network configuration to a client.&lt;/p&gt;

&lt;p&gt;Without DHCP, a host would need to be manually configured with values such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IP = 9.1.1.11&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Subnet = 255.255.255.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Gateway = 9.1.1.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DNS = 8.8.8.8&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With DHCP, the client can obtain these settings automatically from a DHCP server. The basic process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client → DHCP Discover&lt;/li&gt;
&lt;li&gt;Server → DHCP Offer&lt;/li&gt;
&lt;li&gt;Client → DHCP Request&lt;/li&gt;
&lt;li&gt;Server → DHCP ACK&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;D → O → R → A&lt;/strong&gt; — Discover, Offer, Request, ACK&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result is that the host receives its network configuration automatically.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DHCP automatically gives a host its network configuration.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Quick Revision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ARP&lt;/strong&gt; → IP → MAC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS&lt;/strong&gt; → Name → IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DHCP&lt;/strong&gt; → IP + Mask + Gateway + DNS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP&lt;/strong&gt; → Web communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS&lt;/strong&gt; → Secure web communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FTP&lt;/strong&gt; → File transfer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SMTP&lt;/strong&gt; → Send email&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Big Picture
&lt;/h2&gt;

&lt;p&gt;These protocols solve different problems, but they work together when a host accesses something on the Internet. A simplified sequence could look like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DHCP&lt;/strong&gt; → get IP + subnet mask + gateway + DNS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS&lt;/strong&gt; → convert domain name → destination IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing&lt;/strong&gt; → decide whether destination is local or remote&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ARP&lt;/strong&gt; → resolve the next-hop IP → MAC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP / HTTPS&lt;/strong&gt; → communicate with the web server&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So don't think of these protocols as isolated terms. They are different pieces of the same networking process:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;DHCP&lt;/strong&gt; → network configuration · &lt;strong&gt;DNS&lt;/strong&gt; → find destination IP · &lt;strong&gt;Routing&lt;/strong&gt; → find next hop · &lt;strong&gt;ARP&lt;/strong&gt; → find next-hop MAC · &lt;strong&gt;HTTP/HTTPS&lt;/strong&gt; → exchange web data&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And other protocols solve other application-level problems:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;FTP&lt;/strong&gt; → file transfer. &lt;strong&gt;SMTP&lt;/strong&gt; → email.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important mental model is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Protocols exist because different parts of communication need different rules.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 6 of 6 — Networking Fundamentals for Cloud &amp;amp; DevOps&lt;/em&gt;&lt;br&gt;
&lt;em&gt;This completes the current networking fundamentals sequence and provides the protocol foundation needed for deeper AWS networking concepts.&lt;/em&gt;&lt;/p&gt;

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