Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Deploying a React app to a VPS means turning the project into static production files, placing those files in a web root, and configuring Nginx to serve them. Vite normally creates a dist/ directory; Create React App normally creates build/. The critical routing setting is try_files $uri $uri/ /index.html;, which lets React Router handle deep links instead of returning a 404 when a visitor refreshes a route.

What a VPS deployment actually runs

A production React frontend does not need a Node.js process to answer every browser request. After the production build, JavaScript, CSS, images, fonts and the HTML entry point are ordinary files. Nginx can deliver those files directly, which is simpler and uses fewer resources than leaving a development server running.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The VPS still needs an operating system, a web server, DNS and (for a public site) HTTPS. Your source code, package manager and build tool are needed wherever you run the build, but they do not have to remain installed on the server after the files have been generated. If your application also has an API, database or server-side rendering, those are separate services; the procedure here covers a client-rendered React application.

Before you begin

Information and access you need

  • An Ubuntu VPS with an account that can use sudo. Current 2026 deployment guides reference Ubuntu 24.04; confirm the commands against the image supplied by your VPS provider.
  • The server’s public IPv4 address (and IPv6 address if you intend to publish one).
  • SSH access, for example ssh username@203.0.113.10.
  • A React project that builds successfully, plus its package-lock file if you use npm ci.
  • A domain name if you want a permanent hostname and a trusted certificate.

Choose where to build

You can build on your workstation and transfer only the output, or install Node.js and the project on the VPS and build there. Building locally keeps build tools and source files off the server. Building on the VPS is convenient when a deployment script already checks out the repository there. Whichever method you choose, the server ultimately needs only the generated files in the Nginx web root.

#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter | 4Pack Type C Female to A Male Cable Converter for Apple Duo 17 16 15 14 Plus,iWatch Watch 12 11 10,iPad 9 8 Air,Airpods 5 4
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

Build the React application for production

Vite projects

From the project root, install the locked dependencies and create a production build:

npm ci
npm run build

Vite places the result in dist/ by default, relative to the project root. A project’s build.outDir setting can change that directory. Vite empties an output directory inside the project root before building and warns when the configured directory is outside the root, so check the build output rather than assuming its name.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create React App projects

Run the same two npm commands when the project uses Create React App:

npm ci
npm run build

Create React App normally writes the production files to build/. The folder name is the practical difference at the transfer step: copy dist/ for the usual Vite configuration and build/ for the usual Create React App configuration.

Inspect the output before uploading

  • Confirm that the selected directory contains index.html.
  • Check that JavaScript and CSS assets are present beneath the directory.
  • Open the built app locally with a static file server if possible; do not use a development-only assumption that hides missing assets.
  • Record the exact output directory and the URL path where the app will live. Those two choices determine the Nginx root and the build tool’s public base path.

Set the correct public base path

An app served at https://example.com/ uses the root base path. An app served at https://example.com/portal/ must generate asset URLs beginning with /portal/. If the build still assumes /, the HTML may load while its JavaScript and CSS requests return 404.

Vite

Vite’s base option defaults to /. Set it in vite.config or pass it during the build:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
vite build --base=/portal/

For code that constructs a URL dynamically, use import.meta.env.BASE_URL. Keep that expression literal in the source so Vite can replace it at build time. For a root deployment, leave the base at /.

Rank #2
Sale
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.

Create React App

Set the project’s homepage value to the public path, such as https://example.com/portal or the appropriate relative form supported by that project. Rebuild after changing it. A root-hosted app should use the root homepage setting rather than a portal path.

Match the Nginx path

The path in the build configuration and the path in Nginx must agree. A subpath example later in this guide stores files in /var/www/portal, serves them through /portal/, and falls back to /portal/index.html. If you change any one of those values, change the others and rebuild.

Install and prepare Nginx on the VPS

Connect to the server and install Nginx:

sudo apt-get update
sudo apt-get install -y nginx
sudo systemctl enable --now nginx

The first command refreshes Ubuntu’s package index. The second installs Nginx, and the last starts it immediately and configures it to start after a reboot. Visiting the server’s IP address at this point should show Nginx’s default response; that confirms the web server is reachable before you add your application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create a web root outside the home directory

Keep public files in a dedicated directory such as /var/www/myapp, not inside a user’s home directory. Create it with:

sudo mkdir -p /var/www/myapp
sudo chown -R www-data:www-data /var/www/myapp

www-data is the common Nginx account on Ubuntu. The ownership change lets Nginx read the files and makes the intended serving account explicit. If your deployment account must update the directory directly, use a group or a controlled release directory rather than making the entire filesystem world-writable.

Copy the build output to the web root

Copy the contents of the output directory, not the directory itself. Nginx should find the entry file at /var/www/myapp/index.html, not at /var/www/myapp/dist/index.html or /var/www/myapp/build/index.html.

Copying from a local computer

One safe pattern is to upload the directory to a temporary location and then install its contents with elevated privileges. For a Vite build:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.
scp -r dist/. username@203.0.113.10:/tmp/myapp-dist/
ssh username@203.0.113.10
sudo rsync -av --delete /tmp/myapp-dist/ /var/www/myapp/
sudo chown -R www-data:www-data /var/www/myapp

Replace dist with build for Create React App. The trailing slash and dist/. notation copy the contents. The --delete option removes files left by an older release; use it only when the destination contains no unrelated uploads or user data.

Copying from a build performed on the VPS

If the build ran on the server, run the equivalent installation command from the project directory:

sudo rsync -av --delete dist/ /var/www/myapp/
sudo chown -R www-data:www-data /var/www/myapp

Use build/ instead of dist/ for Create React App. Check the result:

ls -l /var/www/myapp/index.html

Configure an Nginx server block for a root-hosted app

Create a site configuration:

sudo nano /etc/nginx/sites-available/myapp

Use a server block like this, replacing the domain names and directory as necessary:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/myapp;
    index index.html;

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

    location ~* .(?:css|js|mjs|json|png|jpg|jpeg|gif|svg|ico|webp|woff2?)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Why the try_files line is essential

React Router handles routes such as /dashboard in the browser. There is no physical dashboard file in the web root. With try_files $uri $uri/ /index.html;, Nginx serves a real file when one exists and otherwise returns the React entry point, allowing the client router to select the route. Without that fallback, loading the home page may work while refreshing /dashboard produces an Nginx 404.

Enable and validate the site

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo nginx -t
sudo systemctl reload nginx

nginx -t must report a successful configuration test before you reload. If the default site captures the request, disable its enabled link after confirming that your server block has the correct server_name:

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Open the domain or server IP in a browser. Test both the root URL and at least one client-side route by entering that route directly in the address bar and refreshing it.

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft

Serving the app from a subpath

