Root & Alias #

root and alias are two directives that seem to do the same thing — determine where Nginx reads files from — but the way they construct file paths is very different. This is one of the biggest sources of confusion in Nginx, even among fairly experienced users. Choosing wrong between the two results in confusing 404s, or worse, files served from the wrong location.

This article dissects the difference between them in depth until you truly understand when to use which.

The Core Difference: How Paths Are Constructed #

The fundamental difference between root and alias is what gets appended to the URI to form the file path:

root:   file path = root value  +  full URI
alias:  file path = alias value +  URI after the matched location part

Let’s look at a concrete example using the request GET /assets/style.css:

With root #

location /assets/ {
    root /var/www;
}
URI:      /assets/style.css
root:     /var/www
          ─────────────────────────────
          /var/www  +  /assets/style.css
          = /var/www/assets/style.css

The entire URI (/assets/style.css) is appended to the root value. The /assets/ segment gets included in the path.

With alias #

location /assets/ {
    alias /var/www/static/;
}
URI:           /assets/style.css
location:      /assets/
                          ──────────── this is what remains after matching the location
URI remainder: style.css
alias:         /var/www/static/
               ─────────────────────────────────────
               /var/www/static/  +  style.css
               = /var/www/static/style.css

The part of the URI that matched the location (/assets/) is replaced with the alias value. Only the remainder of the URI after the matched part gets appended.


Direct Comparison Diagram #

flowchart LR
    subgraph ROOT["With root"]
        direction TB
        R1["Request: GET /assets/style.css"]
        R2["location /assets/\n  root /var/www;"]
        R3["Path = /var/www + /assets/style.css"]
        R4["→ /var/www/assets/style.css"]
        R1 --> R2 --> R3 --> R4
    end

    subgraph ALIAS["With alias"]
        direction TB
        A1["Request: GET /assets/style.css"]
        A2["location /assets/\n  alias /var/www/static/;"]
        A3["Match /assets/ → remainder: style.css"]
        A4["Path = /var/www/static/ + style.css"]
        A5["→ /var/www/static/style.css"]
        A1 --> A2 --> A3 --> A4 --> A5
    end

The result difference is clear: root produces /var/www/assets/style.css, alias produces /var/www/static/style.css. For the same request!


Comprehensive Comparison Table #

# ─── Setup ────────────────────────────────────────────────────────────────────
# Files on the filesystem:
# /var/www/html/about.html
# /var/www/assets/style.css
# /var/www/assets/logo.png
# /data/uploads/report.pdf
# /opt/docs/html/guide.html
ConfigurationRequestPath searchedMatch?
root /var/www/html; at location /GET /about.html/var/www/html/about.html
root /var/www; at location /assets/GET /assets/style.css/var/www/assets/style.css
root /var/www/assets; at location /assets/GET /assets/style.css/var/www/assets/assets/style.css❌ (double /assets/)
alias /var/www/assets/; at location /assets/GET /assets/style.css/var/www/assets/style.css
alias /data/uploads/; at location /files/GET /files/report.pdf/data/uploads/report.pdf
alias /opt/docs/html/; at location /docs/GET /docs/guide.html/opt/docs/html/guide.html

When to Use root #

Use root when the directory structure on the filesystem mirrors the URL structure. This is the most common case for static sites:

