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

Mete ShriniwasMete Shriniwas
12 min read

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:

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 the build/ 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

  1. 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


🧱 2. Launch a New EC2 Instance

Click “Launch Instance”
Fill out the form as follows:

SettingValue
Namereact-deploy-server
Amazon Machine ImageUbuntu Server 22.04 LTS (recommended)
Instance typet2.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

TypeProtocolPort RangeSource
SSHTCP22My IP
HTTPTCP800.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

  1. Open the Nginx default config file:
sudo nano /etc/nginx/sites-available/default
  1. 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 — usually build/. If your workflow copies build/* into react-app/, this is correct.
  • index index.html;: If the user doesn't specify a file (e.g., goes to /), Nginx will serve index.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;.

    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; and log_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, serve index.html

    • =404: If even /index.html doesn’t exist, return 404

  1. Save and exit (Ctrl+O → Enter → Ctrl+X)

  2. 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.

  1. On your local machine:
cat id_rsa_github.pub
  1. Copy the entire key (starts with ssh-rsa...)

  2. SSH into your EC2:

ssh -i react-deploy-key.pem ubuntu@<EC2_PUBLIC_IP>
  1. Inside the EC2 terminal:
mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys
  1. Paste the public key into that file:

    1. 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.

    2. Do not delete the existing key.

    3. 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
      
    4. Save the file:

  • Ctrl + O → Enter to write the file

  • Ctrl + X to exit

  1. 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:

  1. Go to Settings → Secrets and Variables → Actions → New Repository Secret

  2. Add the following secrets:

NameValue
SSH_PRIVATE_KEYPaste contents of id_rsa_github (private key)
EC2_HOSTPublic IP of your EC2 (e.g., 54.12.34.56)
EC2_USERubuntu

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

  1. Go to your repository on GitHub (e.g., https://github.com/yourusername/your-repo)

  2. Click on the "Actions" tab (next to Code, Issues, Pull requests)

  3. If you haven't set up Actions yet, you'll see:

    "Get started with GitHub Actions"

  4. 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:

  1. Go to your repo → click Add file → Create new file

  2. 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:

  1. Triggers on push to main

  2. Checks out your React app

  3. Installs dependencies (npm install)

  4. Builds the production app (npm run build)

  5. Copies build/ files to ~/react-app on EC2 using scp

  6. Restarts Nginx to reflect the latest deployment


🧪 Test the CI/CD Pipeline

  1. Push a change to your main branch (e.g., change some text in App.js)

  2. Go to the Actions tab in GitHub → watch the deployment workflow

  3. 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 as ubuntu. 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

StepAction
✅ 1Create and build React app (npm run build)
✅ 2Launch EC2 instance with correct ports open
✅ 3SSH into EC2 and prepare app directory
✅ 4Generate SSH keys and set up GitHub secrets
✅ 5Add GitHub Actions workflow to auto-deploy
✅ 6Configure Nginx to serve React app
✅ 7Fix directory permissions for Nginx
✅ 8Test 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)


0
Subscribe to my newsletter

Read articles from Mete Shriniwas directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mete Shriniwas
Mete Shriniwas