Subpath hosting requires both build-time and web-server changes. Suppose the public URL is https://example.com/portal/. Set Vite’s base to /portal/ (or set the corresponding Create React App homepage), then place the files in /var/www/portal. A server block whose root is /var/www can map the URL as follows:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
server {
    listen 80;
    listen [::]:80;
    server_name example.com;

    root /var/www;
    index index.html;

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

Here, a request for /portal/assets/app.js maps to /var/www/portal/assets/app.js, while an unknown client-side route falls back to /var/www/portal/index.html. The fallback must include the subpath; using /index.html would send the wrong application entry point.

Set DNS, HTTPS and the firewall

Point the domain to the VPS

At your DNS provider, create an A record for the hostname that points to the VPS’s IPv4 address. If you publish IPv6, create an AAAA record only when the server is configured to accept IPv6 traffic. DNS changes can take time to appear, so test the hostname after it resolves and before requesting a certificate.

Allow web traffic through UFW

If Ubuntu’s UFW firewall is active, allow both HTTP and HTTPS:

sudo ufw allow 'Nginx Full'

Do not remove your SSH access rule while changing the firewall. The Nginx Full profile covers ports 80 and 443; it does not replace the separate SSH rule needed to administer the server.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Obtain a certificate with Certbot

Install the Nginx integration and request a certificate after the domain resolves to this server:

sudo apt-get install -y python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

Certbot can update the Nginx server block for HTTPS and redirect HTTP requests. Substitute only the names that actually resolve to the VPS. After it completes, visit the HTTPS URL and test a deep React route again; TLS changes the transport, not the need for the try_files fallback.

Best Value
Sale
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display | 85W Max PD, 1 Powered USB-C 5Gbps & 2 Powered USB-A 3.0 5Gbps Data Ports, for MacBook Pro, MacBook Air, Dell and More (Black)
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Handle environment variables correctly

Frontend environment values are usually embedded into the JavaScript bundle during the build. A value such as VITE_API_URL is therefore fixed at the moment npm run build runs. Editing a .env file on the VPS after the files have been copied does not change an already generated bundle.

When an API endpoint or other public setting changes, set the value in the build environment and run a new build, then copy the new output:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
VITE_API_URL=https://api.example.com npm run build

Do not place private credentials in client-side variables; anything shipped to a browser can be inspected. If you need to change a non-secret setting without rebuilding, one alternative is a runtime JSON configuration file stored in the web root. The application can fetch that file at startup, but you must design loading, caching and failure handling for it explicitly.

Use caching without trapping old releases

Production tools normally give generated JavaScript and CSS files content-based names. Those hashed assets are good candidates for a long cache lifetime, as in the optional Nginx location above. Keep index.html on a short or default cache policy so that a new deployment can point browsers at the newest asset names. If you cache the entry HTML for a year, visitors may continue requesting an old release after you deploy a new one.

When a release appears partly updated, inspect the browser’s network panel for the HTML and asset URLs, then check the response headers with a command such as:

curl -I https://example.com/

Clear a local browser cache only after confirming the server is sending the intended files; cache invalidation should not be the first fix for a wrong path or failed deployment.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A repeatable update workflow

  1. Update the source and lockfile in the project repository.
  2. Run npm ci and npm run build in a clean build environment.
  3. Verify that the expected output directory contains index.html and that the configured base path has not changed accidentally.
  4. Upload the contents to a temporary server directory.
  5. Synchronize the contents into the web root with rsync; use --delete only when the directory contains exclusively generated assets.
  6. Restore www-data ownership if the upload account changed it.
  7. Open the site, a deep client-side route, and an asset URL. A static-file update does not normally require an Nginx reload, but reload after changing the server configuration and run sudo nginx -t first.

This manual process can later be placed in a CI job. The important separation remains the same: build once, transfer the generated files, and keep Nginx serving a known release directory.

Troubleshoot the failures that matter most

Symptom Likely cause Fix
The home page works, but refreshing /about returns 404. The server block lacks the React fallback. Add try_files $uri $uri/ /index.html; inside location /, test with sudo nginx -t, and reload Nginx.
The page is blank and the browser reports missing JavaScript or CSS. The app was built for a different URL base, or the files are nested one directory too deep. Check the generated asset URLs, align Vite base or the Create React App homepage with the public path, and copy the output contents so index.html is directly in the web root.
A subpath deployment sends users to the wrong page. The fallback uses /index.html instead of the subpath entry point. Use the matching fallback, such as /portal/index.html, and ensure the Nginx root maps /portal/ to the directory that contains that file.
Changing .env does nothing. The value was compiled into the existing bundle. Set the variable before npm run build and deploy the newly generated files, or implement a runtime JSON configuration.
Nginx returns 403 or cannot read files. The path or one of its parent directories is not readable by Nginx. Confirm the path in root, inspect permissions with ls -ld, and use an appropriate www-data ownership or group arrangement.
nginx -t fails. A syntax error, duplicate directive, invalid path or malformed regular expression exists in the configuration. Read the reported file and line number, correct that block, run the test again, and reload only after it passes.
The domain times out while the IP works. DNS is not pointing at this VPS, or the firewall blocks ports 80 and 443. Verify the A/AAAA records, confirm Nginx is listening, and allow the Nginx Full UFW profile when UFW is active.
Certbot cannot validate the domain. The hostname does not resolve to this server or HTTP traffic cannot reach it. Fix DNS and port 80 access first, then run the Certbot command again.

Deployment checklist

  • The production command completed without errors.
  • You identified whether the output is dist/, build/, or a custom directory.
  • The output’s index.html is directly beneath the Nginx web root.
  • The Vite base or Create React App homepage matches the public URL path.
  • Nginx has the correct root, server_name and try_files fallback.
  • sudo nginx -t passes before every configuration reload.
  • DNS resolves to the VPS and the firewall permits web traffic.
  • HTTPS works through Certbot, and both the home page and a refreshed client-side route load correctly.
  • Any changed frontend environment variable was present during the build, not added afterward.

Bottom line

The reliable VPS pattern is build, copy, serve and then secure: generate static files with npm ci && npm run build, copy the contents of Vite’s dist/ or Create React App’s build/ directory into a web root, configure Nginx with the matching base path and try_files fallback, then add DNS, HTTPS and firewall rules. Once those paths and build-time settings agree, a React frontend is a straightforward static site for Nginx to deliver.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.