server {
    root /var/www/mysite;
    # Filesystem structure:
    # /var/www/mysite/
    # ├── index.html        → GET /
    # ├── about.html        → GET /about.html
    # ├── images/           → GET /images/...
    # │   ├── logo.png      → GET /images/logo.png
    # │   └── banner.jpg    → GET /images/banner.jpg
    # ├── css/
    # │   └── style.css     → GET /css/style.css
    # └── js/
    #     └── app.js        → GET /js/app.js

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

root is also ideal at the server block level (rather than inside a location), because it gets inherited by all location blocks:

server {
    listen 80;
    server_name example.com;

    # One root for the whole site
    root /var/www/example.com;

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

    location /blog/ {
        # Inherits the root from the server block
        # GET /blog/post.html → /var/www/example.com/blog/post.html ✓
        try_files $uri $uri/ =404;
    }

    location /gallery/ {
        # Inherits the root from the server block
        # GET /gallery/photo.jpg → /var/www/example.com/gallery/photo.jpg ✓
        try_files $uri =404;
    }
}

When to Use alias #

Use alias when the directory name on the filesystem differs from the URL path, or when you’re mapping a URL to a completely separate location:

Case 1: Directory name differs from the URL #

server {
    root /var/www/myapp;

    # Files live at /var/www/assets/ but are accessed via /static/
    # With root /var/www/assets; → looked up at /var/www/assets/static/ ← WRONG!
    # With alias /var/www/assets/; → looked up at /var/www/assets/ ← CORRECT!
    location /static/ {
        alias /var/www/assets/;
        # GET /static/style.css → /var/www/assets/style.css ✓
    }
}

Case 2: Files in a location separate from the web root #

server {
    root /var/www/myapp;

    # Upload files stored on separate storage (fast SSD, NFS mount, etc.)
    location /uploads/ {
        alias /mnt/storage/user-uploads/;
        # GET /uploads/2024/photo.jpg → /mnt/storage/user-uploads/2024/photo.jpg
    }

    # API documentation in a different directory
    location /api-docs/ {
        alias /opt/api-documentation/html/;
        # GET /api-docs/auth.html → /opt/api-documentation/html/auth.html
    }

    # Shared assets for multiple projects
    location /shared-assets/ {
        alias /var/www/shared/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Case 3: alias is not allowed in server blocks #

This is an important limitation: alias is only valid inside a location block. There’s no alias at the server or http level:

# INVALID — alias in a server block:
server {
    alias /var/www/html;  # Error! alias is only valid in location
    ...
}

# VALID — alias only in location:
server {
    root /var/www/myapp;  # root in a server block: OK

    location /docs/ {
        alias /var/www/docs/;  # alias in location: OK
    }
}

The Trailing Slash Trap #

Trailing slash problems are the most common mistakes made with alias. The rule is simple: the trailing slash in location and alias must be consistent.

Scenario 1: Both have a trailing slash ✅ #

location /files/ {    # has trailing slash
    alias /data/;     # has trailing slash
}
# GET /files/photo.jpg → /data/photo.jpg ✓
# GET /files/        → /data/ ✓

Scenario 2: Neither has a trailing slash ✅ #

location /files {    # no trailing slash
    alias /data;     # no trailing slash
}
# GET /files/photo.jpg → /data/photo.jpg ✓
# GET /files         → /data ✓

Scenario 3: Inconsistent — location has one, alias doesn’t ❌ #

location /files/ {   # trailing slash in location
    alias /data;     # NO trailing slash in alias
}
# GET /files/photo.jpg → /dataphoto.jpg ← WRONG! no separator

Scenario 4: Inconsistent — location doesn’t have one, alias does ❌ #

location /files {    # NO trailing slash in location
    alias /data/;    # trailing slash in alias
}
# GET /files/photo.jpg → /data//photo.jpg ← double slash!
# Most OSes handle a double slash correctly,
# but it's still wrong configuration and can cause problems
flowchart TD
    A["alias configuration"] --> B{"location /path/\nhas trailing slash?"}
    B -- Yes --> C{"alias /dir/\nhas trailing slash?"}
    B -- No --> D{"alias /dir\nhas trailing slash?"}
    C -- Yes --> E["✅ Consistent\nCorrect"]
    C -- No --> F["❌ Inconsistent\nPath built incorrectly"]
    D -- No --> G["✅ Consistent\nCorrect"]
    D -- Yes --> H["❌ Inconsistent\nDouble slash"]

Recommendation: always use the pattern with trailing slashes in both (location /path/ + alias /dir/) — it’s the most intuitive and most commonly used.


The Common root-in-location Trap #

This is the mistake Nginx newcomers make most often:

# SITUATION: the file exists at /var/www/assets/style.css
# GOAL: the file should be accessible via /static/style.css

# ❌ WRONG — using root:
location /static/ {
    root /var/www/assets;
    # Nginx will look for: /var/www/assets/static/style.css
    # That file DOESN'T EXIST — what exists is /var/www/assets/style.css
    # → Confusing 404 Not Found
}

# ✅ CORRECT — using alias:
location /static/ {
    alias /var/www/assets/;
    # Nginx will look for: /var/www/assets/style.css ✓
}

# ✅ ALTERNATIVE — using root with the correct path:
# Create the directory /var/www/assets/static/ and store the file there
location /static/ {
    root /var/www/assets;
    # Nginx will look for: /var/www/assets/static/style.css ✓ (if the file is there)
}

alias with try_files #

Using alias together with try_files requires special attention. try_files works based on the URI, not the alias path:

location /files/ {
    alias /data/uploads/;

    # try_files uses $uri (the original URI from the browser)
    # → /data/uploads/report.pdf (after the alias mapping)
    try_files $uri =404;
}

For more complex cases with a directory listing fallback:

location /docs/ {
    alias /opt/docs/html/;

    # Try file → try directory → 404
    try_files $uri $uri/ =404;
}

For a named location fallback with alias:

location /media/ {
    alias /data/media/;

    # If the file doesn't exist, redirect to the handler
    try_files $uri @media_not_found;
}

location @media_not_found {
    return 404 '{"error": "Media file not found"}';
    add_header Content-Type application/json always;
}

Regex Locations with alias #

When a location uses a regex, alias can take advantage of the regex capture groups:

# Example: files stored per year at /data/media/YYYY/
# and accessed via /gallery/YYYY/filename
location ~* ^/gallery/(\d{4})/(.+)$ {
    alias /data/media/$1/$2;
    # GET /gallery/2024/photo.jpg → /data/media/2024/photo.jpg
}

This is a powerful feature but requires care because it’s vulnerable to path traversal if the capture groups aren’t validated. For this case, it’s safer to use try_files with an error fallback:

location ~* ^/gallery/(\d{4})/([a-zA-Z0-9._-]+)$ {
    # The regex capture group already restricts the allowed characters
    alias /data/media/$1/$2;
    try_files "" =404;
}

Practical Configuration Patterns #

Multi-Location with a Mix of root and alias #

server {
    listen 443 ssl;
    server_name example.com;

    # global root — for files in the public/ dir
    root /var/www/example.com/public;

    # ─── Main HTML, JS, CSS ────────────────────────────────
    location / {
        try_files $uri $uri/ /index.html;  # SPA fallback
    }

    # ─── Versioned assets with hashes ───────────────────────
    location /assets/ {
        # Files live at /var/www/assets/ (outside the root)
        alias /var/www/assets/;
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # ─── User uploads ───────────────────────────────────
    location /uploads/ {
        alias /mnt/storage/uploads/;
        # Only allow access to image files
        location ~* \.(jpg|jpeg|png|gif|webp|svg)$ {
            expires 7d;
            add_header Cache-Control "public";
        }
        # Block all other files (PHP, HTML, etc.)
        location ~ {
            deny all;
        }
    }

    # ─── Documentation (separate directory) ───────────────────
    location /docs/ {
        alias /opt/docs/html/;
        try_files $uri $uri/ =404;
    }

    # ─── API → backend ──────────────────────────────────────
    location /api/ {
        proxy_pass http://localhost:3000/;  # trailing slash matters here!
        # With trailing slash: GET /api/users → backend receives GET /users
        # Without trailing slash: GET /api/users → backend receives GET /api/users
    }
}

Debugging: Why Is the File Not Found? #

When you get a confusing 404, the most useful tool is enabling detailed logging and understanding which path Nginx is currently looking for.

Verifying the Path Nginx Builds #

The most direct way is using Nginx variables to see the constructed path:

# Temporarily in development — DON'T in production
server {
    root /var/www/html;

    location /assets/ {
        alias /var/www/static/;

        # Log the path being searched for every request
        # (debugging only, turn off when done)
        add_header X-Debug-Path $document_root$uri;
        # X-Debug-Path will show the path built by root
        # (alias isn't directly reflected in $document_root)
    }
}

A more informative way: use error_log at debug level:

# Set the error log to debug in nginx.conf temporarily
error_log /var/log/nginx/debug.log debug;

# Then request the problematic URL and watch the log
tail -f /var/log/nginx/debug.log | grep "open()"

# Useful example output:
# [debug] ... open() "/var/www/static/photo.jpg" (file doesn't exist)
# [debug] ... open() "/var/www/html/assets/photo.jpg" (file doesn't exist)
# → from here we know which path is being searched

root vs alias Debugging Checklist #

When facing an unexpected 404, follow this checklist:

# 1. Check whether the file really exists at the expected path
ls -la /var/www/static/photo.jpg         # for alias
ls -la /var/www/html/assets/photo.jpg    # for root at location /assets/

# 2. Check Nginx's access permissions
sudo -u www-data ls /var/www/static/photo.jpg
# If "Permission denied" → permission problem, not a path problem

# 3. Check the active Nginx configuration
nginx -T | grep -A 10 "location /assets"
# Make sure the root or alias you see matches expectations

# 4. Check with curl and look at the debug headers
curl -v http://example.com/assets/photo.jpg 2>&1 | grep -E "< HTTP|< X-Debug"

# 5. Check the error log
tail -f /var/log/nginx/error.log | grep "open()"

The Most Common Path Mistakes #

Situation: the file exists at /var/www/static/photo.jpg
           accessed via GET /assets/photo.jpg

❌ Wrong configuration (using root):
   location /assets/ { root /var/www/static; }
   → Nginx looks for: /var/www/static/assets/photo.jpg  ← DOUBLE ASSETS
   → File doesn't exist → 404

✅ Correct configuration (using alias):
   location /assets/ { alias /var/www/static/; }
   → Nginx looks for: /var/www/static/photo.jpg ✓

✅ Correct configuration (using root with the right directory structure):
   location /assets/ { root /var/www; }
   → Nginx looks for: /var/www/assets/photo.jpg ✓
   → The file must exist at /var/www/assets/photo.jpg (not /var/www/static/)

Quick Guide: root or alias? #

Use this decision tree when in doubt:

flowchart TD
    A["Need to determine\nthe file location?"] --> B{"Is the directory name on disk\nthe same as the URL path?"}
    B -- Yes --> C["Use root\nin the server block\n(inherited by all locations)"]
    B -- No --> D{"Is the file in the\ncurrent root?"}
    D -- Yes --> E["Change the directory structure\nto match the URL,\nthen use root"]
    D -- No --> F["Use alias\ninside the location block"]
    F --> G["Make sure the trailing slash\nis consistent between\nlocation and alias"]

Summary #

  • root: file path = root value + full URI. The entire URI is appended to the root.
  • alias: file path = alias value + the URI after the matched location part. The URI part that matched the location is replaced by the alias.
  • Use root at the server block level for the common case — inherited by all locations without redefining.
  • Use alias inside a location block when the directory name on disk differs from the URL, or when files live in a location separate from the web root.
  • Trailing slashes must be consistent: location /path/ + alias /dir/ or location /path + alias /dir — don’t mix.
  • alias is not valid at the server block level — only in location.
  • If you see a nonsensical 404 with root, try switching to alias — that’s most likely the problem.

← Previous: Virtual Host   Next: Index & Autoindex →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact