Report this

What is the reason for this report?

How To Deploy a React Application with Nginx on Ubuntu

Updated on October 6, 2025
Anish Singh Walia

By Anish Singh Walia

Sr Technical Writer

How To Deploy a React Application with Nginx on Ubuntu

Introduction

You can quickly deploy React applications to a server using the default Create React App build tool. The build script compiles the application into a single directory containing all of the JavaScript code, images, styles, and HTML files. With the assets in a single location, you can deploy to a web server with minimal configuration.

In this comprehensive tutorial, you’ll deploy a React application on your local machine to an Ubuntu server running Nginx. You’ll build an application using Create React App, configure Nginx for optimal performance, set up reverse proxy configurations, implement SSL certificates, and learn production optimization techniques. We’ll also cover modern deployment strategies including CI/CD automation and containerization approaches that work across Ubuntu.

Key Takeaways

Before diving into the technical implementation, here are the essential concepts you’ll master:

  • Production Build Optimization: Learn how to create optimized React builds with proper asset compression and caching strategies
  • Nginx Configuration Mastery: Understand server blocks, location directives, and reverse proxy setup for React applications
  • SSL/TLS Security: Implement Let’s Encrypt certificates and configure HTTPS redirects for secure deployments
  • Performance Tuning: Configure gzip compression, browser caching, and static asset optimization for faster load times
  • CI/CD Integration: Set up automated deployment pipelines using GitHub Actions or GitLab CI for streamlined updates
  • Container Deployment: Explore Docker-based deployment strategies for scalable, portable React applications
  • Monitoring & Debugging: Implement logging, error tracking, and performance monitoring for production environments

Prerequisites

This tutorial is compatible with Ubuntu 18.04, 20.04, 22.04, 24.04, and 25.04. If you are using Ubuntu version 16.04 or below, we recommend you upgrade to a more recent version since Ubuntu no longer provides support for these versions. You can refer to this official Ubuntu LTS Upgrade Guide for more information.

To follow this tutorial, you will need:

Step 1 — Creating a React Project

In this step, you’ll create an application using Create React App and build a deployable version of the boilerplate app with production optimizations.

To start, create a new application using Create React App in your local environment. In a terminal, run the command to build an application. In this tutorial, the project will be called react-deploy:

  1. npx create-react-app react-deploy

The npx command will run a Node package without downloading it to your machine. The create-react-app script will install all of the dependencies needed for your React app and will build a base project in the react-deploy directory. For more on Create React App, check out out the tutorial How To Set Up a React Project with Create React App.

Modern React Development: While Create React App is excellent for learning, consider using Vite for new projects as it offers faster build times and better development experience. For this tutorial, we’ll use Create React App for its simplicity and widespread adoption.

The code will run for a few minutes as it downloads and installs the dependencies. When it is complete, you will receive a success message. Your version may be slightly different if you use yarn instead of npm:

Output
Success! Created react-deploy at your_file_path/react-deploy Inside that directory, you can run several commands: npm start Starts the development server. npm build Bundles the app into static files for production. npm test Starts the test runner. npm eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd react-deploy npm start Happy hacking!

Following the suggestion in the output, first move into the project folder:

  1. cd react-deploy

Now that you have a base project, run it locally to test how it will appear on the server. Run the project using the npm start script:

  1. npm start

When the command runs, you’ll receive output with the local server info:

Output
Compiled successfully! You can now view react-deploy in the browser. Local: `http://localhost:3000` On Your Network: `http://192.168.1.110:3000` Note that the development build is not optimized. To create a production build, use npm build.

Open a browser and navigate to http://localhost:3000. You will be able to access the boilerplate React app:

React project template running locally

Stop the project by entering either CTRL+C or ⌘+C in a terminal.

Now that you have a project that runs successfully in a browser, you need to create a production build. Run the create-react-app build script with the following:

  1. npm run build

This command will compile the JavaScript and assets into the build directory. When the command finishes, you will receive some output with data about your build. Notice that the filenames include a hash, so your output will be slightly different:

