# 📘  Terraform Series – Day 8

Automating AWS EC2 Setup with Terraform and `user_data`

Welcome back to our Terraform journey. In infrastructure as code, setting up a server is just the beginning. After your EC2 instance is running, you need to set it up, install what it needs, and start your apps. Doing this by hand goes against the idea of automation.

In this post, we will demonstrate how to completely automate your server bootstrapping process using Terraform and the AWS user\_data feature.

### 🎯 Objective

By the end of this guide, you will learn how to automatically install and configure an Nginx web server on a newly provisioned AWS EC2 instance using a Terraform `user_data` script.

### 🧩 Step 1: Understanding the Power of `user_data`

**The Problem with Manual Configuration**

Imagine you just used Terraform to spin up a fresh EC2 instance. Without an automation script, your next steps would look like this:

1.  SSH into the instance.
    
2.  Manually run package updates.
    
3.  Install Nginx.
    
4.  Start the service.
    
5.  Create a custom HTML page.
    

This approach is **<mark class="bg-yellow-200 dark:bg-yellow-500/30">time-consuming</mark>**<mark class="bg-yellow-200 dark:bg-yellow-500/30">, </mark> **<mark class="bg-yellow-200 dark:bg-yellow-500/30">prone to human error</mark>**, and most importantly, **<mark class="bg-yellow-200 dark:bg-yellow-500/30">not scalable</mark>**<mark class="bg-yellow-200 dark:bg-yellow-500/30">.</mark> If you need to spin up 100 web servers behind a load balancer, logging into each one manually is impossible.

### The Solution: Bootstrap Scripts

AWS provides a feature called `user_data` that allows you to pass a script to your instance at launch.

*   ✔️ **Runs automatically** the very first time the instance boots.
    
*   ✔️ **Fully automates** software installation and configuration.
    
*   ✔️ **Scales infinitely** across as many instances as you deploy.
    

**In short:** `user_data` is your EC2 bootstrapping engine.

### 📄 Step 2: Create the Bootstrapping Script ([`nginx.sh`](http://nginx.sh))

First, we need to define the commands we want our server to run on startup. We will create a simple bash script that installs Nginx and creates a custom landing page.

**Create a new file named** [`nginx.sh`](http://nginx.sh)**:3**

```shell
touch nginx.sh
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/0670068c-a5ae-4fac-9025-ae97babb83a4.png align="center")

Add the following content to the file:-

```shell
#!/bin/bash

# Update package lists
sudo apt-get update

# Install Nginx silently (-y prevents the prompt)
sudo apt-get install nginx -y

# Start the Nginx service
sudo systemctl start nginx

# Enable Nginx to start automatically if the server reboots
sudo systemctl enable nginx

# Create a custom HTML landing page
echo "<h1> Terraform testing with scripting </h1>" > /var/www/html/index.html
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/b4762218-23fb-4e3e-8a04-b44cfc7d2e7e.png align="center")

### 🔗 Step 3: Attach the Script in Terraform

Now, we need to tell Terraform to pass this script to our EC2 instance during creation. We do this by utilizing the `file()` function within the `user_data` argument of our `aws_instance` resource.

O**pen your** [`ec2.tf`](http://ec2.tf) file and configure your instance block:-

```shell
resource "aws_instance" "my_instance" {
  ami           = var.ec2_ami_id
  instance_type = var.ec2_instance_type

  # Attach your SSH key pair
  key_name = aws_key_pair.my_key.key_name

  # Attach the security group (make sure port 80 is open!)
  vpc_security_group_ids = [aws_security_group.my_groups.id]

  # Inject the bootstrap script here
  user_data = file("nginx.sh")

  # Define root storage
  root_block_device {
    volume_size = var.ec2_root_storage_size
    volume_type = "gp3"
  }

  tags = {
    Name = "terraform-ec2-nginx"
  }
}
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Note: Using <code>file("</code><a target="_self" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="http://nginx.sh" style="pointer-events: none;"><code>nginx.sh</code></a><code>")</code> keeps your Terraform code clean by separating the bash logic from the HCL infrastructure definitions.</div>
</div>

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/7634a350-209b-4297-a02a-08aadbd0ff75.png align="center")

### ⚙️ Step 4: Execute the Pipeline

With the script created and Terraform configured, it's time to deploy. Run the following commands in your terminal:

Bash

```plaintext
terraform init
terraform apply -auto-approve
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/e8c3cb46-5109-4f25-a62d-c03461ef3ed7.png align="center")

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/e8221ec0-5081-4de6-a233-8a42dc4a22f9.png align="center")

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/07eeff41-0c6b-42fd-acca-b2348385ce79.png align="center")

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/f6684dfd-0b58-48a1-89e4-5b441c90927e.png align="center")

