<?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: GERALD IZUCHUKWU</title>
    <description>The latest articles on DEV Community by GERALD IZUCHUKWU (@gerald_izuchukwu).</description>
    <link>https://dev.to/gerald_izuchukwu</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%2F534710%2F7861ceac-feab-4927-84e6-21baa247740c.jpeg</url>
      <title>DEV Community: GERALD IZUCHUKWU</title>
      <link>https://dev.to/gerald_izuchukwu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gerald_izuchukwu"/>
    <language>en</language>
    <item>
      <title>What AWS VPC Actually Does Under the Hood (I Rebuilt It on Linux to Find Out)</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:29:46 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/what-aws-vpc-actually-does-under-the-hood-i-rebuilt-it-on-linux-to-find-out-3iif</link>
      <guid>https://dev.to/gerald_izuchukwu/what-aws-vpc-actually-does-under-the-hood-i-rebuilt-it-on-linux-to-find-out-3iif</guid>
      <description>&lt;p&gt;If you've worked with AWS for more than a few weeks, you've created a VPC. You've drawn subnets, attached route tables, written security group rules, maybe even set up peering between two VPCs. It works. You move on.&lt;/p&gt;

&lt;p&gt;But underneath the console clicks and Terraform resources, AWS is orchestrating real network primitives: isolating boundaries, packet filtering, routing decisions, and it is doing this at massive scale. Most of us never see that layer. It's abstracted away, and that's the whole point of a managed service.&lt;/p&gt;

&lt;p&gt;I wanted to see it anyway. So I built &lt;strong&gt;vpcctl&lt;/strong&gt;, a Python CLI that replicates core AWS VPC networking behavior: subnets, routing, security groups, peering, using nothing but native Linux primitives: network namespaces, veth pairs, Linux bridges, and iptables. This post walks through what AWS VPC actually does, and how rebuilding the equivalent behavior by hand changed how I think about designing and debugging real AWS networks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AWS Abstraction
&lt;/h2&gt;

&lt;p&gt;Before diving into the internals, it's worth being precise about what a VPC actually consists of, since these are the pieces the rest of this post maps against:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC:&lt;/strong&gt; an isolated virtual network with its own private IP address range (CIDR block).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subnet:&lt;/strong&gt; A subdivision of that CIDR, each tied to an Availability Zone, marked public or private based on its route table configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route tables:&lt;/strong&gt; The rules that decide where traffic goes, either to the internet via an Internet Gateway, or to another subnet, or to a peered VPC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; This is the security mechanism on instances. It is a stateful, instance-level firewall that allows or denies traffic based on rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC peering:&lt;/strong&gt; A private routing connection between two otherwise isolated VPCs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these is a &lt;em&gt;logical&lt;/em&gt; construct. AWS doesn't have a magic "VPC device" somewhere underneath; it's still doing packet forwarding, filtering, and NAT, just like any other network. The question I set out to answer was: &lt;em&gt;&lt;strong&gt;"What does that look like if you build it yourself?"&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Experiment
&lt;/h2&gt;

&lt;p&gt;vpcctl simulates each of these constructs using Linux tools that already exist on any machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network namespaces&lt;/strong&gt; stand in for isolated subnets; each namespace has its own routing table and interfaces, completely separate from the host.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux bridges&lt;/strong&gt; act as the shared L2 segment a subnet's instances sit on&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;veth pairs&lt;/strong&gt; are like physical wires that connect namespaces to bridges, the way an ENI connects an instance to a subnet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iptables&lt;/strong&gt; rules enforce security-group-style allow/deny behavior&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT via iptables MASQUERADE&lt;/strong&gt; provides the "public subnet" internet access pattern&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CLI supports creating VPCs, adding subnets (public or private), attaching security group rules, setting up peering between two VPCs, and persisting that state across runs. This is the same operations you'd do in the AWS console, just executed as real Linux networking commands instead of API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping AWS Concepts to Linux Primitives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS Concept&lt;/th&gt;
&lt;th&gt;Linux Equivalent&lt;/th&gt;
&lt;th&gt;What It's Really Doing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subnet isolation&lt;/td&gt;
&lt;td&gt;Network namespace&lt;/td&gt;
&lt;td&gt;Separate routing table and interface stack; the same reason instances in different subnets can't see each other's L2 broadcast domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public/private subnet&lt;/td&gt;
&lt;td&gt;Route table + NAT (iptables MASQUERADE)&lt;/td&gt;
&lt;td&gt;A "public" subnet isn't special; it just has a default route to something with internet access, same as a namespace routed through a MASQUERADE rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security groups&lt;/td&gt;
&lt;td&gt;iptables rules on the veth/bridge interface&lt;/td&gt;
&lt;td&gt;Stateful filtering happens at the interface level, not "on the instance," which is exactly why security group changes take effect without a restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VPC peering&lt;/td&gt;
&lt;td&gt;Routes between namespaces via a shared bridge or veth link&lt;/td&gt;
&lt;td&gt;Peering is just a route. No transit, no NAT, which is also why peered VPCs can't have overlapping CIDRs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route tables&lt;/td&gt;
&lt;td&gt;Per-namespace routing tables&lt;/td&gt;
&lt;td&gt;Each subnet's "personality" (public vs private) is entirely determined by what's in this table, nothing else&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seeing these side by side made a few AWS behaviors click that I'd previously just memorized as rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why you can't have transitive peering?&lt;/strong&gt; where A connects to B and B connects to C, and A is expected to communicate to C through B? This is because each peering connection is just a single static route on each side, added directly to each VPC's route table, pointing straight at the peer. There's no protocol exchanging reachability information between B and A, or between B and C. B has a route to A and a route to C, but nothing tells A "hey, go through B to reach C" because there's no routing intelligence running in between, only two independent point-to-point entries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why security group changes are immediate but NACL changes can behave differently?&lt;/strong&gt; Security groups are stateful: they track connections, so if you allow inbound traffic on a port, the return traffic is automatically allowed out, no matching outbound rule needed. NACLs are stateless: they evaluate every packet independently against explicit rules in both directions, with no memory of the connection. That's the same distinction between an iptables conntrack-based rule (which tracks connection state) and a plain stateless packet filter. It's why NACL misconfigurations are a more common source of "one direction works, the other doesn't" bugs than security group misconfigurations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why does subnet CIDR planning matter so much upfront?&lt;/strong&gt; A network namespace doesn't renegotiate its address space once it's created; the range you assign it is fixed unless you tear it down and rebuild it. AWS subnets work the same way: you can't resize a subnet's CIDR block after creation. If you undersize a subnet early on, the fix later is migrating workloads to a new subnet, not adjusting the existing one. This is why real AWS architectures reserve more IP space up front than they think they'll need.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why a route table, not a checkbox, is what makes a subnet "public."?&lt;/strong&gt; There's no attribute on a subnet that says &lt;em&gt;public: true&lt;/em&gt;. A subnet is public purely because its route table has a route sending 0.0.0.0/0 traffic to an Internet Gateway. Attach that same Internet Gateway route to a different subnet, and it becomes public too, nothing else changes. This maps directly to how vpcctl decides a namespace's behavior: it's entirely a function of what's in that namespace's routing table, not a flag set anywhere else.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why peered VPCs can't have overlapping CIDR ranges?&lt;/strong&gt; Peering just adds a route pointing at the peer's CIDR block. If two VPCs share overlapping address space, that route becomes ambiguous; the system can't tell which destination a given IP actually belongs to. This is the same reason you can't route between two Linux namespaces with identical subnet ranges: routing depends on non-overlapping destinations to make an unambiguous decision.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why an Internet Gateway attached to a VPC doesn't automatically give every subnet internet access?&lt;/strong&gt; Attaching the gateway just makes it available as a possible next hop for the VPC. Each subnet still needs its own route table entry pointing to it. This two step design (attach the gateway, then explicitly route to it per subnet) is why you can have a VPC with internet connectivity available but individual subnets still fully private, by simply not adding that route.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Broke, and What It Taught Me
&lt;/h2&gt;