Output
Creating an optimized production build... Compiled successfully. File sizes after gzip: 41.21 KB build/static/js/2.82f639e7.chunk.js 1.4 KB build/static/js/3.9fbaa076.chunk.js 1.17 KB build/static/js/runtime-main.1caef30b.js 593 B build/static/js/main.e8c17c7d.chunk.js 546 B build/static/css/main.ab7136cd.chunk.css The project was built assuming it is hosted at /. You can control this with the homepage field in your package.json. The build folder is ready to be deployed. You may serve it with a static server: serve -s build Find out more about deployment here: https://cra.link/deployment

The build directory will now include compiled and minified versions of all the files you need for your project. At this point, you don’t need to worry about anything outside of the build directory. All you need to do is deploy the directory to a server.

In this step, you created a new React application. You verified that the application runs locally and you built a production version using the Create React App build script. In the next step, you’ll log onto your server to learn where to copy the build directory.

Step 2 — Determining Deployment File Location on your Ubuntu Server

In this step, you’ll start to deploy your React application to a server. But before you can upload the files, you’ll need to determine the correct file location on your deployment server. This tutorial uses Nginx as a web server, but the approach is the same with Apache (Ubuntu 22.04 / Ubuntu 20.04 / Ubuntu 18.04). The main difference is that the configuration files will be in a different directory.

To find the directory the web server will use as the root for your project, log in to your server using ssh:

  1. ssh username@server_ip

Once on the server, look for your web server configuration in /etc/nginx/sites-enabled. There is also a directory called sites-allowed; this directory includes configurations that are not necessarily activated. Once you find the configuration file, display the output in your terminal with the following command:

  1. cat /etc/nginx/sites-enabled/your_domain

If your site has no HTTPS certificate, you will receive a result similar to this:

Output
server { listen 80; listen [::]:80; root /var/www/your_domain/html; index index.html index.htm index.nginx-debian.html; server_name your_domain www.your_domain; location / { try_files $uri $uri/ =404; } }

If you followed the Let’s Encrypt prerequisite to secure your Ubuntu server, you will receive this output:

