Author: Odoworitse Afari
Date: 20/1/2026
Introduction
Deploying a React application to production involves more than just running npm start. In this guide, I'll walk you through the complete process of deploying a React app on an Ubuntu server and serving it with Nginx—from initial setup to making it publicly accessible.
This isn't theory. This is a real deployment with real challenges and real solutions.
About me: I'm Odoworitse Afari, a DevOps Engineer documenting my journey building production infrastructure. Follow me on LinkedIn and GitHub for more DevOps content.
What we'll cover:
Setting up Node.js and Nginx on Ubuntu
Building a React app for production
Configuring Nginx for Single Page Application (SPA) routing
Troubleshooting common deployment issues
By the end of this guide, you'll have a live React application accessible via your server's public IP.
Prerequisites
Before starting, you'll need:
An Ubuntu server (VM, EC2, or any cloud provider)
SSH access to your server
Basic Linux command line knowledge
A React application (I'll use a sample repo from GitHub)
The Technology Stack
Server OS: Ubuntu 20.04/22.04
Runtime: Node.js & npm
Web Server: Nginx
Application: React (production build)
Step 1: Installing Node.js and npm
First, we need to install Node.js and npm on the Ubuntu server. These tools are essential for building the React application.
sudo apt update
sudo apt install -y nodejs npm
Verify the installation:
node -v && npm -v
You should see version numbers for both Node.js and npm.
Why this matters:
Node.js provides the JavaScript runtime needed to build React applications outside the browser. npm manages the project dependencies and build scripts. Without these, we can't create a production-ready build.

Node.js and npm successfully installed
Step 2: Installing and Configuring Nginx
Nginx will serve our React application's static files to users.
sudo apt install -y nginx
sudo systemctl start nginx
sudo systemctl enable nginx
Verify Nginx is running:
sudo systemctl status nginx
You should see active (running) in the output.
Why this matters:
The enable command ensures Nginx starts automatically after server reboots, preventing manual intervention during maintenance or unexpected restarts. This is critical for production reliability.

Nginx service active and enabled
Step 3: Getting the React Application
Clone the React app repository to your server:
git clone https://github.com/pravinmishraaws/my-react-app.git
cd my-react-app
Why use Git for deployment?
In professional environments, code is always pulled from version control systems. This ensures consistency across environments and provides a clear audit trail of what's deployed.

React app repository cloned successfully
Step 4: Customizing the Application
Before building, let's customize the app to prove ownership. Navigate to the source directory:
cd my-react-app/src
nano App.js
Update the relevant section with your details:
jsx
<h2>Deployed by: <strong>Your Full Name</strong></h2>
<p>Date: <strong>DD/MM/YYYY</strong></p>
Save and exit (Ctrl+O, Enter, Ctrl+X in nano).
Why this step matters:
In real deployments, applications often need environment-specific configurations. While this example uses direct code editing, production systems typically handle this through environment variables or config files.

Application customized with deployment details
Step 5: Building for Production
React applications need to be built differently for production versus development.
Install dependencies:
npm install
Create the production build:
npm run build
This creates an optimized build/ directory with minified, compressed files ready for production.
Development vs Production builds:
Development: Large file sizes, readable code, slower performance
Production: Minified code, compressed assets, optimized performance, no source maps
Production builds can be 10x smaller and significantly faster.

Production build completed successfully
Step 6: Deploying to Nginx
Now we deploy the built application to Nginx's web root directory.
Clear the default Nginx content:
sudo rm -rf /var/www/html/*
Copy the production build:
sudo cp -r build/* /var/www/html/
Set proper ownership and permissions:
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
Understanding permissions:
Nginx runs as the www-data user on Ubuntu. Files must be owned by this user for Nginx to read them. The 755 permission allows the owner to read/write/execute, while others can only read/execute.
Common mistake: Incorrect permissions lead to "403 Forbidden" errors—one of the most frequent deployment issues.

Application files successfully deployed to /var/www/html
Step 7: Configuring Nginx for React (Critical Step)
React is a Single Page Application (SPA). All routing happens on the client side. Without proper Nginx configuration, refreshing any route besides / results in a 404 error.
Replace the default Nginx configuration:
echo 'server {
listen 80;
server_name _;
root /var/www/html;
index index.html;
location / {
try_files $uri /index.html;
}
error_page 404 /index.html;
}' | sudo tee /etc/nginx/sites-available/default > /dev/null
Breaking down this configuration:
listen 80; - Nginx listens on port 80 (HTTP)
root /var/www/html; - Where to find files
try_files $uri /index.html; - This is the key line
The try_files directive tells Nginx:
First, try to serve the requested file directly
If the file doesn't exist, serve index.html instead
This allows React Router to handle navigation.
Restart Nginx to apply changes:
sudo systemctl restart nginx
Why this matters:
Most tutorials skip proper SPA configuration. Without it:
Direct URL access fails (404 error)
Page refreshes break (404 error)
Only the home page works
This is the #1 issue developers face when deploying React apps.

Nginx configured for SPA routing and restarted
Step 8: Testing the Deployment
Get your server's public IP:
curl ifconfig.me
Open your browser and navigate to:
http://[your-public-ip]
Your React application should now be live!

React app successfully deployed and accessible via public IP
Common Issues and Solutions
Issue 1: 404 Errors on Page Refresh
Symptom: Home page works, but refreshing /about shows 404.
Cause: Nginx isn't configured for SPA routing.
Solution: Ensure the try_files directive is in your Nginx config (Step 7).
Issue 2: 403 Forbidden Error
Symptom: Browser shows "403 Forbidden" when accessing the site.
Cause: Incorrect file permissions or ownership.
Solution:
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
Issue 3: Connection Refused
Symptom: Browser can't connect to the server.
Cause: Nginx isn't running, or firewall is blocking port 80.
Solution:
sudo systemctl status nginx # Check if running
sudo ufw allow 80/tcp # If using UFW firewall
What I Learned
Configuration is everything - One missing semicolon or wrong directive can break an entire deployment.
Production builds matter - Development builds are 10x larger and significantly slower. Always use npm run build for production.
SPA routing is non-obvious - Most deployment guides don't cover this properly, leading to broken apps in production.
Permissions are critical - Understanding Linux file ownership prevents hours of debugging.
Testing is mandatory - Never assume deployment worked. Always verify in a browser.
Security Considerations (For Production)
While this guide gets your app running, production deployments need additional hardening:
Use HTTPS: Set up SSL/TLS certificates (Let's Encrypt is free)
Configure firewall: Only open necessary ports
Keep software updated: Regular security patches
Use environment variables: Never hardcode secrets
Set up monitoring: Track uptime and errors
Next Steps
Now that you have a working deployment, consider:
Setting up a CI/CD pipeline to automate deployments
Configuring a custom domain instead of using IP addresses
Implementing HTTPS with Let's Encrypt
Adding monitoring and logging (PM2, CloudWatch, or similar)
Using a reverse proxy for multiple applications
Conclusion
Deploying a React app to production involves more than just copying files to a server. You need to understand:
How to build for production
How web servers serve static files
How client-side routing works
How permissions and ownership affect accessibility
This guide walked you through a complete, working deployment. The challenges you'll face in real production environments will be similar—but now you have the foundation to troubleshoot and solve them.
Key takeaway: Most deployment issues come from misconfiguration, not broken code. Understanding your infrastructure is just as important as understanding your application.
About the Author
I'm Odoworitse Afari, a DevOps Engineer focused on building scalable cloud infrastructure and automating deployment pipelines. I'm currently part of the DevOps Micro Internship (DMI) program, where I'm building real-world projects and documenting my journey.
Connect with me:
LinkedIn: linkedin.com/in/odoworitse-afari
GitHub: github.com/0dow0ri7s3
Upwork: upwork.com/freelancers/~0121603c4d11c8e4e3
I write about DevOps, cloud infrastructure, and automation. Follow me for more hands-on tutorials and project breakdowns.
If you found this guide helpful:
⭐ Star my GitHub repos
💬 Connect with me on LinkedIn
🔄 Share this article with others learning DevOps
Top comments (0)