Running Scripts on EC2 Startup: Auto-Install Nginx with User Data
Manually SSHing into a new EC2 instance to install software is a one-way ticket to configuration drift and operational toil — especially when Auto Scaling spins up instances at 3 AM. EC2 User Data solves this by letting you inject a bootstrap shell script that runs automatically on first launch.
TL;DR
| Step | Action | Where |
|---|---|---|
| 1 | Write your bootstrap shell script | Local editor |
| 2 | Paste script into User Data field | EC2 Launch Wizard → Advanced Details |
| 3 | Launch the instance | AWS Console / CLI / CloudFormation |
| 4 | Script runs as root on first boot | Inside the instance (cloud-init) |
| 5 | Verify Nginx is live | Browser → http://<public-ip> |
How EC2 User Data Works Under the Hood
User Data is not magic — it is powered by cloud-init, an industry-standard multi-distribution package that AWS pre-installs on all Amazon Linux, Ubuntu, and other official AMIs. When the instance boots for the first time, cloud-init fetches the User Data payload from the EC2 Instance Metadata Service (IMDS) at http://169.254.169.254/latest/user-data and executes it as the root user.
(169.254.169.254)" participant OS as "Instance OS" Console->>EC2API: RunInstances + UserData payload EC2API->>Hypervisor: Provision & boot instance Hypervisor->>CloudInit: systemd starts cloud-init service CloudInit->>IMDS: GET /latest/user-data IMDS-->>CloudInit: Return shell script CloudInit->>OS: Execute script as root OS-->>CloudInit: Script completes (nginx installed & running) CloudInit->>OS: Log output to /var/log/cloud-init-output.log
- EC2 Launch: You submit a RunInstances API call (Console, CLI, or IaC) with a User Data payload attached.
- Hypervisor Boot: The underlying hardware virtualizes and boots the instance kernel.
- cloud-init Starts: As part of the OS init sequence (systemd), the
cloud-initservice activates. - IMDS Fetch: cloud-init queries the link-local IMDS endpoint to retrieve the User Data script.
- Script Execution: The shell script runs as
root. All stdout/stderr is logged to/var/log/cloud-init-output.log. - Instance Ready: After the script completes, the instance is fully operational with Nginx installed and running.
Analogy: Think of User Data like the setup instructions inside a new employee's onboarding packet. The moment they walk in the door (first boot), they follow the checklist exactly once — install the right tools, configure their workstation, and get to work. You don't need to supervise them; the instructions are self-contained.
Key Behavioral Rules You Must Know
- Runs only once by default: User Data executes on the first boot only. Rebooting the instance does NOT re-run it unless you explicitly reconfigure cloud-init.
- Runs as root: No
sudoprefix is needed inside the script. - Size limit: User Data is limited to 16 KB in raw form. For larger scripts, store the script in S3 and use a small bootstrap script to download and execute it.
- Must start with a shebang: Shell scripts must begin with
#!/bin/bash(or another valid interpreter) for cloud-init to execute them correctly. - Base64 encoding: When passing User Data via the AWS CLI or SDK, the payload must be Base64-encoded. The Console handles this automatically.
Where to Paste the Script in the AWS Console
The User Data field is located inside the EC2 Launch Wizard. Follow this exact path:
- Navigate to EC2 → Instances → Launch Instances.
- Configure your AMI, instance type, key pair, and network settings as normal.
- Scroll down to the Advanced Details section and expand it.
- Locate the User data field at the bottom of that section.
- Select "As text" and paste your script directly into the text area.
- Complete the rest of the wizard and click Launch Instance.
Launch Instances"] --> B["Step 1: Choose AMI"] B --> C["Step 2: Instance Type"] C --> D["Step 3: Key Pair"] D --> E["Step 4: Network Settings"] E --> F["Advanced Details
(Expand Section)"] F --> G["📋 User Data Field
Select: As text"] G --> H["Paste your
#!/bin/bash script"] H --> I["Launch Instance"] I --> J["cloud-init executes
script on first boot"] style G fill:#ff9900,color:#000,font-weight:bold style J fill:#1a9c3e,color:#fff
The Bootstrap Scripts
Amazon Linux 2 and Amazon Linux 2023 use different package managers. Do not mix these scripts — using the wrong one will cause silent failures or errors during boot.
Option A: Amazon Linux 2
Amazon Linux 2 uses yum as its package manager. Nginx is available directly from the Amazon Linux Extras repository or the default repos.
#!/bin/bash
yum update -y
yum install nginx -y
systemctl start nginx
systemctl enable nginx
echo "<h1>Deployed via EC2 User Data</h1>" > /usr/share/nginx/html/index.html
Option B: Amazon Linux 2023
Amazon Linux 2023 (AL2023) replaced yum with dnf as the primary package manager. The yum command is not functional on AL2023. Use dnf exclusively.
#!/bin/bash
dnf update -y
dnf install nginx -y
systemctl start nginx
systemctl enable nginx
echo "<h1>Deployed via EC2 User Data</h1>" > /usr/share/nginx/html/index.html
Ubuntu (Reference)
#!/bin/bash
apt-get update -y
apt-get install nginx -y
systemctl start nginx
systemctl enable nginx
echo "<h1>Deployed via EC2 User Data</h1>" > /var/www/html/index.html
Deploying via AWS CLI
When using the CLI, User Data must be passed as a Base64-encoded string. The --user-data flag accepts a file:// reference, and the CLI handles encoding automatically when using this syntax.
🔽 [Click to expand] AWS CLI launch command
# Save your script to a file first
cat > userdata.sh <<'EOF'
#!/bin/bash
dnf update -y
dnf install nginx -y
systemctl start nginx
systemctl enable nginx
echo "<h1>Deployed via EC2 User Data</h1>" > /usr/share/nginx/html/index.html
EOF
# Launch the instance (AL2023 AMI example — replace AMI ID with a current one for your region)
aws ec2 run-instances \
--image-id ami-0abcdef1234567890 \
--instance-type t3.micro \
--key-name my-key-pair \
--security-group-ids sg-0123456789abcdef0 \
--subnet-id subnet-0123456789abcdef0 \
--user-data file://userdata.sh \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=nginx-bootstrap}]'
Infrastructure as Code: CloudFormation (Amazon Linux 2 Only)
The following CloudFormation snippet is scoped to Amazon Linux 2 AMIs only because it uses yum. If you are targeting an AL2023 AMI, replace all yum commands with dnf.
🔽 [Click to expand] CloudFormation YAML snippet (AL2)
Resources:
NginxInstance:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AmazonLinux2AMI # Must be an AL2 AMI ID
InstanceType: t3.micro
KeyName: my-key-pair
SecurityGroupIds:
- !Ref WebServerSG
SubnetId: !Ref PublicSubnet
UserData:
Fn::Base64: |
#!/bin/bash
yum update -y
yum install nginx -y
systemctl start nginx
systemctl enable nginx
echo "<h1>Deployed via EC2 User Data</h1>" > /usr/share/nginx/html/index.html
Tags:
- Key: Name
Value: nginx-al2-bootstrap
Verifying the Script Ran Successfully
After the instance reaches the running state and passes both status checks (allow 2–3 minutes), use these verification methods:
- Browser test: Navigate to
http://<EC2-Public-IP>. You should see the custom HTML page. Ensure your Security Group allows inbound TCP port 80 from your IP or0.0.0.0/0. - Check cloud-init logs: SSH into the instance and run:
sudo cat /var/log/cloud-init-output.log
This log captures all stdout and stderr from your User Data script execution. - Check service status:
sudo systemctl status nginx
Security Considerations
- Do not embed secrets in User Data. User Data is retrievable by anyone with access to the instance via IMDS (without IMDSv2 enforcement) or via the EC2 Console by any IAM principal with
ec2:DescribeInstanceAttributepermission. Use AWS Secrets Manager or AWS Systems Manager Parameter Store to inject secrets at runtime. - IMDSv2: Enforce IMDSv2 (token-based) on all instances to prevent SSRF-based metadata exfiltration. This does not affect User Data execution.
- Least privilege Security Groups: Open only port 80 (and 443 for HTTPS) inbound. Do not use
0.0.0.0/0for SSH (port 22) in production.
Glossary
| Term | Definition |
|---|---|
| User Data | An EC2 feature that allows you to pass a script or cloud-config directive to an instance at launch time, executed by cloud-init on first boot. |
| cloud-init | An open-source, multi-distribution tool pre-installed on AWS AMIs that handles early instance initialization, including executing User Data scripts. |
| IMDS (Instance Metadata Service) | A link-local HTTP endpoint (169.254.169.254) accessible only from within an EC2 instance, providing instance metadata and User Data to running processes. |
| systemctl enable | A systemd command that configures a service to start automatically on every subsequent system boot, not just the current session. |
| dnf | The package manager used on Amazon Linux 2023 (and Fedora/RHEL 8+), replacing yum. Not interchangeable with yum on AL2023. |
Next Steps
- For repeatable, version-controlled bootstrapping at scale, graduate from User Data to AWS Systems Manager State Manager or EC2 Image Builder (bake Nginx into a custom AMI).
- To re-run User Data on every boot (not just first boot), refer to the official cloud-init documentation on running commands on your Linux instance at launch.
- Official reference: AWS EC2 User Data Documentation.
Comments
Post a Comment