Skip to content
DevOps devops webservers 5 min read

Serving a Static Site with Nginx

A static site is a website made of plain files — HTML (the page structure), CSS (the styling), and JavaScript (the browser-side behavior) — that the server sends to the browser exactly as they are, with no per-request processing. Nginx (pronounced “engine-x”) is a web server, which is a program that listens for incoming web requests and returns files or responses. Nginx is one of the fastest, most efficient tools for this job, which is why it powers a huge share of the internet. This page walks you through the entire process on Ubuntu: placing your files, setting permissions, writing a configuration block, enabling it, and visiting your live site.

When to use Nginx for a static site

A static site is the right fit when your content does not change based on who is visiting or what they typed — think landing pages, documentation, portfolios, marketing sites, or the compiled output of a single-page application (SPA) built with React, Vue, or Angular. Nginx is ideal here because it serves files directly from disk with very little CPU and memory, so a small server can handle thousands of visitors.

ScenarioUse Nginx static serving?
Plain HTML/CSS/JS siteYes — perfect fit
Compiled React/Vue/Angular SPA build outputYes — with a fallback (shown below)
Server-rendered app (PHP, Node, Python)No — Nginx forwards to that app instead (reverse proxy)
Content that changes per user on every requestNo — you need a backend application

If your site needs a database, user logins, or server-side code, you do not have a static site. In that case Nginx acts as a reverse proxy (a server that sits in front of your app and forwards requests to it) rather than serving files directly.

Step 1 — Create the site directory

By convention on Ubuntu, websites live under /var/www. We will create a dedicated folder for this site so each site stays separate and tidy.

sudo mkdir -p /var/www/site

Now add a simple index.html. The index.html file is special — when a visitor requests your site’s root URL (like https://example.com/), Nginx looks for index.html by default.

sudo tee /var/www/site/index.html > /dev/null <<'EOF'
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My Static Site</title>
  </head>
  <body>
    <h1>Hello from Nginx!</h1>
    <p>This page is served as a static file.</p>
  </body>
</html>
EOF

If you have an existing build (for example a React app), copy its output instead. The -r flag means “recursive,” so it copies the folder and everything inside it.

sudo cp -r ./dist/* /var/www/site/

Step 2 — Set ownership and permissions

Nginx runs as a low-privilege system user called www-data on Ubuntu. For Nginx to read your files, that user needs access. We give ownership of the folder to www-data and set sensible permissions.

sudo chown -R www-data:www-data /var/www/site
sudo chmod -R 755 /var/www/site

chown -R www-data:www-data sets both the owner and group to www-data recursively (-R). chmod -R 755 means the owner can read/write/execute, while everyone else can read and execute — which is exactly what a web server needs.

Never use chmod 777. That makes files writable by every user on the system, which is a serious security hole. 755 for directories and 644 for files is the safe, standard choice for web content.

Step 3 — Write the server block

A server block is the chunk of Nginx configuration that defines one website — its domain name, where its files live, and how to handle requests. On Ubuntu, each site gets its own file in /etc/nginx/sites-available. Create one for your site:

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

Paste in the following configuration:

server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

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

    location / {
        try_files $uri $uri/ =404;
    }
}

Here is what each line does:

  • listen 80; tells Nginx to accept normal HTTP traffic on port 80. The [::]:80 line does the same for IPv6 (the newer internet addressing scheme).
  • server_name lists the domains this block answers for. Replace example.com with your real domain, or use _ to match any hostname while testing.
  • root is the folder on disk that holds your files.
  • index names the default file to serve for a directory request.
  • try_files $uri $uri/ =404; tells Nginx: try the exact requested file ($uri), then try it as a directory ($uri/), and if neither exists return a 404 “Not Found” error.

SPA fallback

If you are hosting a single-page application, the browser handles routing itself, so URLs like /dashboard have no matching file on disk. Without help, Nginx would return a 404. The fix is to fall back to index.html so the app can take over routing:

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

Now any unknown path serves index.html, and your SPA’s router displays the correct page.

Step 4 — Enable the site

Ubuntu’s Nginx only serves sites that are “enabled,” meaning a link to them exists in /etc/nginx/sites-enabled. We create that link with ln -s (a symbolic link, which is a pointer to the original file).

sudo ln -s /etc/nginx/sites-available/site /etc/nginx/sites-enabled/

Ubuntu ships with a default site that may grab requests first. Remove its link so your site responds:

sudo rm -f /etc/nginx/sites-enabled/default

Step 5 — Test and reload

Always test your configuration before applying it. A typo could otherwise take Nginx down.

sudo nginx -t

Output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If the test passes, reload Nginx so it picks up the new configuration. reload applies changes without dropping active connections, unlike a full restart.

sudo systemctl reload nginx

Step 6 — Open the firewall and visit

If you use UFW (Uncomplicated Firewall, Ubuntu’s simple firewall tool), allow web traffic so the outside world can reach your site:

sudo ufw allow 'Nginx HTTP'

Now confirm it works from the server itself with curl, a command-line tool that fetches URLs:

curl -I http://localhost

Output:

HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Content-Type: text/html
Content-Length: 213

A 200 OK status means success. Open http://your-server-ip (or your domain) in a browser and you should see your page.

Best Practices

  • Keep each site in its own folder under /var/www and its own file in sites-available — never edit the main nginx.conf for individual sites.
  • Always run sudo nginx -t before reloading; a broken config that reloads can take every site on the server offline.
  • Use chown www-data:www-data and 755/644 permissions; avoid 777 entirely.
  • Use try_files ... /index.html only for SPAs — for plain static sites keep =404 so missing pages return a real 404.
  • Add HTTPS with a free Let’s Encrypt certificate (via certbot) before going to production, so traffic is encrypted.
  • Prefer systemctl reload nginx over restart to apply changes without interrupting live visitors.
Last updated June 15, 2026
Was this helpful?