&lt;p&gt;Building this surfaced a handful of real bugs, each of which mapped back to an "aha" about AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interface name length limits.&lt;/strong&gt; Linux caps network interface names at 15 characters (&lt;code&gt;IFNAMSIZ&lt;/code&gt; in the kernel). I hit this when generating veth pair names programmatically from VPC and subnet identifiers, since concatenating a VPC ID with a subnet ID blew past that limit, and the interface creation silently failed or was truncated in unexpected ways. This explains something about AWS I'd never questioned: why AWS enforces its own opaque, fixed-format resource IDs (&lt;code&gt;vpc-0a52d63562a6bb5c6&lt;/code&gt;, &lt;code&gt;subnet-xxxx&lt;/code&gt;) instead of letting you name resources freely and using those names at the networking layer. AWS isn't just being tidy; it's very likely working around the same low-level constraints, just hidden behind an abstraction layer you never see.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unguarded top-level function calls executing at import time.&lt;/strong&gt; This was a Python structural bug: code that should only run when explicitly called was instead executing the moment the module was imported, because it sat at the top level of the file instead of inside a function or behind an &lt;code&gt;if __name__ == "__main__"&lt;/code&gt; guard. The practical effect was things happening before their dependencies existed. It forced me to think seriously about initialization order, which is exactly the same category of problem that shows up in Terraform when a route resource gets created before the NAT Gateway it depends on exists. Terraform normally infers this dependency graph from resource references, but when that inference fails (implicit dependencies it can't see, or resources defined in a way that hides the relationship), you get the same "this was built before its prerequisite existed" failure, just at the infrastructure layer instead of the code layer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing &lt;code&gt;-j ACCEPT&lt;/code&gt; targets in iptables chains.&lt;/strong&gt; iptables rules that don't end in an explicit &lt;code&gt;ACCEPT&lt;/code&gt; (or another terminating target) don't do anything on their own; they just fall through to the next rule, and eventually to the chain's default policy. I had rules that looked correct on paper but never actually accepted traffic because I'd forgotten the terminating target, so packets were silently dropped with zero error output. That's precisely the debugging experience of a "connection timed out" against a misconfigured AWS security group: there's no rejection, no error message, just silence, because a stateful firewall that doesn't explicitly allow traffic doesn't tell you it denied it either. Rebuilding this by hand made that failure mode intuitive rather than mysterious, since I'd actually watched packets vanish for the exact same structural reason.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;delete_vpc&lt;/code&gt; name collisions.&lt;/strong&gt; My state management identified VPCs by name, and I hadn't accounted for two VPCs being given the same name, which meant a delete operation could match and remove the wrong resource, or fail ambiguously. This is a small-scale version of why AWS enforces strict uniqueness constraints on resource identifiers within an account and region: without a uniqueness guarantee, any operation that looks up a resource by a human-readable identifier becomes unreliable the moment there's a collision. AWS solves this by generating unique IDs regardless of what name tag you attach, so the name is cosmetic and the ID is what every underlying operation actually references.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Race conditions between namespace creation and veth attachment.&lt;/strong&gt; Bringing up a namespace and immediately attaching a veth pair to it sometimes failed if the namespace wasn't fully initialized yet, since these are asynchronous operations at the OS level even though they look sequential in a script. This maps directly onto a very common AWS/Terraform pattern: attaching an ENI, an EBS volume, or a security group to an EC2 instance immediately after the instance is launched, before it's actually in a running state. Both cases are the same underlying lesson: that "created" and "ready to be attached to" are not the same moment, and code that assumes otherwise will fail intermittently rather than consistently, which makes it worse to debug.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;None of this changes how you'd actually configure a VPC in the console or in Terraform. But it changes how you debug one. When a security group rule "isn't working," or a peering connection isn't routing traffic, or a private subnet can't reach the internet, knowing that these are, underneath, just routing tables and stateful packet filters makes the failure modes obvious instead of opaque.&lt;/p&gt;

&lt;p&gt;If you want to build the same intuition, the fastest path isn't reading more VPC documentation; it's picking one AWS networking concept and asking "what would I have to build, from raw Linux primitives, to reproduce this behavior?" You'll hit the edge cases AWS handles for you, and you'll understand exactly why they exist.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;vpcctl is written in Python and available on &lt;a href="https://github.com/Gerald-Izuchukwu/working_vpcctl" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>aws</category>
      <category>linux</category>
      <category>networking</category>
    </item>
    <item>
      <title>Nginx from Zero: Config Files to HTTPS</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:46:30 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/nginx-from-zero-config-files-to-https-aek</link>
      <guid>https://dev.to/gerald_izuchukwu/nginx-from-zero-config-files-to-https-aek</guid>
      <description>&lt;p&gt;This would be a simple write-up explaining Nginx to a complete beginner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nginx Folder Structure
&lt;/h3&gt;

