This project documents a beginner-friendly, real-world style deployment of a React application using NGINX on an AWS EC2 instance running Amazon Linux.
The goal was to:
- Keep costs low
- Avoid over-engineering
- Learn how Linux + NGINX actually serve frontend apps
- Debug real problems instead of hiding them
This walkthrough reflects the exact path taken, including lessons learned during troubleshooting.
π§ What Youβll Learn
- How NGINX serves a static React build
- How to deploy a React app directly on an EC2 instance
- Why Single Page Applications (SPAs) need special NGINX configuration
- How to diagnose and fix common NGINX errors (500 errors, redirect loops)
- How Node.js versions affect modern frontend tooling
π° Cost & Environment
- Cloud Provider: AWS
- Instance Type: t2.micro / t3.micro
- OS: Amazon Linux 2023
- Web Server: NGINX
- Frontend: React (Vite)
- Estimated Cost: Free tier or a few dollars/month
ποΈ Architecture Overview
Browser
β
Public IP (EC2)
β
NGINX (port 80)
β
/var/www/react-app
β
React build (index.html, JS, CSS)
NGINX serves static files only. React is built first, then copied into the web root.
π Phase 1: Launch the EC2 Instance
- Launch a new EC2 instance
- Select Amazon Linux 2023
- Choose instance type:
t2.microort3.micro - Enable Auto-assign Public IP
Security Group (Inbound Rules)
| Type | Port | Source |
|---|---|---|
| SSH | 22 | Your IP |
| HTTP | 80 | 0.0.0.0/0 |
SSH is restricted. HTTP is public so the app is accessible.
Note: Do not forget to create your Key Pair and save in a location you can find later.
I used my linux desktop to connect to the instance so my key needed to be in the same directory where I was launching ssh.
If you are using an ssh tool (like Putty) you will need to upload the key into the ssh tool, so it is important to remember where you saved the key.
π Phase 2: Connect to the Instance
From your local machine:
ssh -i your-key.pem ec2-user@<PUBLIC_IP>
Amazon Linux uses the ec2-user account by default.
π§° Phase 3: Install System Dependencies
Update packages
sudo dnf update -y
Install NGINX
sudo dnf install -y nginx
sudo systemctl enable --now nginx
Verify:
sudo systemctl status nginx --no-pager
βοΈ Phase 4: Install Node.js (Temporary Session)
Modern React tools require newer Node versions.
For this project, Node was upgraded for the active shell only (not persisted across reboots).
Install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
Note: I did not use this step, as I completed this in a single sitting. I only used
exec bashto refresh the shell. This step may be necessary if you need the shell to persist to continue work later.Load nvm for the current shell:
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
Install and use Node 22:
nvm install 22
nvm use 22
Verify:
node -v
npm -v
Note: Node will revert to the system version after logout unless nvm is persisted.
βοΈ Phase 5: Create and Build the React App
Create the project
mkdir -p ~/projects
cd ~/projects
npm create vite@latest react-app -- --template react
cd react-app
npm install
Build for production
npm run build
The production-ready files are created in the dist/ directory.
π Phase 6: Create the NGINX Web Root
Create a directory for the React app:
sudo mkdir -p /var/www/react-app
sudo chown -R ec2-user:ec2-user /var/www/react-app
Copy the build output:
sudo rm -rf /var/www/react-app/*
sudo cp -r dist/* /var/www/react-app/
Fix permissions so NGINX can read files:
sudo chown -R nginx:nginx /var/www/react-app
sudo chmod -R 755 /var/www/react-app
π Phase 7: Configure NGINX for React (SPA Routing)
Create the NGINX configuration:
sudo nano /etc/nginx/conf.d/react-app.conf
Paste:
server {
listen 80 default_server;
server_name _;
root /var/www/react-app;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Disable the default config if present:
sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.disabled
Test and restart:
sudo nginx -t
sudo systemctl restart nginx
β Phase 8: Verify Deployment
From the server:
curl -I http://localhost
From your browser:
http://<EC2_PUBLIC_IP>
Your React app should now load successfully.
π§― Troubleshooting (Lessons Learned)
β 500 Internal Server Error
Cause
- NGINX root pointed to a directory that did not exist
- Missing
index.html
Fix
ls -la /var/www/react-app/index.html
Ensure the build was copied correctly.
β Infinite redirect / rewrite loop
Error
rewrite or internal redirection cycle while internally redirecting to "/index.html"
Cause
- SPA routing without proper
try_files - Conflicting server blocks
Fix
- Disable
default.conf - Use a single
serverblock - Verify
try_filesconfiguration
β Vite / crypto.hash error
Cause
- Node.js version too old
Fix
nvm use 22
rm -rf node_modules package-lock.json
npm install
π§ Final Thoughts
This project intentionally avoided:
- Load balancers
- Containers
- CI/CD pipelines
Instead, it focused on:
- Linux fundamentals
- NGINX configuration
- Real debugging scenarios
If you can deploy and troubleshoot this setup, youβre building the right foundation.
π Possible Next Steps
- Persist Node using nvm
- Add HTTPS with Letβs Encrypt
- Introduce a release + rollback structure
- Move the frontend to S3 + CloudFront
π Credits & Learning Acknowledgement
This project builds on foundational DevOps concepts learned from Pravin Mishra teaching; beginner-focused content providing an initial framework for understanding how applications move from development to deployment.
The original learning foundation came from the following resources:
-
Udemy Course -- DevOps for Beginners: Docker, Kubernetes, Cloud & CI/CD (4 Projects)
-
GitHub Repository
How This Project Differs and Expands
While the course introduced core ideas, this implementation represents my own hands-on work and problem-solving, including:
- Deploying a React application on Amazon Linux 2023
- Configuring NGINX to serve a Single Page Application (SPA)
- Troubleshooting real-world issues such as:
- Node.js version incompatibilities with modern tooling
- NGINX 500 Internal Server Errors
- SPA rewrite and redirect loops
- Adapting the deployment process to fit a low-cost, beginner-accessible AWS setup
- Documenting mistakes, fixes, and reasoning to reflect the actual learning process
All configuration choices, debugging steps, and documentation in this project reflect my own experimentation and understanding, built on top of the foundational concepts introduced in the referenced materials.
Top comments (0)