Output
server { root /var/www/your_domain/html; index index.html index.htm index.nginx-debian.html; server_name your_domain www.your_domain; location / { try_files $uri $uri/ =404; } listen [::]:443 ssl ipv6only=on; # managed by Certbot listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { if ($host = www.your_domain) { return 301 https://$host$request_uri; } # managed by Certbot if ($host = your_domain) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; listen [::]:80; server_name your_domain www.your_domain; return 404; # managed by Certbot }

In either case, the most important field for deploying your React app is root. This points HTTP requests to the /var/www/your_domain/html directory. That means you will copy your files to that location. In the next line, you can see that Nginx will look for an index.html file. If you look in your local build directory, you will see an index.html file that will serve as the main entry point.

Log off the Ubuntu server and go back to your local development environment.

Now that you know the file location that Nginx will serve, you can upload your build.

Step 3 — Uploading Build Files with scp

At this point, your build files are ready to go. All you need to do is copy them to the server. A quick way to do this is to use scp to copy your files to the correct location. The scp command is a secure way to copy files to a remote server from a terminal. The command uses your ssh key if it is configured. Otherwise, you will be prompted for a username and password.

The command format will be scp files_to_copy username@server_ip:path_on_server. The first argument will be the files you want to copy. In this case, you are copying all of the files in the build directory. The second argument is a combination of your credentials and the destination path. The destination path will be the same as the root in your Nginx config: /var/www/your_domain/html.

Copy all the build files using the * wildcard to /var/www/your_domain/html:

  1. scp -r ./build/* username@server_ip:/var/www/your_domain/html

When you run the command, you will receive output showing that your files are uploaded. Your results will be slightly different:

Output
asset-manifest.json 100% 1092 22.0KB/s 00:00 favicon.ico 100% 3870 80.5KB/s 00:00 index.html 100% 3032 61.1KB/s 00:00 logo192.png 100% 5347 59.9KB/s 00:00 logo512.png 100% 9664 69.5KB/s 00:00 manifest.json 100% 492 10.4KB/s 00:00 robots.txt 100% 67 1.0KB/s 00:00 main.ab7136cd.chunk.css 100% 943 20.8KB/s 00:00 main.ab7136cd.chunk.css.map 100% 1490 31.2KB/s 00:00 runtime-main.1caef30b.js.map 100% 12KB 90.3KB/s 00:00 3.9fbaa076.chunk.js 100% 3561 67.2KB/s 00:00 2.82f639e7.chunk.js.map 100% 313KB 156.1KB/s 00:02 runtime-main.1caef30b.js 100% 2372 45.8KB/s 00:00 main.e8c17c7d.chunk.js.map 100% 2436 50.9KB/s 00:00 3.9fbaa076.chunk.js.map 100% 7690 146.7KB/s 00:00 2.82f639e7.chunk.js 100% 128KB 226.5KB/s 00:00 2.82f639e7.chunk.js.LICENSE.txt 100% 1043 21.6KB/s 00:00 main.e8c17c7d.chunk.js 100% 1045 21.7KB/s 00:00 logo.103b5fa1.svg 100% 2671 56.8KB/s 00:00

When the command completes, you are finished. Since a React project is built of static files that only need a browser, you don’t have to configure any further server-side language. Open a browser and navigate to your domain name. When you do, you will find your React project:

Browser with React Project on Server

In this step, you deployed a React application to a server. You learned how to identify the root web directory on your server and you copied the files with scp. When the files finished uploading, you were able to view your project in a web browser.

Step 4 — How To Configure Nginx for Production Optimization

While your React app is now accessible, the default Nginx configuration isn’t optimized for production React applications. In this step, you’ll configure Nginx for better performance, security, and React Router compatibility.

Understanding React Router and Nginx Configuration

React applications using client-side routing (React Router) require special Nginx configuration. When users navigate to routes like /about or /contact, the browser requests these paths from the server, but Nginx doesn’t know about these client-side routes and returns a 404 error.

The solution is to configure Nginx to serve the index.html file for all routes that don’t correspond to actual files. This is called the “fallback” configuration.

How To Create an Optimized Nginx Configuration

Create a new Nginx configuration file specifically for your React application:

  1. sudo nano /etc/nginx/sites-available/your_domain

Replace the existing configuration with this optimized version:

server {
    listen 80;
    listen [::]:80;
    server_name your_domain www.your_domain;
    
    root /var/www/your_domain/html;
    index index.html;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied expired no-cache no-store private must-revalidate auth;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;
    
    # Static assets caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }
    
    # React Router fallback - this is crucial for SPAs
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # API proxy (if you have a backend API)
    location /api/ {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

In a nutshell, the configuration does the following:

  • try_files $uri $uri/ /index.html: This is the React Router fallback. It first tries to serve the requested file, then the directory, and finally falls back to index.html for client-side routing.
  • Gzip compression: Reduces file sizes by 60-80%, significantly improving load times.
  • Static asset caching: Sets long-term caching for static files with immutable cache control.
  • Security headers: Protects against common web vulnerabilities.
  • API proxy: Routes /api/ requests to your backend server (if applicable).

Understanding the Configuration Components

This Nginx configuration is specifically optimized for React applications. Let’s break down each section to understand what it does and why it’s important:

1. Basic Server Configuration

listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
root /var/www/your_domain/html;
index index.html;
  • Dual listening: Handles both IPv4 (listen 80) and IPv6 (listen [::]:80) connections for maximum compatibility
  • Domain handling: Serves both your-domain.com and www.your-domain.com from the same configuration
  • Document root: Points to where your React build files are located
  • Default file: Serves index.html when no specific file is requested

2. Security Headers

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

These headers protect against common web vulnerabilities:

  • X-Frame-Options: Prevents clickjacking attacks by controlling if your site can be embedded in frames
  • X-Content-Type-Options: Prevents MIME type sniffing attacks that could execute malicious code
  • X-XSS-Protection: Enables the browser’s built-in XSS (Cross-Site Scripting) protection
  • Referrer-Policy: Controls how much referrer information is sent with requests, protecting user privacy

3. Gzip Compression

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/javascript
    application/xml+rss
    application/json;

This section provides significant performance improvements:

  • File size reduction: Compresses text-based files by 60-80%, dramatically reducing bandwidth usage
  • Faster load times: Especially important for mobile users and slower connections
  • Smart compression: Only compresses files larger than 1KB to avoid CPU overhead on small files
  • Targeted compression: Focuses on file types that benefit most from compression (CSS, JS, JSON)

4. Static Asset Caching

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

This configuration optimizes caching for static assets:

  • Long-term caching: Static assets are cached for 1 year, reducing repeat downloads
  • Immutable cache: The immutable directive tells browsers the file won’t change (safe because React adds hashes to filenames like main.abc123.js)
  • Reduced server load: access_log off prevents logging of static asset requests, reducing I/O overhead
  • Faster repeat visits: Users don’t need to re-download unchanged files

5. React Router Fallback (Critical for SPAs)

location / {
    try_files $uri $uri/ /index.html;
}

This is the most important configuration for React applications:

  • Client-side routing support: Essential for React Router to work properly
  • Fallback mechanism: If a requested file doesn’t exist, Nginx serves index.html
  • Prevents 404 errors: Users can bookmark and share direct links to React routes like /about or /contact
  • SPA compatibility: Enables proper single-page application behavior

How it works:

  1. First tries to serve the requested file ($uri)
  2. Then tries to serve the directory ($uri/)
  3. Finally falls back to index.html for client-side routing

6. API Reverse Proxy

location /api/ {
    proxy_pass http://localhost:3001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_cache_bypass $http_upgrade;
}

This section handles backend API integration:

  • API routing: Routes all /api/ requests to your backend server running on port 3001
  • WebSocket support: Handles WebSocket upgrades for real-time features
  • Header forwarding: Preserves client information for proper logging and security
  • CORS elimination: Serves everything from the same domain, eliminating cross-origin issues
  • Load balancing ready: Can easily be extended to proxy to multiple backend servers

Performance Impact Summary

This optimized configuration provides significant improvements over basic Nginx setups:

  • 60-80% reduction in file sizes through gzip compression
  • 1-year caching for static assets, eliminating repeat downloads
  • Enhanced security with multiple protection headers
  • Perfect SPA support with React Router fallback configuration
  • Seamless API integration with reverse proxy setup
  • Reduced server load through optimized logging and caching

The configuration is production-ready and handles the unique requirements of React Single Page Applications while providing enterprise-grade performance and security optimizations.

Testing the Configuration

Test your Nginx configuration for syntax errors:

  1. sudo nginx -t

If the test passes, reload Nginx:

  1. sudo systemctl reload nginx

Step 5 — How To Set Up Reverse Proxy for API Integration

Many React applications need to communicate with backend APIs. Nginx can act as a reverse proxy, routing API requests to your backend server while serving the React app from the same domain.

How To Set Up a Backend API

If you have a Node.js/Express backend, here’s how to set it up:

  1. # Install PM2 for process management
  2. sudo npm install -g pm2
  3. # Create a simple Express server
  4. mkdir /var/www/api
  5. cd /var/www/api
  6. npm init -y
  7. npm install express cors

Create a simple API server:

// /var/www/api/server.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3001;

app.use(cors());
app.use(express.json());

app.get('/api/health', (req, res) => {
    res.json({ status: 'OK', timestamp: new Date().toISOString() });
});

app.get('/api/users', (req, res) => {
    res.json([
        { id: 1, name: 'John Doe', email: 'john@example.com' },
        { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ]);
});

app.listen(PORT, () => {
    console.log(`API server running on port ${PORT}`);
});

Start the API server with PM2:

  1. pm2 start server.js --name "react-api"
  2. pm2 save
  3. pm2 startup

Updating Nginx Configuration

The Nginx configuration from Step 4 already includes the API proxy. The /api/ location block routes requests to your backend server running on port 3001.

Step 6 — How To Implement SSL/HTTPS Security

SSL certificates are essential for production applications. Let’s set up Let’s Encrypt certificates for your domain.

Installing Certbot

  1. sudo apt update
  2. sudo apt install certbot python3-certbot-nginx

Obtaining SSL Certificate

  1. sudo certbot --nginx -d your_domain -d www.your_domain

Certbot will automatically update your Nginx configuration to include SSL settings and redirect HTTP to HTTPS.

Verifying SSL Configuration

After installation, your Nginx configuration will include SSL settings:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name your_domain www.your_domain;
    
    ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    
    # Your existing configuration...
}

server {
    listen 80;
    listen [::]:80;
    server_name your_domain www.your_domain;
    return 301 https://$server_name$request_uri;
}

Setting Up Auto-Renewal

  1. sudo crontab -e

Add this line to renew certificates automatically:

0 12 * * * /usr/bin/certbot renew --quiet

Step 7 — Configuring Performance Optimization

Advanced Nginx Performance Tuning

Create a performance-optimized Nginx configuration by editing the main configuration file:

# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # Basic settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    
    # Buffer sizes
    client_body_buffer_size 128k;
    client_max_body_size 10m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    
    # Timeouts
    client_body_timeout 12;
    client_header_timeout 12;
    keepalive_timeout 15;
    send_timeout 10;
    
    # Gzip settings
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json
        application/atom+xml
        image/svg+xml;
    
    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    
    # Include your site configuration
    include /etc/nginx/sites-enabled/*;
}

Understanding the above Advanced Performance Configuration

This configuration optimizes Nginx at the system level for high-performance React application serving. Let’s break down each section:

1. Worker Process Configuration

worker_processes auto;
worker_rlimit_nofile 65535;
  • worker_processes auto: Automatically sets the number of worker processes to match your CPU cores, maximizing parallel processing
  • worker_rlimit_nofile 65535: Increases the file descriptor limit per worker, allowing more concurrent connections (default is usually 1024)

2. Event Processing Optimization

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}
  • worker_connections 4096: Each worker can handle up to 4,096 simultaneous connections (total = workers × 4096)
  • use epoll: Uses the most efficient event processing method on Linux systems
  • multi_accept on: Allows workers to accept multiple connections at once, reducing context switching

3. HTTP Performance Settings

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
  • sendfile on: Uses the kernel’s sendfile() system call for efficient file serving, bypassing user-space copying
  • tcp_nopush on: Optimizes TCP packet transmission by waiting to send packets until they’re full
  • tcp_nodelay on: Sends small packets immediately for better real-time performance
  • keepalive_timeout 65: Keeps connections alive for 65 seconds, reducing connection overhead
  • types_hash_max_size 2048: Increases hash table size for faster MIME type lookups

4. Buffer Size Optimization

client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
  • client_body_buffer_size 128k: Allocates 128KB buffer for request bodies, reducing disk I/O for small uploads
  • client_max_body_size 10m: Allows file uploads up to 10MB (adjust based on your React app’s needs)
  • client_header_buffer_size 1k: Standard buffer for HTTP headers
  • large_client_header_buffers 4 4k: Handles large headers (cookies, custom headers) with 4 buffers of 4KB each

5. Timeout Configuration

client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
  • client_body_timeout 12: Gives clients 12 seconds to send request body data
  • client_header_timeout 12: Gives clients 12 seconds to send complete headers
  • keepalive_timeout 15: Keeps idle connections alive for 15 seconds
  • send_timeout 10: Gives 10 seconds to send response data to clients

6. Advanced Gzip Configuration

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/javascript
    application/xml+rss
    application/json
    application/atom+xml
    image/svg+xml;
  • gzip_comp_level 6: Balances compression ratio (6) with CPU usage (1-9 scale)
  • gzip_proxied any: Compresses responses for all proxy requests
  • gzip_vary on: Adds “Vary: Accept-Encoding” header to help caches understand compression
  • Extended file types: Includes SVG and Atom feeds for comprehensive compression coverage

7. Rate Limiting Protection

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
  • API rate limiting: Allows 10 requests per second per IP for API endpoints
  • Login rate limiting: Restricts login attempts to 1 request per second per IP
  • Memory allocation: Uses 10MB of memory to track rate limiting state
  • DDoS protection: Prevents abuse while allowing legitimate traffic

Performance Impact Summary for the above Advanced Performance Configuration

This advanced configuration provides:

  • 4x more concurrent connections compared to default settings
  • Reduced CPU usage through optimized event processing
  • Faster file serving with sendfile and TCP optimizations
  • Better compression with tuned gzip settings
  • DDoS protection through intelligent rate limiting
  • Optimized memory usage with appropriate buffer sizes

Applying the Configuration

To apply these changes:

  1. sudo nano /etc/nginx/nginx.conf

Replace the existing configuration with the optimized version above, then test and reload:

  1. sudo nginx -t
  2. sudo systemctl reload nginx

Note: These settings are optimized for production environments. For development, you may want to reduce some values to conserve resources.

How To Optimize Your React Build

Optimize your React build for production:

// package.json
{
  "scripts": {
    "build": "GENERATE_SOURCEMAP=false npm run build",
    "build:analyze": "npm run build && npx serve -s build"
  }
}

How To Create Environment-Specific Builds

Create environment-specific builds:

# .env.production
REACT_APP_API_URL=https://your-domain.com/api
REACT_APP_ENVIRONMENT=production

Step 8 — How To Set Up CI/CD Automation

How To Set Up a GitHub Actions Workflow

Create .github/workflows/deploy.yml:

name: Deploy React App to Ubuntu

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm test -- --coverage --watchAll=false
    
    - name: Build React app
      run: npm run build
      env:
        REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}
    
    - name: Deploy to server
      uses: appleboy/ssh-action@v0.1.5
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          cd /var/www/your-domain/html
          rm -rf *
          # Files will be uploaded via rsync
    
    - name: Upload files
      run: |
        rsync -avz --delete ./build/ ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/var/www/your-domain/html/
    
    - name: Restart Nginx
      uses: appleboy/ssh-action@v0.1.5
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.SSH_KEY }}
        script: |
          sudo systemctl reload nginx

This automated deployment pipeline streamlines your workflow by automatically building, testing, and deploying your React application whenever you push changes to the main branch. Here’s how the process works, step by step:

  1. Code Checkout: The workflow checks out your latest code from the repository.
  2. Node.js Setup: It sets up a Node.js environment and enables npm caching for faster builds.
  3. Dependency Installation: Installs all dependencies using npm ci for reliable and reproducible builds.
  4. Testing: Runs your test suite with coverage reporting to ensure code quality before deployment.
  5. Production Build: Compiles your React application for production, using environment variables from GitHub Secrets to create optimized static files.
  6. Server Preparation: Connects to your Ubuntu server via SSH and clears the existing web directory.
  7. File Upload: Uses rsync to efficiently upload only changed files, minimizing transfer time and bandwidth usage.
  8. Nginx Reload: Reloads Nginx to serve the updated application without downtime.
  9. Isolated Environment: The entire process runs in a clean, isolated environment on GitHub’s servers, ensuring consistent deployments regardless of your local setup.
  10. Immediate Feedback: If any step fails, you receive immediate feedback, allowing you to quickly identify and fix issues before they reach production.

Step 9 — How To Monitor and Debug Production Issues

How To Set Up Logging

Configure Nginx logging for better debugging:

# In your site configuration
access_log /var/log/nginx/react-app.access.log;
error_log /var/log/nginx/react-app.error.log;

# Log format for detailed analysis
log_format detailed '$remote_addr - $remote_user [$time_local] '
                   '"$request" $status $body_bytes_sent '
                   '"$http_referer" "$http_user_agent" '
                   '$request_time $upstream_response_time';

How To Monitor with PM2

  1. # Install PM2 monitoring
  2. pm2 install pm2-logrotate
  3. pm2 monit

How To Set Up a Health Check Endpoint

Add a health check to your React app:

// In your React app
const HealthCheck = () => {
  const [status, setStatus] = useState('checking');
  
  useEffect(() => {
    fetch('/api/health')
      .then(res => res.json())
      .then(data => setStatus(data.status))
      .catch(() => setStatus('error'));
  }, []);
  
  return <div>API Status: {status}</div>;
};

How To Monitor Performance

Install and configure monitoring tools:

  1. # Install htop for system monitoring
  2. sudo apt install htop
  3. # Monitor Nginx status
  4. sudo systemctl status nginx
  5. # Check disk usage
  6. df -h
  7. # Monitor memory usage
  8. free -h

Frequently Asked Questions

1. How do I deploy a React app with Nginx on Ubuntu?

Deploying a React app with Nginx on Ubuntu involves several key steps:

  1. Build your React application using npm run build to create optimized production files
  2. Configure Nginx with proper server blocks and React Router fallback using try_files $uri $uri/ /index.html
  3. Upload build files to your server’s web directory (typically /var/www/your-domain/html)
  4. Set up SSL certificates using Let’s Encrypt for secure HTTPS connections
  5. Optimize performance with gzip compression, static asset caching, and security headers

The critical configuration for React Router compatibility is the try_files directive, which ensures all client-side routes fall back to index.html for proper single-page application functionality.

2. Where should I put the React build files in Nginx?

React build files should be placed in your Nginx server’s document root directory. The standard location is /var/www/your-domain/html/ on Ubuntu systems.

Here’s the typical structure:

/var/www/your-domain/html/
├── index.html          # Main entry point
├── static/
│   ├── css/           # Compiled CSS files
│   ├── js/            # Compiled JavaScript files
│   └── media/         # Images and other assets
├── manifest.json      # PWA manifest
└── robots.txt         # SEO robots file

Ensure your Nginx configuration points to this directory with the root directive and that the web server has proper read permissions.

3. How do I fix React Router routes not working with Nginx?

React Router routes fail with Nginx because the server doesn’t know about client-side routes. When users navigate to /about or /contact, Nginx tries to find these files and returns 404 errors.

Solution: Configure Nginx with a fallback to index.html:

location / {
    try_files $uri $uri/ /index.html;
}

This configuration tells Nginx to:

  1. First try to serve the requested file ($uri)
  2. Then try to serve the directory ($uri/)
  3. Finally fall back to index.html for client-side routing

Additionally, ensure your React app uses BrowserRouter instead of HashRouter for clean URLs without hash fragments.

4. Can I deploy both frontend and backend on the same Nginx server?

Yes, you can deploy both frontend and backend on the same Nginx server using reverse proxy configuration. This is a common and efficient setup for full-stack applications.

Configuration example:

server {
    listen 80;
    server_name your-domain.com;
    
    # Serve React app for all routes except API
    location / {
        root /var/www/your-domain/html;
        try_files $uri $uri/ /index.html;
    }
    
    # Proxy API requests to backend
    location /api/ {
        proxy_pass http://localhost:3001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

This setup allows your React app to make API calls to /api/endpoint which get proxied to your backend server running on port 3001.

5. How do I enable HTTPS for a React app deployed with Nginx?

Enable HTTPS for your React app using Let’s Encrypt free SSL certificates:

  1. Install Certbot:

    sudo apt update
    sudo apt install certbot python3-certbot-nginx
    
  2. Obtain SSL certificate:

    sudo certbot --nginx -d your-domain.com -d www.your-domain.com
    
  3. Verify automatic renewal:

    sudo certbot renew --dry-run
    

Certbot automatically configures Nginx with:

  • SSL certificate paths
  • HTTP to HTTPS redirects
  • Modern SSL security settings
  • Automatic certificate renewal

Your React app will then be accessible via https://your-domain.com with a valid SSL certificate.

6. What are best practices for caching React static assets with Nginx?

Optimize React static asset caching with these Nginx configurations:

Long-term caching for static assets:

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Cache busting for updated files: React’s build process automatically adds hashes to filenames (e.g., main.abc123.js), so you can safely cache these files for long periods.

Gzip compression:

gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
    text/plain
    text/css
    text/javascript
    application/javascript
    application/json;

Browser caching for HTML:

location ~* \.html$ {
    expires 1h;
    add_header Cache-Control "public, must-revalidate";
}

This configuration reduces bandwidth usage by 60-80% and significantly improves page load times for returning visitors.

Conclusion

You’ve successfully deployed a production-ready React application with Nginx on Ubuntu, incorporating enterprise-grade optimizations, robust security configurations, and comprehensive monitoring capabilities.

This approach ensures optimized performance through features like gzip compression, static asset caching, and advanced Nginx tuning for faster load times. Security is strengthened with SSL certificates, security headers, and proper access controls, while scalability is achieved via a reverse proxy setup that supports API integration and microservices.

The techniques outlined in this tutorial are compatible with the latest Ubuntu versions, ensuring your deployment remains reliable and future-proof across current and upcoming Ubuntu LTS releases.

Next Steps

Recommended Tutorials to Continue Learning:

If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

Tutorial Series: How To Code in React.js

React is a popular JavaScript framework for creating front-end applications, such as user interfaces that allow users to interact with programs. Originally created by Facebook, it has gained popularity by allowing developers to create fast applications using an intuitive programming paradigm that ties JavaScript with an HTML-like syntax known as JSX.

In this series, you will build out examples of React projects to gain an understanding of this framework, giving you the knowledge you need to pursue front-end web development or start out on your way to full stack development.

About the author

Anish Singh Walia
Anish Singh Walia
Author
Sr Technical Writer
See author profile

I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix

Still looking for an answer?

Was this helpful?


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

The quit command doesn’t work on MacOS, since abort task on macOS is still ctrl+c

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.