&lt;p&gt;An Nginx folder can range from having just a single &lt;code&gt;nginx.conf&lt;/code&gt; file to several other directories and files. Let's explore!&lt;br&gt;
Nginx reads the &lt;code&gt;nginx.conf&lt;/code&gt; file; while other files can be configured, Nginx reads&lt;code&gt;nginx.conf&lt;/code&gt; directly; any other file must be explicitly included or referenced from there. A simple &lt;code&gt;nginx.conf&lt;/code&gt; contains the following blocks of configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="s"&gt;www-data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;worker_processes&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;events&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;worker_connections&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;mime.types&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;default_type&lt;/span&gt;  &lt;span class="nc"&gt;application/octet-stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;keepalive_timeout&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="n"&gt;/etc/nginx/conf.d/*.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;user www-data&lt;/code&gt; tells Nginx what user the worker processes should run as. Nginx master process will always start as root, so it can bind to ports like 80 and 443 on the host machine, but as soon as the master process initializes, the worker processes run but not with root privileges. The user of the worker process can be obtained using the command &lt;code&gt;ps aux | grep nginx&lt;/code&gt;, and for most Ubuntu or Debian Systems, the user is typically &lt;em&gt;www-data&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Why not "ubuntu"? The "ubuntu" user is your login account. It owns your shell, PM2 processes, and Node.js apps. Nginx should run under a dedicated service account like www-data because it has fewer privileges (better security), it's the standard on Ubuntu, and permissions are easier to manage.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;worker_processes auto&lt;/code&gt; tells Nginx to create one worker per CPU core of the host machine. A specific number can be defined in this block, but it is safest for Nginx to adapt to the host machine. The &lt;em&gt;worker processes&lt;/em&gt; perform the actual work of serving requests, while the master process manages them. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;events{}&lt;/code&gt; block defines or configures how workers handle connections, and this is defined with &lt;code&gt;worker_connections 1024&lt;/code&gt;, which is the &lt;strong&gt;maximum number of connections a worker can handle simultaneously.&lt;/strong&gt; What this means is that if we have 4 workers and  1024 worker connections, then we will have roughly 4096(4*1024) simultaneous connections that the workers can handle.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http{}&lt;/code&gt; block is used to define everything related to HTTP and HTTPS configuration. Inside it, we find things like &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;include mime.types;&lt;/code&gt; This tells browsers what type of file they are receiving, e.g., text/html, text/css, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;default_type application/octet-stream;&lt;/code&gt; A fallback MIME type if Nginx encounters a file type it doesn't understand.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;keepalive_timeout 65;&lt;/code&gt; Normally, after sending a response, the connection closes, but with this line, Nginx opens the connection for up to 65 seconds, waiting for additional requests before closing it. This reduces the overhead of creating new TCP connections for every resource on a page.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include /etc/nginx/conf.d/*.conf;&lt;/code&gt; By default, Nginx won't read other config files, but this tells Nginx to read every file with the &lt;em&gt;.conf&lt;/em&gt; extension inside the &lt;em&gt;/etc/nginx/conf.d/&lt;/em&gt; folder as if the contents were written in the nginx.conf file. This file usually contains the &lt;code&gt;server {}&lt;/code&gt; block&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The typical server block contains the settings for one website or application. Here is a common example with the most important directives&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Port to listen on&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Domain names this server responds to&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Root directory for static files&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Default file to serve&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Access and error logs&lt;/span&gt;
    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle requests&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Custom error page&lt;/span&gt;
    &lt;span class="kn"&gt;error_page&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="n"&gt;/404.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/404.html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;internal&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;listen 80;&lt;/code&gt; tells Nginx to accept incoming HTTP requests on port 80. For HTTPS, we will use &lt;code&gt;listen 443 ssl;&lt;/code&gt;. Listening can be done on multiple ports.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;server_name&lt;/code&gt; This specifies which hostnames this server block should respond to. It can be your domain name or, in most development cases, &lt;em&gt;localhost.&lt;/em&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;server_name _;&lt;/code&gt; is used to match any hostname not defined in the server block. It is mostly used with a default server to catch all placeholder. You would use it when you have multiple server_name blocks and want explicit, deliberate control over what happens when none of them match, rather than letting Nginx silently pick the first one it finds. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;root /var/www/html;&lt;/code&gt; This is the directory where Nginx looks for static files. If a request comes in for &lt;em&gt;GET /about.html&lt;/em&gt;, Nginx looks for &lt;em&gt;/var/www/html/about.html&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index index.html index.htm;&lt;/code&gt; When a user visits &lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt;, Nginx looks for index.html or index.htm and serves whichever it finds first.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;access_log&lt;/code&gt; and &lt;code&gt;error_log&lt;/code&gt; are both &lt;strong&gt;directives&lt;/strong&gt; (keywords) in Nginx, and it is used to specify where the access logs and errors will be saved. It is standard practice to save both logs in the same directory &lt;code&gt;/var/log/nginx/&lt;/code&gt;. While access_logs are saved in access.log files, error_logs are saved in error.log files. Access logs record every request, and error logs record every error or warning request.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;location {}&lt;/code&gt; block is used to specify how Nginx handles requests for a particular file path. It varies, depending on whether you are serving static files or reverse proxying. A simple static file location block might look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that if someone requests/images/cat.png, Nginx checks and, if the file exists, serves it; if not, it returns a 404 error. The general syntax is to try the files one after the other until a match is found, and if no match is found, it resolves to the fallback, which is the last argument passed.&lt;/p&gt;

&lt;p&gt;The location block can also look like this when it is reverse proxying&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt; &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt; &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;proxy_pass http://localhost:3000;&lt;/code&gt; Forwards the client's request to the backend application running on localhost (the same machine) on port 3000, then returns the backend's response to the client.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_set_header Host $host;&lt;/code&gt; Sends the original Host header (for example, example.com) to the backend so it knows which domain the client requested, rather than seeing localhost:3000&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_set_header X-Real-IP $remote_addr;&lt;/code&gt; Passes the client's actual IP address to the backend application so it knows who made the request instead of only seeing the Nginx server's IP.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/code&gt;
Sends a list of all client and proxy IP addresses the request has passed through, appending the current client's IP to any existing list, which is useful when multiple reverse proxies are in place.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;For example&lt;br&gt;
If the client IP is 203.0.113.10&lt;br&gt;
and the request passes through another proxy of 198.51.100.5&lt;br&gt;
then Nginx forwards &lt;code&gt;X-Forwarded-For: 203.0.113.10, 198.51.100.5&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;proxy_set_header X-Forwarded-Proto $scheme;&lt;/code&gt; Tells the backend whether the client originally connected using HTTP or HTTPS, allowing the application to generate correct URLs, perform HTTPS redirects, or enforce secure cookies.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;For example&lt;br&gt;
Client visits &lt;a href="https://example.com" rel="noopener noreferrer"&gt;https://example.com&lt;/a&gt; → X-Forwarded-Proto: https&lt;br&gt;
Client visits &lt;a href="http://example.com" rel="noopener noreferrer"&gt;http://example.com&lt;/a&gt; → X-Forwarded-Proto: http&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These &lt;code&gt;proxy_set_header&lt;/code&gt; directives ensure your backend application sees information about the original client request, rather than only seeing requests coming from Nginx itself.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;error_page 404 /404.html;&lt;/code&gt; This is used to customize an error page that Nginx will serve when we hit a 404 error, instead of the default 404.&lt;/p&gt;

&lt;p&gt;That's it for the basic configuration on Nginx, and most of the time, this setup will be all you need to set up a simple HTTP static file server or reverse proxy server. Now all of these could be in the nginx.conf file, or you can create another file and call it whatever you like, say proxy_setup.conf, and because our server block most times is our largest block, we can move the server block to the file created and reference that file from our original nginx.conf. This keeps our files separated and easy to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTPS NGINX
&lt;/h2&gt;

&lt;h4&gt;
  
  
  What problem is HTTPS trying to solve?
&lt;/h4&gt;

&lt;p&gt;Imagine you are logging in to a website via HTTP and entering your credentials. Since HTTP is just a hypertext transfer protocol, all the details you entered, including sensitive information, will be sent as plain text, and this information can and will be seen by anyone in between. This can include your ISP, someone on a public WiFi, and even a hacker. HTTP does not provide encryption, authentication, or integrity checks; this is where HTTPS comes in.&lt;/p&gt;

&lt;p&gt;The S in HTTPS stands for &lt;em&gt;secure&lt;/em&gt;, and this means information sent over HTTPS will be secured in the sense that it will be encrypted, will be authenticated, and will have some checks that look out for tampering. So HTTPS is HTTP running inside an encrypted TLS connection.&lt;/p&gt;

&lt;h4&gt;
  
  
  SSL vs TLS
&lt;/h4&gt;

&lt;p&gt;SSL means &lt;em&gt;Secure Sockets Layer&lt;/em&gt;. It was the original encryption protocol. SSL introduced the concept of a handshake before any data was exchanged. There were several versions of SSL, and version SSL 3.0 was the standard for a long time, but today SSL is obsolete, and TLS replaced it. &lt;/p&gt;

&lt;p&gt;TLS means &lt;em&gt;Transport Layer Security&lt;/em&gt;. Instead of fixing SSL indefinitely, the standards community developed a new protocol. Think of TLS as the next generation of SSL, and serves the same purpose. People still say 'SSL Certificate' till today, but that's simply because the SSL name stuck. In reality, if you obtain a certificate today from a certificate authority like Let's Encrypt or DigiCert, it's used with TLS, not SSL. So the terms SSL Certificate and TLS Certificate are commonly used interchangeably, but TLS Certificate is technically correct. &lt;/p&gt;

&lt;p&gt;Compared with SSL, TLS offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stronger encryption algorithms.&lt;/li&gt;
&lt;li&gt;Better key exchange mechanisms.&lt;/li&gt;
&lt;li&gt;Better protection against downgrade attacks.&lt;/li&gt;
&lt;li&gt;Stronger integrity checks.&lt;/li&gt;
&lt;li&gt;Faster handshakes (especially TLS 1.3).&lt;/li&gt;
&lt;li&gt;Removal of outdated and insecure cryptographic features&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  What you need before setting up HTTPS
&lt;/h4&gt;

&lt;p&gt;Setting up HTTPS is not as hard as most people think; you need a few things in place before you can set it up for reverse proxying on your server. First, you need to have a domain name pointing to your server's IP address. This is because HTTPS is secure and it uses a TLS/SSL certificate to prove the server's identity. Picture it this way: when you browse a website, for example, &lt;code&gt;example.com&lt;/code&gt;, the browser asks, "Can you prove you are actually example.com?" and the server replies by providing a certificate. The browser verifies that the certificate was issued by a trusted Certificate Authority (CA), that the certificate hasn't expired, and that the hostname you're visiting matches one of the names on the certificate. Now, if you are visiting &lt;code&gt;example.com&lt;/code&gt; and the certificate is for example.com, everything is fine. On the other hand, if you are visiting &lt;code&gt;https://203.0.113.10&lt;/code&gt; and the certificate is for example.com, the browser will show a certificate warning because the names don't match.&lt;/p&gt;

&lt;p&gt;Let's Encrypt is the CA most people use for free certificates, and it requires a domain name, such as &lt;code&gt;example.com&lt;/code&gt;, &lt;code&gt;api.example.com&lt;/code&gt;, or &lt;code&gt;myapp.dev&lt;/code&gt;. It checks/validates that you control the domain before issuing the certificate. It will not issue a normal certificate for &lt;code&gt;https://203.0.113.10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Can HTTPS work with an IP address? Yes, but it's uncommon. A Certificate Authority can issue a certificate containing an IP address instead of a domain, but not all CAs offer this; it's less common, and it's generally used in enterprise or internal environments.&lt;/p&gt;

&lt;h4&gt;
  
  
  Setting Up HTTPS NGINX
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Get your domain name
Some platforms allow you to own your own domain and subdomain names for free, such as &lt;a href="//freedomain.one"&gt;FreeDomain&lt;/a&gt;. Let's say our domain name is &lt;code&gt;myspeech.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Point your domain name to your IP address
After getting the domain name, you can use the same platform you got the name from to point your domain name to your IP address, or you can use a cloud provider like AWS Route53. This takes up to 3 minutes, and the command &lt;code&gt;nslookup domain name&lt;/code&gt; allows you to confirm if your domain name points to your server IP address&lt;/li&gt;
&lt;li&gt;Create the HTTP for your website 
This step is not compulsory, but it makes it easy to create the HTTPS version&lt;/li&gt;
&lt;li&gt;Create a new config file in the sites-available folder 
&lt;code&gt;sudo nano /etc/nginx/sites-available/myspeech.com.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create the conf file for &lt;em&gt;myspeech.com.conf&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;myspeech.com&lt;/span&gt; &lt;span class="s"&gt;www.myspeech.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://localhost:3002&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_http_version&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Host&lt;/span&gt;              &lt;span class="nv"&gt;$host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Real-IP&lt;/span&gt;         &lt;span class="nv"&gt;$remote_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-For&lt;/span&gt;   &lt;span class="nv"&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class="nv"&gt;$scheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Simple health check — no backend needed, nginx answers directly&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/health&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;'OK&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;n'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/plain&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Enable the site &lt;code&gt;sudo ln -s /etc/nginx/sites-available/myspeech.com.conf /etc/nginx/sites-enabled/&lt;/code&gt; and disable the default nginx page
&lt;code&gt;sudo rm -f /etc/nginx/sites-enabled/default&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test config syntax &lt;code&gt;sudo nginx -t&lt;/code&gt; and if "syntax is ok" then reload &lt;code&gt;sudo systemctl reload nginx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test your setup by hitting the health route
&lt;code&gt;curl http://myspeech.com/health&lt;/code&gt;
or the service route 
&lt;code&gt;curl http://myspeech.com/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we get our desired response on HTTP, we can then move to HTTPS&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check if certbot exists 
&lt;code&gt;certbot --version&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then run this single command &lt;code&gt;sudo certbot --nginx -d myspeech.com&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Certbot will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect your existing nginx config&lt;/li&gt;
&lt;li&gt;Issue the cert from Let's Encrypt&lt;/li&gt;
&lt;li&gt;Auto-modify your nginx config to add SSL&lt;/li&gt;
&lt;li&gt;Set up the HTTP → HTTPS redirect automatically &lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Check the new configuration and reload nginx &lt;code&gt;sudo nginx -t &amp;amp;&amp;amp; sudo systemctl reload nginx&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test your setup by hitting the health route
&lt;code&gt;curl https://myspeech.com/health&lt;/code&gt;
or the service route 
&lt;code&gt;curl https://myspeech.com/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why is HTTPS already working without you writing the 443 block?
&lt;/h3&gt;

&lt;p&gt;That's certbot's magic. When you run &lt;code&gt;certbot --nginx&lt;/code&gt;, it reads your existing config and automatically injects the SSL server block and the HTTP to HTTPS redirect into your conf file. Certbot gives you a working HTTPS setup, but it is bare minimum; you can choose to add other functionalities depending on your requirements&lt;/p&gt;

</description>
      <category>backend</category>
      <category>beginners</category>
      <category>nginx</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Traditional IT vs Cloud Computing: The Great Shift</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Thu, 23 Apr 2026 09:46:02 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/traditional-it-vs-cloud-computing-the-great-shift-1216</link>
      <guid>https://dev.to/gerald_izuchukwu/traditional-it-vs-cloud-computing-the-great-shift-1216</guid>
      <description>&lt;p&gt;For decades, organizations relied on &lt;strong&gt;Traditional IT&lt;/strong&gt; as the backbone of their technology operations. This meant owning and managing physical infrastructure—servers, storage devices, and networking equipment—housed in dedicated data centres. While this model gave companies full control, it also came with heavy responsibilities. Businesses had to invest large amounts of capital upfront, maintain hardware, handle failures, and plan capacity far in advance. Scaling was slow and expensive; if demand increased, new hardware had to be purchased and installed, often taking weeks or months.&lt;/p&gt;

&lt;p&gt;Then came &lt;strong&gt;Cloud Computing&lt;/strong&gt;, a model that fundamentally changed how technology is delivered. Instead of owning infrastructure, companies can now access computing resources—servers, databases, storage, and more—over the internet. These resources are available on-demand and operate on a pay-as-you-go pricing model. This means businesses only pay for what they use, eliminating the need for massive upfront investments. More importantly, cloud platforms allow near-instant scalability. Whether you're launching a startup or handling millions of users, you can scale resources up or down in minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The “Why” Behind the Shift
&lt;/h3&gt;

&lt;p&gt;So why are modern businesses moving to the cloud? The answer lies in &lt;strong&gt;speed, flexibility, and innovation&lt;/strong&gt;. In today’s fast-paced digital world, companies need to experiment, deploy, and iterate quickly. Traditional IT simply can’t keep up with this demand. Cloud computing removes the bottlenecks associated with hardware procurement and infrastructure setup, enabling teams to focus on building and improving products.&lt;/p&gt;

&lt;p&gt;Additionally, the cloud supports global accessibility. Teams can collaborate from anywhere, and applications can be deployed closer to users worldwide. This not only improves performance but also enhances user experience. Ultimately, the cloud empowers businesses to innovate faster, reduce operational overhead, and stay competitive.&lt;/p&gt;

&lt;h3&gt;
  
  
  The DevOps Connection
&lt;/h3&gt;

&lt;p&gt;This shift to cloud computing is also what makes &lt;strong&gt;DevOps&lt;/strong&gt; possible at scale. DevOps is all about automation, collaboration, and continuous delivery and the cloud provides the perfect environment for these practices. With cloud platforms, teams can automate infrastructure provisioning, deploy applications using CI/CD pipelines, and monitor systems in real time.&lt;/p&gt;

&lt;p&gt;In a traditional IT setup, implementing such automation would be complex and slow due to hardware limitations. In contrast, the cloud enables Infrastructure as Code (IaC), containerization, and rapid deployment cycles. This allows development and operations teams to work more closely together, reducing friction and accelerating delivery.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;The transition from traditional IT to cloud computing is more than just a technological upgrade, it’s a mindset shift. It moves organizations from rigid, hardware-focused operations to flexible, software-driven innovation. For anyone starting a journey in cloud and DevOps, understanding this shift is essential as it’s the foundation for everything that comes next.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>cloud</category>
      <category>ai</category>
    </item>
    <item>
      <title>Provisioning A Three-Tier Application on AWS using Infrastructure-As-Code (IaC)</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Sat, 07 Dec 2024 10:33:01 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/provisioning-a-three-tier-application-on-aws-using-infrastructure-as-code-iac-1a21</link>
      <guid>https://dev.to/gerald_izuchukwu/provisioning-a-three-tier-application-on-aws-using-infrastructure-as-code-iac-1a21</guid>
      <description>&lt;p&gt;This is a general architecture of what we will build. In case it isn't clear enough, please click &lt;a href="https://testserviceaandserviceb.s3.us-east-1.amazonaws.com/three-tier-main-s3-upload.png" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5srbifi49xwnxr3iplol.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5srbifi49xwnxr3iplol.png" alt="Project HLD" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IaC stands for Infrastructure as code and for a while I struggled to understand this concept, and when it seemed like I was starting to get it, I started confusing it with some other thing. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share. Infrastructure as code is basically provisioning or creating your resources through code or configuration files, in other words, automating the process of creating them in one click, instead of creating them singly through GUI or CLI, and Terraform is a tool that helps us to do that. An AWS cloud native used to also achieve IaC is &lt;a href="https://aws.amazon.com/cloudformation/" rel="noopener noreferrer"&gt;AWS CLoudFormation&lt;/a&gt; and you can learn more about it &lt;a href="https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Terraform is an open-source tool that efficiently helps us to provision infrastructure. It is owned by Hashicorp and uses its own language HCL (Hashicorp Configuration Language), for the configuration of these infrastructures. Terraform can be used for several things but since it is only an IaC provisioning tool, it also has its limitations. &lt;/p&gt;

&lt;p&gt;This article focuses on using Terraform to provision a three-tier application on AWS. There are a lot of three-tier apps out there, maybe more detailed than this but what I intend to do for this is to explain every concept used here, to help me understand further as it takes a while for me to grasp a concept fully and to help people like me.&lt;/p&gt;

&lt;p&gt;This writeup will use the following technologies&lt;/p&gt;
&lt;h4&gt;
  
  
  Network
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Subnet&lt;/li&gt;
&lt;li&gt;Route Table&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;Nat Gateway&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Compute
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;launch template&lt;/li&gt;
&lt;li&gt;Key pair&lt;/li&gt;
&lt;li&gt;Elastic Load Balancer&lt;/li&gt;
&lt;li&gt;Target Groups&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Database
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;RDS Database&lt;/li&gt;
&lt;li&gt;Subnet Groups&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Other AWS Resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;IAM Role&lt;/li&gt;
&lt;li&gt;S3 Bucket&lt;/li&gt;
&lt;li&gt;AWS SNS&lt;/li&gt;
&lt;li&gt;AWS CloudWatch&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Other Non-AWS Resources
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Nginx&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Nodejs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are going to break this down into steps&lt;/p&gt;
&lt;h3&gt;
  
  
  STEP 1: Upload your static files and logic  code to Amazon S3 Bucket
&lt;/h3&gt;

&lt;p&gt;To do this, we need to create an S3 bucket, create two folders, and name them frontend and backend (can be named otherwise). In the frontend folder, upload all your static files as well as your nginx.conf file. In the backend folder, we upload all our logic code files. A link to the repo can be found &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

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

&lt;p&gt;Dont forget to name your bucket something unique as bucket names are specific per region&lt;/p&gt;
&lt;h3&gt;
  
  
  STEP 2: Set up IAM (User, Roles and Policies)
&lt;/h3&gt;

&lt;p&gt;You can use various ways to configure AWS authentication, I will walk you through using IAM &lt;/p&gt;

&lt;p&gt;An IAM user represents a specific person or application that interacts with resources. This is the "user" that allows Terraform to perform certain tasks in our AWS account. A User's action is defined by the policies attached to that user. An IAM User is quite different from an IAM Role because, a user is mostly configured to perform long-term tasks as it has permanent credentials, whereas an IAM role is for short-term and immediate functions as its credentials are temporary. To create an IAM user for CLI, follow the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://https://us-east-1.console.aws.amazon.com/console/home?region=us-east-1#" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Navigate to IAM&lt;/li&gt;
&lt;li&gt;Add User, give the user a name&lt;/li&gt;
&lt;li&gt;Attach &lt;strong&gt;AdminstratorAccess&lt;/strong&gt; Policy to the user &lt;/li&gt;
&lt;li&gt;Review and create user&lt;/li&gt;
&lt;li&gt;After creating a user, select the user and navigate to security credentials, scroll down to access keys, and click &lt;code&gt;create access keys&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select a use case, add a description and the Access key and Secret Access key will be created, download the CSV and save it in a secure folder&lt;/li&gt;
&lt;li&gt;Now set the environment variables
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_REGION="your_region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note: The reason why I gave this user Administrator Access policy privilege is because the user will interact with so many resources. The best practice is to compile all the rules the user will need to one policy and attach that policy to the user&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set up IAM Role
We will need an IAM role to perform two basic functions, therefore we create an IAM role and attach two policies to it. The first policy will be for our instances to be able to read our uploaded code files from Amazon S3. The second policy we will need is the SSM managed instance core policy, to be able to connect to our instance instead of opening an SSH port. Both policies already exist in AWS so there will be no need to create them. The following steps are to be followed to create the IAM role with these two policies&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Navigate to IAM, on the sidebar, click on roles&lt;/li&gt;
&lt;li&gt;Click on AWS Service (since we are using it for EC2)&lt;/li&gt;
&lt;li&gt;Choose EC2 for the use case, click next&lt;/li&gt;
&lt;li&gt;Add permissions, search for "AmazonS3ReadOnlyAccess" and AmazonSSMManagedInstanceCore, check them, and click next. &lt;/li&gt;
&lt;li&gt;Give the role a name and click "create role"&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  STEP 3: Set up your terraform
&lt;/h3&gt;

&lt;p&gt;I won't assume that you already have Terraform installed, if you do, that's fine, if you don't follow the steps below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;Teraform Download File&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select the download configuration for your operating system&lt;/li&gt;
&lt;li&gt;Test if the download was successful using &lt;code&gt;terraform -version&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create a folder for this terraform project, call it whatever you want. I will call mine &lt;em&gt;"three-tier-app-projects"&lt;/em&gt;
change directory into the folder &lt;code&gt;cd three-tier-app-projects&lt;/code&gt; and create a file named &lt;em&gt;main.tf&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Add the terraform block and aws provider block into the file and save
on the CLI, run the command &lt;code&gt;terraform init&lt;/code&gt; This command takes a while, so be patient
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 4.16"
    }
  }

  required_version = "&amp;gt;= 1.8.0"
}

provider "aws" {
  region  = "us-east-1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Providers are plugins that help manage and create your resources in IaC. providers could include aws, docker, nginx, etc.&lt;br&gt;
N/B For a simple project such as this, we can simply initialize &lt;code&gt;terraform&lt;/code&gt; with only the &lt;code&gt;provider&lt;/code&gt; block, without the terraform block&lt;/p&gt;
&lt;h3&gt;
  
  
  STEP 4: Setup VPC Network Aspect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;strong&gt;&lt;em&gt;terraform.tfvars file&lt;/em&gt;&lt;/strong&gt;. This file is used to store all our static variables&lt;/li&gt;
&lt;li&gt;Create the VPC, which is a network house that houses all our resources. Store the vpc_cidr block in the &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&lt;/strong&gt;, access it using the variable keyword before calling it in the aws_vpc resource block. Your &lt;strong&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&lt;/strong&gt; files should look like this
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region  = "us-east-1"
}

variable "env_prefix" {}
variable "avail_zone" {}
variable "vpc_cidr" {}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${var.env_prefix}_vpc"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;terraform.tfvars&lt;/code&gt; file, we have&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env_prefix    = "three-tier-demo"
avail_zone    = ["us-east-1a", "us-east-1b"]
vpc_cidr      = "10.0.0.0/16"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run the following command 
-&lt;code&gt;terraform fmt&lt;/code&gt;
-&lt;code&gt;terraform validate&lt;/code&gt;
-&lt;code&gt;terraform plan&lt;/code&gt;
-&lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;terraform fmt&lt;/code&gt; formats the terraform files in the current directory it was ran, to follow a particular structure,&lt;br&gt;
&lt;code&gt;terraform validate&lt;/code&gt; checks if the configuration is syntactically valid and internally consistent, &lt;br&gt;
&lt;code&gt;terraform plan&lt;/code&gt; checks and outputs all the resources that will be added by the configuration and&lt;br&gt;
&lt;code&gt;terraform apply&lt;/code&gt; goes ahead to apply the configuration after the prompt-"yes" is entered. These are some of the most used terraform commands.&lt;/p&gt;

&lt;p&gt;If everything goes well, a VPC named three-tier-demo_vpc will be provisioned in our us-east-1 zone&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 5: Let's finish creating our Network resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create Subnets for Public and Private resources in two availability zones for redundancy
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.env_prefix}_public_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 2)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_private_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "db_private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 4)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_db_private_subnet-${count.index + 1}"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Internet Gateway and Nat Gateway
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${var.env_prefix}_igw"
  }
}
resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.env_prefix}_nat_gateway"
  }
}

resource "aws_eip" "this" {
  vpc = true
  tags = {
    Name = "${var.env_prefix}_eip_nat"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;N/B: NatGW and ElasticIP (eip) are paid for, there is no free tier available for this. NatGW costs &lt;strong&gt;$0.05/hr&lt;/strong&gt; and EIP costs &lt;strong&gt;$0.005/hr&lt;/strong&gt; when it is not attached to an ec2 instance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Public and Private RouteTable and associate them to a subnet
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }


  tags = {
    Name = "${var.env_prefix}_public_route_table"
  }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public_route_table.id
}

resource "aws_route_table" "private_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.nat.id
  }

  tags = {
    Name = "${var.env_prefix}_private_route_table"
  }
}

resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

resource "aws_route_table_association" "db_private" {
  count          = 2
  subnet_id      = aws_subnet.db_private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did we just do?&lt;br&gt;
We created a VPC for all our resources to live in with a CIDR block of &lt;strong&gt;10.0.0.0/16&lt;/strong&gt;&lt;br&gt;
We created subnets which are like smaller houses in the VPC that house one or more resources. &lt;br&gt;
The public subnets (internet-facing) have an internet gateway route configured in the route table, to allow internet access.&lt;br&gt;
The private subnets (internal facing) have NAT gateway route configured in the route table to allow internet access when needed.&lt;br&gt;
The elastic IP is for Controlled Access, it allows us to use a particularly defined public IP address that doesn't change even if the resource it is attached to goes down. With a NAT Gateway, instances in a private subnet do not have direct public IPs, enhancing security. By using an EIP with the NAT Gateway, you maintain control over how and when outbound internet access is granted without exposing private instances directly to the internet&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 6: Security
&lt;/h3&gt;

&lt;p&gt;For security, we can set security on the subnet level(NACL) or instance level(Security groups). For this project, we will be using security groups. We need to create Security Groups for the Elastic Load Balancer, the Instances, and the RDS instance. To make our solution highly secure, we allow the web tier instance to receive traffic only from the external load balancer. We also allow the app tier instances to receive HTTP traffic only from the internal load balancer. Since the web tier and app tier has to communicate, we allow the internal load balancer to allow HTTP traffic only from the web-tier instances.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create Security Group for external Load Balancer, that allows HTTP traffic on port 80 and HTTPS on port 443
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "externalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }

  ingress {
    from_port   = 443 // https
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "01. External LoadBalancer Security Group"
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create SG for Web tier Instance, that allows traffic on Port 80 only from the external load balancer security group. You can add an SSH security group rule too, but it's best to use the SSM manager to access the instance terminal. If you prefer ssh, uncomment the ssh security group rule
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "webserverSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.externalLoadBalancerSG.id]

  }
    #ingress {
    #from_port       = 22
    #to_port         = 22
    #protocol        = "tcp"
    #cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "02. Web Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for the internal load balancer, that allows HTTP traffic on port 80 only from the web-tier security groups
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "internalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.webserverSG.id]
  }

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

  tags = {
    Name = "03. Internal Load Balancer Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for app-tier instances that allows HTTP traffic on port 9662 (our NodeJS server port) only from the internal load balancer security group. If you will be needing ssh access, you can add the ssh rule like the one we added in the web tier security group
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "appserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 9662
    to_port         = 9662
    protocol        = "tcp"
    security_groups = [aws_security_group.internalLoadBalancerSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. App Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, we will create a security group for our database instances that allow inbound traffic on port 3306 (AURORA/MYSQL) port
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "dbserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.appserverSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. Database Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The created security groups as seen on AWS console&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs0zba27lz1o91lcdcy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbs0zba27lz1o91lcdcy8.png" alt="Security Groups" width="799" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  STEP 7: Provision Web Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Role
&lt;/h5&gt;

&lt;p&gt;We will be needing the IAM Role we created earlier with two policies - &lt;code&gt;S3ReadOnly&lt;/code&gt; and &lt;code&gt;SSManagerProfile&lt;/code&gt;. This role will be attached to the instances and it will enable them to read the uploaded files on S3 as well as connect to the instance's terminal through SSMManagedInstanceCore  rather than opening port 22 for SSH-ing. We already created this role via the console but it can also be created using Terraform&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;Since we are trying to automate as much as possible and we want auto-scaling of instances, we use auto-scaling group. Auto-scaling group works with launch templates, so we create a launch template with keypair for SSH, entry-script i.e. a list of commands the instances will run after it is launched, and attach the IAM Profile created earlier. The frontend entry-script is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/blob/main/frontend_script.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;Next, we create a load balancer, we want this load balancer to be able to receive traffic from outside the VPC, so we make it internet-facing. The job of the load balancer is to distribute traffic evenly across all instances in order not to overwhelm a particular instance. For the load balancer to know the instances to distribute traffic evenly, the instances must be added to the load balancer's target group. The load balancer also checks the health of an instance before it routes traffic to it. The part of the ALB responsible for listening for incoming traffic requests, processing them, and routing them to the target group is called the &lt;code&gt;listener&lt;/code&gt;. It listens on port 80 or 443 and routes these traffic requests based on the rules specified.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The auto-scaling group is used to scale instances up or down based on traffic demand. For this to work, we specify the launch template, the minimum and maximum amount of instances to create, the subnets to launch these instances in, the target group to place these instances in for the ALB, as well as the health check. We also set an autoscaling policy to scale up or down the instances.&lt;br&gt;
The link to the nginx server configuration is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main/frontend" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STEP 8: Provision App Tier Instances
&lt;/h4&gt;

&lt;p&gt;Now for the backend configuration, we do something similar but with little but extremely important changes. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Role
&lt;/h5&gt;

&lt;p&gt;We also attach the same IAM role we created earlier to the backend instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;The launch template configuration is the same as earlier but the entry-script should be different as we want different commands to run in our backend instances. The backend entry-script is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/blob/main/backend_script.sh" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;This time, we create an internal load balancer(not internet facing) as we don't want internet traffic to hit our instances directly.&lt;br&gt;
This load balancer also listens on port 80&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The Autoscaling group configuration is also similar to the previous, the only difference is the subnets where the instances should be placed in is the private subnets, and the target group should be that of the internal ALB. &lt;br&gt;
The link to the backend express server configuration is &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main/backend" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can see our created web-tier and app-tier instances to serve our frontend and backend files respectively&lt;/p&gt;

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

&lt;h4&gt;
  
  
  STEP 9: Provision Database Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Database Subnet Group
&lt;/h5&gt;

&lt;p&gt;First, we create a subnet group in two of the private instances already created earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  DB Instance
&lt;/h5&gt;

&lt;p&gt;Then we create our DB instances in those DB subnet groups. We specify the name, the engine, the username and password of the default user, the security group, and the Availability Zone, (do not use Multi-AZ as that will incur costs outside the free-tier. You can deploy the DB instance in one AZ and deploy a read replica in another AZ. Data from the read replica that way, we can still achieve redundancy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 10: Run Terraform Commands
&lt;/h4&gt;

&lt;p&gt;We need to run the following command to test our configuration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform fmt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan -output=tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there are any errors, terraform will update you on these errors so you can correct them.&lt;br&gt;
After successfully provisioning the infrastructure, visit the external load balancer DNS to view the hosted website&lt;/p&gt;

&lt;p&gt;Navigate to EC2 page on AWS, and scroll the sidebar down for LoadBalancer. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtmkxwq72tqhjy35gtys.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtmkxwq72tqhjy35gtys.png" alt="Load Balancer" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the external load balancer, copy the DNS, and paste this into a browser to see the website and interact with the three-tier application. If you used my frontend code, it should look like this&lt;/p&gt;

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

&lt;p&gt;Up Next: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We configure Amazon CloudWatch and SNS to notify us when there is any change in our project and Amazon Route53 for our DNS&lt;/li&gt;
&lt;li&gt;We modularize the terraform configuration, which is a best practice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; for this Project&lt;/p&gt;

&lt;p&gt;Refs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-build" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/aws-get-started/aws-build&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=spt3Grgfvu0" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=spt3Grgfvu0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>aws</category>
      <category>infrastructureascode</category>
    </item>
    <item>
      <title>IaC Provisioning A Three-Tier Application on AWS</title>
      <dc:creator>GERALD IZUCHUKWU</dc:creator>
      <pubDate>Mon, 14 Oct 2024 10:54:53 +0000</pubDate>
      <link>https://dev.to/gerald_izuchukwu/iac-provisioning-a-three-tier-application-on-aws-2b5g</link>
      <guid>https://dev.to/gerald_izuchukwu/iac-provisioning-a-three-tier-application-on-aws-2b5g</guid>
      <description>&lt;p&gt;IaC Provisioning A Three-Tier Application on AWS&lt;/p&gt;

&lt;p&gt;IaC means Infrastructure as code and for a while I struggled to understand this concept, and when it seemed like I was starting to get it, I started confusing it with platform as code. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share. Infrastructure as code is basically provisioning or creating your resources through code or configuration files, in other words, automating the process of creating them in one click, instead of creating them singly through GUI or CLI, and Terraform is a tool that helps us to do that. &lt;/p&gt;

&lt;p&gt;Terraform is an open-source tool that efficiently helps us to provision infrastructure, it is owned by Hashicorp and it also uses its own language HCL (Hashicorp Configuration Language), for the configuration of these infrastructures. Terraform can be used for several things but since it is only an IaC provisioning tool, it also has its limitations. &lt;/p&gt;

&lt;p&gt;This write-up focuses on using Terraform to provision a three-tier application on AWS. There are a lot of three-tier apps out there, maybe more detailed than this but what I intend to do for this is to explain every concept used here, to help me understand further as it takes a while for me to grasp a concept fully and to help people like&lt;/p&gt;

&lt;p&gt;This writeup will use the following technologies&lt;/p&gt;

&lt;h4&gt;
  
  
  Network
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Subnet&lt;/li&gt;
&lt;li&gt;Route Table&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;Nat Gateway&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Compute
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;launch template&lt;/li&gt;
&lt;li&gt;Key pair&lt;/li&gt;
&lt;li&gt;Elastic Load Balancer&lt;/li&gt;
&lt;li&gt;Target Groups&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Database
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;RDS Database&lt;/li&gt;
&lt;li&gt;Subnet Groups&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Others
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;IAM Role&lt;/li&gt;
&lt;li&gt;S3 Bucket&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We are going to break this down into steps&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 1: Upload our static files and logic  code to Amazon S3 Bucket
&lt;/h3&gt;

&lt;p&gt;To do this, we need to create an S3 bucket, create two folders, and name them frontend and backend. In the frontend folder, upload all our static files as well as our nginx.conf file. In the backend folder, we upload all our logic code files&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 2: Configure AWS auth
&lt;/h3&gt;

&lt;p&gt;You can use various ways to configure AWS authentication, I will walk you through using IAM &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://https://us-east-1.console.aws.amazon.com/console/home?region=us-east-1#" rel="noopener noreferrer"&gt;AWS Management Console&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;Navigate to IAM&lt;/li&gt;
&lt;li&gt;Add User, give the user a name&lt;/li&gt;
&lt;li&gt;Attach AdminstratorAccess Policy to the user &lt;/li&gt;
&lt;li&gt;Review and create user&lt;/li&gt;
&lt;li&gt;After creating a user, select the user and navigate to security credentials, scroll down to access keys, and click create access keys&lt;/li&gt;
&lt;li&gt;Select a use case, add a description and the Access key and Secret Access key will be created, download the CSV and save it in a secure folder&lt;/li&gt;
&lt;li&gt;Now set the environment variables
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AWS_ACCESS_KEY_ID="your_access_key"
export AWS_SECRET_ACCESS_KEY="your_secret_key"
export AWS_REGION="your_region
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  STEP 3: Set up your terraform
&lt;/h3&gt;

&lt;p&gt;I won't assume that you already have Terraform installed, if you do, that's fine, if you don't follow the steps below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit &lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;Teraform Download File&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Select the download configuration for your operating system&lt;/li&gt;
&lt;li&gt;Test if the download was successful using "terraform -version"&lt;/li&gt;
&lt;li&gt;Create a folder for this terraform project, call it whatever you want. I will call mine &lt;em&gt;"three-tier-app-projects"&lt;/em&gt;
change directory into the folder &lt;code&gt;cd three-tier-app-projects&lt;/code&gt; and create a file named &lt;em&gt;main.tf&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Add the terraform block and aws provider block into the file and save
on the CLI, run the command &lt;code&gt;terraform init&lt;/code&gt; This command takes a while, so be patient
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~&amp;gt; 4.16"
    }
  }

  required_version = "&amp;gt;= 1.2.0"
}

provider "aws" {
  region  = "us-east-1"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Providers are plugins that help manage and create your resources in IaC. providers could include aws, docker, nginx, etc.&lt;br&gt;
N/B For a simple project such as this, we can simply initialize &lt;code&gt;terraform&lt;/code&gt; with only the &lt;code&gt;provider&lt;/code&gt; block, without the terraform block&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 4: Setup Network Aspect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;strong&gt;&lt;em&gt;terraform.tfvars file&lt;/em&gt;&lt;/strong&gt;. This file is used to store all our static variables&lt;/li&gt;
&lt;li&gt;Create the VPC, which is a network house that houses all our resources. Store the vpc_cidr block in the &amp;gt;terraform.tfvars, access it using the variable keyword before calling it in the aws_vpc block. Your &lt;strong&gt;&lt;em&gt;main.tf&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;terraform.tfvars&lt;/em&gt;&lt;/strong&gt; files should look like this
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;provider "aws" {
  region  = "us-east-1"
}

variable "env_prefix" {}
variable "avail_zone" {}
variable "vpc_cidr" {}

resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${var.env_prefix}_vpc"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env_prefix    = "three-tier-demo"
avail_zone    = ["us-east-1a", "us-east-1b"]
vpc_cidr      = "10.0.0.0/16"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Run the following command 
&lt;code&gt;terraform fmt&lt;/code&gt;
&lt;code&gt;terraform validate&lt;/code&gt;
&lt;code&gt;terraform plan&lt;/code&gt;
&lt;code&gt;terraform apply&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;terraform fmt formats the file, to follow a particular structure&lt;br&gt;
terraform validate checks if the configuration is syntactically valid and internally consistent &lt;br&gt;
terraform plan checks and outputs all the resources that will be added by the configuration&lt;br&gt;
terraform apply goes ahead to apply the configuration after the prompt is entered&lt;/p&gt;

&lt;p&gt;If everything goes well, a VPC named three-tier-demo_vpc will be provisioned in our us-east-1 zone&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 5: Let's finish creating our Network resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Create Subnets for Public and Private resources in two availability zones for redundancy
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_subnet" "public" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "${var.env_prefix}_public_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 2)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_private_subnet-${count.index + 1}"
  }
}

resource "aws_subnet" "db_private" {
  count                   = 2
  vpc_id                  = aws_vpc.main.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 8, count.index + 4)
  availability_zone       = var.avail_zone[count.index]
  map_public_ip_on_launch = false

  tags = {
    Name = "${var.env_prefix}_db_private_subnet-${count.index + 1}"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create Internet Gateway and Nat Gateway
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_internet_gateway" "this" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "${var.env_prefix}_igw"
  }
}
resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name = "${var.env_prefix}_nat_gateway"
  }
}

resource "aws_eip" "nat" {
  vpc = true
  tags = {
    Name = "${var.env_prefix}_eip_nat"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;N/B: NatGW and ElasticIP (eip) are paid for, there is no free tier available for this. NatGW costs &lt;strong&gt;$0.05/hr&lt;/strong&gt; and EIP costs &lt;strong&gt;$0.005/hr&lt;/strong&gt; it is not attached to an ec2 instance&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Public and Private RouteTable and associate them to a subnet
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.this.id
  }


  tags = {
    Name = "${var.env_prefix}_public_route_table"
  }
}

resource "aws_route_table_association" "public" {
  count          = 2
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public_route_table.id
}

resource "aws_route_table" "private_route_table" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.nat.id
  }

  tags = {
    Name = "${var.env_prefix}_private_route_table"
  }
}

resource "aws_route_table_association" "private" {
  count          = 2
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

resource "aws_route_table_association" "db_private" {
  count          = 2
  subnet_id      = aws_subnet.db_private[count.index].id
  route_table_id = aws_route_table.private_route_table.id
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What did we just do?&lt;br&gt;
We created a VPC for all our resources to live in with a CIDR block of &lt;strong&gt;10.0.0.0/16&lt;/strong&gt;&lt;br&gt;
We created subnets which are like smaller houses in the VPC that house one or more resources. &lt;br&gt;
The public subnets (internet-facing) have an internet gateway route configured in the route table.&lt;br&gt;
The private subnets (internal facing) have NAT gateway route configured in the route table.&lt;br&gt;
The elastic IP is for Controlled Access. With a NAT Gateway, instances in a private subnet do not have direct public IPs, enhancing security. By using an EIP with the NAT Gateway, you maintain control over how and when outbound internet access is granted without exposing private instances directly to the internet&lt;/p&gt;

&lt;h3&gt;
  
  
  STEP 6: Security
&lt;/h3&gt;

&lt;p&gt;For security, we can set security on the subnet level(NACL) or instance level(Security groups). For this project, we will be using security groups. We need to create Security Groups for the Elastic Load Balancer, the Instances, and the RDS instance. To make our solution highly secure, we allow the web tier instance to receive traffic only from the external load balancer. We also allow the app tier instances to receive HTTP traffic only from the internal load balancer. Since the web tier and app tier has to communicate, we allow the internal load balancer to allow HTTP traffic only from the web-tier instances.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create SG for external Load Balancer, that allows HTTP traffic on port 80 and HTTPS on port 443
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "externalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }

  ingress {
    from_port   = 443 // https
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [var.my_ip_address]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "01. External LoadBalancer Security Group"
  }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create SG for Web tier Instance, that allows traffic on Port 80 only from the external load balancer security group. You can add ssh security group rule too, but it's best to use the SSM ssh manager to access the instance terminal
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "webserverSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.externalLoadBalancerSG.id]

  }
    ingress {
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "02. Web Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for the internal load balancer, that allows HTTP traffic on port 80 only from the web-tier security groups
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "internalLoadBalancerSG" {
  vpc_id = aws_vpc.main.id
  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.webserverSG.id]
  }

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

  tags = {
    Name = "03. Internal Load Balancer Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Create a security group for app-tier instances that allows HTTP traffic on port 9662 (our NodeJS server port) only from the internal load balancer security group
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "appserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 9662
    to_port         = 9662
    protocol        = "tcp"
    security_groups = [aws_security_group.internalLoadBalancerSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. App Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Finally, we will create a security group for our database instances that allows inbound traffic on port 3306 (AURORA/MYSQL) port
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_security_group" "dbserverSG" {
  vpc_id = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.appserverSG.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "04. Database Server Security Group"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  STEP 7: Provision Web Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Profile
&lt;/h5&gt;

&lt;p&gt;We will need to create an IAM Profile with two roles - &lt;code&gt;S3ReadOnly&lt;/code&gt; and &lt;code&gt;SSManagerProfile&lt;/code&gt;. This profile will be attached to the instances and it will enable them to read the uploaded files on S3 as well as connect to the instance's terminal through SSMManagedInstanceCore  rather than opening port 22 for ssh-ing&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;Since we are trying to automate as much as possible and we want auto-scaling of instances, we use auto-scaling group. Auto-scaling group works with launch templates, so we create a launch template with keypair for ssh, entry-script, and attach the IAM Profile created earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;Next, we create a load balancer, we want this load balancer to be able to receive traffic from outside the VPC, so we make it internet-facing. The job of the load balancer is to distribute traffic evenly across all instances in order not to overwhelm a particular instance. For the load balancer to know the instances to evenly distribute traffic to, the instances must be added to the load balancer's target group. The load balancer also checks the health of an instance before it routes to it. The part of the ALB that is responsible for listens for incoming traffic requests, processes them, and routes them to the target group is called the &lt;code&gt;listener&lt;/code&gt;. It listens on a port, 80 or 443, and routes these traffic requests based on the rules specified.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The auto-scaling group is used to scale instances up or down based on traffic demand. For this to work, we specify the launch template, the minimum and maximum amount of instances to create, the subnets to launch these instances in, the target group to place these instances in for the ALB, as well as health check. We also set an autoscaling policy to scale up or down the instances.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STEP 8: Provision App Tier Instances
&lt;/h4&gt;

&lt;p&gt;Now for the backend configuration, we do something similar but with little but extremely important changes. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  IAM Profile
&lt;/h5&gt;

&lt;p&gt;We also attach the same IAM profile we created earlier to the backend instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Launch Template
&lt;/h5&gt;

&lt;p&gt;The launch template configuration is the same as earlier but the entry-script should be different as we want different commands to run in our backend instances&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Load Balancer
&lt;/h5&gt;

&lt;p&gt;This time, we create an internal load balancer(not internet facing) as we don't want internet traffic to hit our instances directly.&lt;br&gt;
This load balancer also listens on port 80&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Auto-Scaling Group
&lt;/h5&gt;

&lt;p&gt;The Autoscaling group configuration is also similar to the previous, the only difference is the subnets where the instances should be placed in is the private subnets, and the target group should be that of the internal ALB. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  STEP 9: Provision Database Tier Instances
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  Database Subnet Group
&lt;/h5&gt;

&lt;p&gt;First, we create a subnet group in two of the private instances already created earlier&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h5&gt;
  
  
  DB Instance
&lt;/h5&gt;

&lt;p&gt;Then we create our DB instances in those DB subnet groups. We specify the name, the engine, the username and password of the default user, the security group, the Availability Zone, (do not use Multi-AZ as that will incur costs outside the free-tier. You can deploy the DB instance in one AZ and deploy a read replica in another AZ. Data from the read replica that way, we can still achieve redundancy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Step 10: Run Terraform Commands
&lt;/h4&gt;

&lt;p&gt;We need to run the following command to test our configuration&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform fmt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform plan -output=tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform apply tf.plan&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If there are any errors, terraform will update you on these errors so you can correct them.&lt;br&gt;
After successfully provisioning the infrastructure, visit the external load balancer DNS to view the hosted website&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/Gerald-Izuchukwu/three-tier-application/tree/main" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt; for this Project&lt;/p&gt;

&lt;p&gt;Refs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code" rel="noopener noreferrer"&gt;https://developer.hashicorp.com/terraform/tutorials/azure-get-started/infrastructure-as-code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=spt3Grgfvu0" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=spt3Grgfvu0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>terraform</category>
      <category>configuration</category>
    </item>
  </channel>
</rss>
