Deploying a React App to AWS EC2 with GitHub Actions and Nginx: A Complete Guide

Automate your React app deployment to an AWS EC2 server using GitHub Actions and serve it with Nginx.
Introduction
Continuous Integration and Continuous Deployment (CI/CD) pipelines help automate deploying code changes, saving time and reducing errors. In this guide, we'll walk through deploying a React.js app to an AWS EC2 instance using GitHub Actions for automated deployment and Nginx as a web server.
You’ll learn:
How to set up an EC2 instance for hosting your React app
How to configure SSH keys for secure automated deployments
How to create a GitHub Actions workflow to deploy your app on push
How to configure Nginx to serve your React app properly
How to troubleshoot common issues like 404 or 500 errors
Prerequisites
Before you start deploying your React app to AWS EC2 using GitHub Actions and Nginx, make sure you have the following ready:
1. AWS Account to Create an EC2 Instance
An AWS account is required to launch and manage cloud servers (EC2 instances).
If you don’t have one, sign up at https://aws.amazon.com/.
AWS Free Tier offers free usage of t2.micro instances for 12 months, perfect for small projects and learning.
2. GitHub Repository Containing Your React App
Your React app’s source code should be hosted in a GitHub repository.
If you don’t have a repository yet:
- Create a GitHub account at https://github.com/.
3. Basic Familiarity with Linux Commands and SSH
You should be comfortable using:
Basic Linux terminal commands such as
cd
,ls
,mkdir
,chmod
.SSH (Secure Shell) for remotely connecting to servers (
ssh
command).
These skills are essential for managing your EC2 instance, configuring the environment, and troubleshooting deployment issues.
Step 1: Create and Build Your React App
A. Create the React App (Locally)
Start with your React app ready to deploy. If you haven't created one yet, use:
npx create-react-app my-profile
cd my-profile
npm run build
npx create-react-app my-profile
– scaffolds the React app.npm run build
– creates a production-ready build in thebuild/
folder (static HTML, CSS, JS).
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>Hi, I am Shriniwas</h1>
</header>
</div>
);
}
export default App;
You can test it locally:
npx serve -s build
B. Push to GitHub
- Initialize git and push the project:
git init
git remote add origin https://github.com/yourusername/my-react-app.git
git add .
git commit -m "Initial React app"
git push -u origin main
📁 Directory Structure (After Build)
my-react-app/
├── build/ <-- This is what we will deploy to EC2
├── public/
├── src/
├── package.json
├── ...
Step 2: Launch an AWS EC2 Instance
🌐 1. Log into AWS Console
Make sure you're in the correct AWS region (e.g.,
us-east-1
)
🧱 2. Launch a New EC2 Instance
Click “Launch Instance”
Fill out the form as follows:
Setting | Value |
Name | react-deploy-server |
Amazon Machine Image | Ubuntu Server 22.04 LTS (recommended) |
Instance type | t2.micro (Free Tier eligible) |
Key pair (login) | Create new or use existing |
🔑 If creating a new Key Pair:
Name:
react-deploy-key
Type: RSA
File format:
.pem
Click “Create key pair”
Save the
.pem
file safely (on local)
🔒 3. Configure Network Settings (Security Group)
Under “Network Settings”:
Click Edit to configure inbound rules:
Allow SSH (Port 22) – from your IP (for security)
Allow HTTP (Port 80) – from Anywhere
Type | Protocol | Port Range | Source |
SSH | TCP | 22 | My IP |
HTTP | TCP | 80 | 0.0.0.0/0 |
This lets you SSH into the server and access your app via the browse
🚀 4. Launch the Instance
Click “Launch instance”
Wait a minute or two → your instance will be ready.
🌐 5. Get Your EC2 Public IP
Go to Instances
Click your instance name
Find the Public IPv4 address (e.g.,
54.12.34.56
)
Copy this — you'll need it for:
SSH access
GitHub Actions secrets
Browser access to the deployed app
STEP 3: Connect to EC2 & Set Up the Server
🧩 A. Connect to EC2 via SSH
On your local machine, open a terminal and run where downloaded .pem
file exists:
chmod 400 react-deploy-key.pem
ssh -i react-deploy-key.pem ubuntu@<YOUR_EC2_PUBLIC_IP>
Replace <YOUR_EC2_PUBLIC_IP>
with the actual IP address, for example:
ssh -i react-deploy-key.pem ubuntu@54.12.34.56
🧰 B. Update & Install Required Packages
Once connected to your EC2 server:
# Update packages
sudo apt update
# Install Nginx
sudo apt install nginx -y
# Start Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
🌐 C. Verify Nginx Is Running
On your browser, go to:
http://<YOUR_EC2_PUBLIC_IP>
You should see the default Nginx welcome page.
✅ This confirms Nginx is working and reachable over the internet.
📂 D. Create a Directory to Host the React App
Let’s create a folder where GitHub Actions will deploy your app:
# Create a folder for deployment
mkdir ~/react-app
# Give proper permissions
chmod 755 ~/react-app
💡 Why This Folder?
This folder:
Will be the target directory where GitHub Actions copies your React app build files via SSH.
Is set as the root directory in your Nginx config to serve the React app (
/home/ubuntu/react-app
).
So it's important that:
GitHub Actions has write access here
Nginx is pointed to serve files from this location
🛠️ E. Configure Nginx to Serve React App
- Open the Nginx default config file:
sudo nano /etc/nginx/sites-available/default
- Replace its content with the following:
server {
listen 80;
listen [::]:80;
root /home/ubuntu/react-app;
index index.html;
server_name _;
location ~ /\.(?!well-known) {
deny all;
access_log off;
log_not_found off;
}
location / {
try_files $uri $uri/ /index.html =404;
}
}
🔍 Nginx Config Explained
server {
listen 80;
listen [::]:80;
listen 80;
: Tells Nginx to listen for HTTP traffic on port 80 (standard web port).listen [::]:80;
: Same as above, but for IPv6 connections.
root /home/ubuntu/react-app;
index index.html;
root
: Sets the base directory for this site. All static files will be looked for here.- For a React app, it should point to the folder containing
index.html
— usuallybuild/
. If your workflow copiesbuild/*
intoreact-app/
, this is correct.
- For a React app, it should point to the folder containing
index index.html;
: If the user doesn't specify a file (e.g., goes to/
), Nginx will serveindex.html
by default.
server_name _;
server_name _;
: Matches any hostname (wildcard). Common placeholder when you don’t have a domain name yet.- In production, you'd use something like
server_name
example.com
;
.
- In production, you'd use something like
location ~ /\.(?!well-known) {
deny all;
access_log off;
log_not_found off;
}
This denies access to hidden files, like
.env
,.git
,.htaccess
, etc.~
means a regex match.\.
: Any file that starts with a dot.(?!well-known)
: Exclude.well-known/
folder (used for SSL validation).deny all;
: Prevents access for security.access_log off;
andlog_not_found off;
: Clean logs, don’t log these blocked requests.
location / {
try_files $uri $uri/ /index.html =404;
}
This is critical for React apps (or any SPA):
location / {}
: This block handles all root and sub-path requests (like/
,/about
,/contact
, etc).try_files
:$uri
: Try serving the file if it exists (like/static/js/main.js
)$uri/
: Try it as a folder/index.html
: If none match, serveindex.html
=404
: If even/index.html
doesn’t exist, return 404
Save and exit (Ctrl+O → Enter → Ctrl+X)
Restart Nginx:
sudo systemctl restart nginx
✅ Nginx is now ready to serve files from ~/react-app
STEP 4: Set Up SSH Key-Based Access for GitHub Actions
This step will allow GitHub Actions to connect securely to your EC2 instance using SSH — without a password.
🔐 A. Generate SSH Key Pair (for GitHub Actions)
On your local machine (NOT EC2):
ssh-keygen -t rsa -b 4096 -C "github-actions-deploy"
When prompted for file path, type:
Enter file in which to save the key (/your/home/.ssh/id_rsa): id_rsa_github
When asked for a passphrase, leave it empty (just press Enter).
You’ll now have two files:
id_rsa_github
– the private key (used by GitHub Actions)id_rsa_
github.pub
– the public key (goes on the EC2 server)
📤 B. Copy Public Key to EC2 Instance
This allows the GitHub Actions runner to SSH into your server.
- On your local machine:
cat id_rsa_github.pub
Copy the entire key (starts with
ssh-rsa
...)SSH into your EC2:
ssh -i react-deploy-key.pem ubuntu@<EC2_PUBLIC_IP>
- Inside the EC2 terminal:
mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys
Paste the public key into that file:
if you're seeing text that looks like a key (starts with
ssh-rsa
and a long string), it means the EC2 instance already has a public key in~/.ssh/authorized_keys
.Do not delete the existing key.
Move to a new line at the end, and paste the content of
id_rsa_
github.pub
(from your local machine).Example — After pasting, it might look like this:
ssh-rsa AAAAB3...== existing-key-for-your-pem ssh-rsa AAAAB3...== new-key-for-github-actions
Save the file:
Ctrl + O → Enter to write the file
Ctrl + X to exit
- Set permissions:
chmod 600 ~/.ssh/authorized_keys
✅ Now, EC2 will trust SSH connections made using the private key.
🔒 C. Add Secrets to GitHub Repository
In your GitHub repo:
Go to Settings → Secrets and Variables → Actions → New Repository Secret
Add the following secrets:
Name | Value |
SSH_PRIVATE_KEY | Paste contents of id_rsa_github (private key) |
EC2_HOST | Public IP of your EC2 (e.g., 54.12.34.56 ) |
EC2_USER | ubuntu |
To get private key content:
cat id_rsa_github
✅ Now GitHub can SSH into your EC2 securely using these secrets.
STEP 6: Create GitHub Actions Workflow
📄 Create This File in Your Repo:
Path: .github/workflows/deploy.yml
Option 1: From Your Repository’s Actions Tab
Go to your repository on GitHub (e.g.,
https://github.com/yourusername/your-repo
)Click on the "Actions" tab (next to Code, Issues, Pull requests)
If you haven't set up Actions yet, you'll see:
"Get started with GitHub Actions"
Click “Set up a workflow yourself” (usually at the bottom)
That opens the .github/workflows/deploy.yml
file in the browser where you can paste your workflow.
🧭 Option 2: Create the Workflow File Manually in the GitHub UI
If the Actions tab isn't showing the "new workflow" screen (possibly due to an existing config), you can still create the file like this:
Go to your repo → click Add file → Create new file
Name the file:
.github/workflows/deploy.yml
🛠️ deploy.yml
(Full CI/CD Workflow)
name: Deploy React App to EC2
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Build React app
run: npm run build
- name: Copy build files to EC2
uses: appleboy/scp-action@v0.1.3
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "build/*"
target: "/home/ubuntu/react-app"
- name: Restart Nginx on EC2
uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
sudo systemctl restart nginx
✅ What This Does:
Triggers on push to
main
Checks out your React app
Installs dependencies (
npm install
)Builds the production app (
npm run build
)Copies
build/
files to~/react-app
on EC2 usingscp
Restarts Nginx to reflect the latest deployment
🧪 Test the CI/CD Pipeline
Push a change to your
main
branch (e.g., change some text inApp.js
)Go to the Actions tab in GitHub → watch the deployment workflow
After it completes, visit:
http://<your-EC2-public-IP>
Step 7: Fix Directory Permissions for Nginx
Nginx runs as www-data
and needs permission to access your app folder.
Set permissions:
sudo chmod o+x /home/ubuntu
sudo chmod -R o+r /home/ubuntu/react-app
🧩 Debugging Nginx 404 / 500 Errors: Real Fix Example
After deploying, you might get:
curl http://localhost
# Or from browser: http://<your-ec2-ip>
❌ 500 Internal Server Error or 404 Not Found?
Let’s debug it together. Here's what we saw during real troubleshooting:
ls -l /home/ubuntu/react-app/build/index.html
# Output: -rw-r--r-- 1 ubuntu ubuntu 644 Jul 11 19:49 /home/ubuntu/react-app/build/index.html
Looks fine! The file exists and is readable.
But check this:
ls -ld /home/ubuntu /home/ubuntu/react-app /home/ubuntu/react-app/build
Output:
drwxr-x--- 6 ubuntu ubuntu 4096 Jul 11 19:51 /home/ubuntu
drwxr-xr-x 3 ubuntu ubuntu 4096 Jul 11 19:32 /home/ubuntu/react-app
drwxr-xr-x 3 ubuntu ubuntu 4096 Jul 11 19:49 /home/ubuntu/react-app/build
💡 Problem: The /home/ubuntu
directory is missing "x" (execute) permission for others.
Nginx runs as
www-data
, not asubuntu
. Without execute permission, it can’t “enter” that directory.
✅ Solution:
Grant execute permission to allow traversal:
sudo chmod o+x /home/ubuntu
Now the permission becomes:
drwxr-x--x 6 ubuntu ubuntu 4096 Jul 11 19:51 /home/ubuntu
Reload Nginx:
sudo systemctl reload nginx
Test again:
curl -I http://localhost
curl -I http://<your-ec2-ip>
🎉 Now your React app should load properly in the browser!
🧪 Final Testing
Push changes to
main
→ GitHub Actions runs → App deployed.Visit
http://<your-ec2-ip>
in a browser.You should see your React app live.
📚 Summary
Step | Action |
✅ 1 | Create and build React app (npm run build ) |
✅ 2 | Launch EC2 instance with correct ports open |
✅ 3 | SSH into EC2 and prepare app directory |
✅ 4 | Generate SSH keys and set up GitHub secrets |
✅ 5 | Add GitHub Actions workflow to auto-deploy |
✅ 6 | Configure Nginx to serve React app |
✅ 7 | Fix directory permissions for Nginx |
✅ 8 | Test from browser using EC2 public IP |
🎯 Conclusion
You’ve now built a production-ready CI/CD pipeline for deploying a React app to AWS EC2 with GitHub Actions and Nginx.
🔐 Secure (SSH + secrets)
⚡ Fast (auto-deploy on push)
🧩 Debug-friendly (logs + permissions)
🚀 Scalable (built on EC2 + Nginx)
Subscribe to my newsletter
Read articles from Mete Shriniwas directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