### What happens internally?

Once you hit apply, an elegant automated workflow kicks off:

1.  **Infrastructure Provisioned:** Terraform calls the AWS API to launch a new EC2 instance.
    
2.  **Script Passed:** The contents of [`nginx.sh`](http://nginx.sh) are passed to the instance metadata.
    
3.  **Bootstrapping Execution:** As the EC2 instance boots up, the OS executes the script as the `root` user.
    
4.  **App Deployed:** Packages are updated, Nginx is installed, the service is started, and your custom HTML page is generated.
    

Within minutes, you can grab the public IP of your new EC2 instance, paste it into your browser, and see your custom HTML page—zero SSH required.

### <mark class="bg-yellow-200 dark:bg-yellow-500/30">🧪 Testing: Verify NGINX on EC2 Instance</mark>

After provisioning the EC2 instance using **Terraform**, we need to test whether **NGINX is properly installed and running**.

### 🔹 Step 1: Connect to EC2 via SSH

```shell
ssh -i "your-key.pem" ubuntu@<EC2-PUBLIC-IP>
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/8c3eb8a8-a3c5-4bee-9ef7-0ffc66afec7b.png align="center")

✔ Replace:

*   `your-key.pem` → your private key
    
*   `<EC2-PUBLIC-IP>` → instance public IP
    

### 🔹 Step 2: Check NGINX Status

```shell
sudo systemctl status nginx
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/9f427d6c-204c-43e2-9efd-10dad93d2728.png align="center")

✔ Expected Output:

*   `active (running)` → ✅ NGINX is working
    
*   `inactive / failed` → ❌ issue needs fixing
    

### 🔹 Step 3: Test via Browser

Open your browser and hit:

```shell
http://<EC2-PUBLIC-IP>
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/619516b2-c1aa-48a3-9402-a3503de4e255.png align="center")

✔ Expected:

*   Default **NGINX Welcome Page**
    

### 🔹 Step 5: Test via Curl (CLI Testing)

```plaintext
curl http://localhost
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/d93d1b5b-90a8-4868-96d5-2022836b4e65.png align="center")

OR from your local system:

```plaintext
curl http://<EC2-PUBLIC-IP>
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/012ae3eb-d89e-4c62-b350-e0105478f032.png align="center")

✔ If HTML response comes → ✅ Server is working

### 🔹 Step 6: Check Port 80 (Important for DevOps)

```plaintext
sudo netstat -tulpn | grep :80
```

OR

```plaintext
sudo ss -tulpn | grep :80
```

![](https://cdn.hashnode.com/uploads/covers/685cdc0d5ca95e55fac3ab09/cbf8c0a4-7034-4ca3-991d-02050306b8fc.png align="center")

✔ Confirms:

*   NGINX is listening on port 80
    

### 💡 Key Takeaways

*   **No More Manual SSH:** Bootstrapping completely eliminates the need to manually configure infrastructure after it is provisioned.
    
*   **Separation of Concerns:** By using the `file()` function, you keep your shell scripts separate from your Terraform code, making both easier to maintain.
    
*   **Idempotency and Scale:** A script guarantees that every server you provision will be configured exactly the same way, every single time.
    

### **👨‍💻 About the Author**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1751797710818/123a7231-3dca-4273-ad68-7bd026f69b95.png?auto=compress,format&format=webp&auto=compress,format&format=webp&auto=compress,format&format=webp&auto=compress,format&format=webp&auto=compress,format&format=webp align="center")

“A complete Terraform series covering everything from fundamentals to advanced real-world infrastructure automation in a DevOps environment.”

### **📬 Let's Stay Connected**

*   📧 **Email**: [**gujjarapurv181@gmail.com**](mailto:gujjarapurv181@gmail.com)
    
*   🐙 **GitHub**: [**github.com/ApurvGujjar07**](http://github.com/ApurvGujjar07)
    
*   💼 **LinkedIn**: [**linkedin.com/in/apurv-gujjar**](http://linkedin.com/in/apurv-gujjar)
