| |

LFCE 46 🐧 Nginx — Basic Configuration

⏱️ Estimated read: 12 min | |

Nginx configuration is built from blocks. The http block contains one or more server blocks, and each server block represents a virtual host—a site that nginx can serve. Inside a server block, location blocks define how different URL paths are handled. The directives inside these blocks control the listening port, the hostname the server responds to, the filesystem path where content is stored, and the files served when a directory is requested. A configuration that parses without error is not necessarily correct; a missing semicolon, a misplaced brace, or a server_name that does not match the request’s Host header produces a server that starts but answers incorrectly.

The previous chapter covered installation and the default welcome page. This chapter covers the configuration that replaces the default with a working site: the server block, the listen and server_name directives that determine which requests the block handles, the root and index directives that determine what content is served, and the location block that maps URL paths to filesystem paths. It also covers the validation command that prevents a syntax error from becoming an outage.

Key point: Nginx selects the server block by matching the request’s Host header against the server_name directive, and it selects the location block by matching the request’s URI against the location patterns. The listen directive determines the port and address, and the default_server parameter on listen designates the block that handles requests that match no other server name. Always run nginx -t before reloading; a configuration error prevents the new configuration from being applied, and the old configuration continues to run only if the reload is rejected.


Why basic configuration requires understanding block selection

The server-block selection problem.

A single nginx instance can serve many sites on the same IP address and port. When a request arrives, nginx reads the Host header and compares it to the server_name directives in every server block that listens on the matching address and port . If a request arrives for www.example.com and one block has server_name www.example.com;, that block handles the request. If no block matches, the request goes to the default server for that port, which is the first block unless another block has default_server on its listen directive . This is why the server_name directive must match the hostname that clients actually use, and why the default_server parameter is a deliberate choice rather than an accident.

The root-and-index problem.

The root directive tells nginx where the site’s files live on the filesystem. The index directive tells it which file to serve when the request is for a directory. A request for http://example.com/ with root /var/www/example; and index index.html; serves /var/www/example/index.html. A request for http://example.com/about.html serves /var/www/example/about.html . If the root path is wrong, nginx returns a 404 even though the configuration is syntactically valid. If the index file does not exist, nginx returns a 403 or a directory listing depending on the autoindex setting.

The location-matching problem.

The location block matches the URI portion of the request. A location / block matches every request that no more specific location matches. A location /images/ block matches requests whose URI begins with /images/. The matching is prefix-based by default, but the = modifier forces an exact match, the ~ modifier uses a case-sensitive regular expression, and the ~* modifier uses a case-insensitive regular expression . The order of evaluation is: exact match first, then the longest prefix match, then regular expressions in the order they appear in the configuration. A location block that does not match the expected requests is usually a matter of prefix versus exact versus regex.


a. The server block and the listen directive

The server block is the container for a virtual host. The listen directive inside it specifies the address and port that nginx binds to for that block.

server {
    listen 80;                          # Listen on port 80, all addresses
    server_name example.com www.example.com;

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

    location / {
        try_files $uri $uri/ =404;      # Serve file, then directory, then 404
    }
}

The listen 80; directive binds the block to port 80 on all interfaces. The listen 80 default_server; form designates the block as the default for port 80, meaning it handles requests that do not match any other block’s server_name . The listen 192.168.1.10:80; form binds only to a specific address, which is useful on a multi-homed host.

The server_name directive accepts multiple hostnames separated by spaces. A request with a Host header matching any of them is handled by this block. The first block in the configuration is the default for its port unless another block declares default_server .


b. The root, index, and location directives

The root directive sets the filesystem base path for the site. The index directive sets the file that is served when the request is for a directory. The location directive maps URI patterns to configuration.

server {
    listen 80;
    server_name example.com;

    root /var/www/example;              # Base path for all requests
    index index.html index.htm;         # Files to try for directory requests

    # Match the root path
    location / {
        try_files $uri $uri/ =404;      # Try file, then directory, then 404
    }

    # Match requests beginning with /images/
    location /images/ {
        # Inherits root from server block
        expires 30d;                    # Add cache headers
    }

    # Exact match for a specific path
    location = /health {
        return 200 "OK\n";              # Health check endpoint
    }

    # Case-insensitive regex for static assets
    location ~* \.(css|js|png|jpg)$ {
        expires 1h;
    }
}

The try_files $uri $uri/ =404; directive is the standard pattern for serving static files. It tries the exact file requested, then the path as a directory, and returns 404 if neither exists . This avoids the directory listing behavior that would otherwise occur when a request matches a directory with no index file.

The location = /health block uses the = modifier to match only the exact URI /health. The location ~* \.(css|js|png|jpg)$ block uses a case-insensitive regular expression to match requests for static assets, regardless of case in the file extension .


c. Validating and applying the configuration

The nginx -t command parses the configuration and reports syntax errors without affecting the running server. It is mandatory before reloading.

# Validate the configuration
sudo nginx -t
# Output: nginx: configuration file /etc/nginx/nginx.conf test is successful

# Reload to apply the changes without dropping connections
sudo nginx -s reload
# Or: sudo systemctl reload nginx

If nginx -t reports an error, the reload is not attempted, and the running configuration remains in effect. This is the safety mechanism that prevents a typo from taking the site down . The error message includes the file and line number, which points directly to the problem.

The nginx -s reload command sends the reload signal to the master process. The master checks the new configuration, starts new worker processes if the configuration is valid, and signals the old workers to finish their current requests and exit . The reload is graceful: active connections are not dropped.


Complete Example Session

# ============================================
# PART 1: CREATE THE SITE DIRECTORY
# ============================================
sudo mkdir -p /var/www/example
echo "<h1>Example Site</h1>" | sudo tee /var/www/example/index.html

# ============================================
# PART 2: CREATE THE SERVER BLOCK
# ============================================
sudo tee /etc/nginx/conf.d/example.com.conf > /dev/null <<'EOF'
server {
    listen 80;
    server_name example.com www.example.com;

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

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

    location = /health {
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }
}
EOF

# ============================================
# PART 3: DISABLE THE DEFAULT SITE (OPTIONAL)
# ============================================
# On Debian/Ubuntu, the default site may also listen on port 80
# Remove the symlink to prevent it from being the default server
sudo rm -f /etc/nginx/sites-enabled/default

# ============================================
# PART 4: VALIDATE THE CONFIGURATION
# ============================================
sudo nginx -t
# Output: nginx: configuration file /etc/nginx/nginx.conf test is successful

# ============================================
# PART 5: RELOAD NGINX
# ============================================
sudo systemctl reload nginx

# ============================================
# PART 6: TEST THE SITE LOCALLY
# ============================================
curl -H "Host: example.com" http://localhost
# Output: <h1>Example Site</h1>

# ============================================
# PART 7: TEST THE HEALTH ENDPOINT
# ============================================
curl -H "Host: example.com" http://localhost/health
# Output: OK

# ============================================
# PART 8: TEST A MISSING FILE
# ============================================
curl -I -H "Host: example.com" http://localhost/missing.html
# Output: HTTP/1.1 404 Not Found

# ============================================
# PART 9: VERIFY SERVER BLOCK SELECTION
# ============================================
# A request without a matching Host header goes to the default server
curl -I http://localhost
# Output depends on which block is the default

# ============================================
# PART 10: CHECK THE ACCESS LOG
# ============================================
sudo tail -5 /var/log/nginx/access.log
# Output shows the requests and their status codes

The ten parts demonstrate the complete configuration workflow: create content, write the server block, disable the default site, validate, reload, test the root path, test the health endpoint, test a 404, verify server-block selection, and inspect the access log. The critical step is Part 4: nginx -t catches syntax errors before the reload, and the running configuration remains in effect if the validation fails.


Quick Reference

Server Block Directives

DirectivePurposeExample
listenAddress and portlisten 80;
listen ... default_serverDefault for that portlisten 80 default_server;
server_nameHostnames to matchserver_name example.com;
rootDocument root pathroot /var/www/example;
indexDefault index filesindex index.html;
locationURI matchinglocation / { ... }
try_filesFile resolution ordertry_files $uri $uri/ =404;

Location Modifiers

ModifierMatch TypeExample
(none)Prefixlocation /images/
=Exactlocation = /health
~Case-sensitive regexlocation ~ \.php$
~*Case-insensitive regexlocation ~* \.(css|js)$

Validation and Control

CommandPurpose
nginx -tValidate configuration syntax
nginx -s reloadReload without dropping connections
systemctl reload nginxsystemd equivalent
curl -H "Host: ..."Test a specific server block

Best Practices

✅ Do This:

# Validate before reloading
nginx -t                                                      # ✅

# Use try_files to prevent directory listings
try_files $uri $uri/ =404;                                    # ✅

# Use exact match for specific endpoints
location = /health { return 200 "OK\n"; }                     # ✅

# Set root at the server level and inherit it
root /var/www/example;                                        # ✅

# Explicitly set default_server when multiple blocks listen on 80
listen 80 default_server;                                     # ✅

❌ Don’t Do This:

# Forget the semicolon after a directive
listen 80    # Syntax error; nginx -t reports it                # ❌

# Omit the Host header in local tests
curl http://localhost    # May hit the wrong server block     # ❌

# Use a regex location without a fallback
location ~ \.php$ { ... }    # Non-PHP requests have no block  # ❌

# Set root inside a location that inherits from server
location / { root /other; }    # Overrides server root        # ❌

# Reload without validation
systemctl reload nginx    # A broken config is rejected       # ❌

Common Pitfalls

PitfallWhy It HappensFix
Site returns 404root path wrong or file missingVerify the path and file permissions
Wrong site servedserver_name mismatch or default serverCheck Host header; use default_server
nginx -t failsMissing semicolon or braceRead the error; it includes the file and line
Directory listing appearsNo index file and no try_filesAdd try_files $uri $uri/ =404;
Reload does not apply changesValidation failedFix the error and reload again

Real-World Examples

1. Minimal server block

server {
    listen 80;
    server_name example.com;
    root /var/www/example;
    index index.html;
}

2. Default server

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

3. Static file location

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

4. Exact health endpoint

location = /health {
    return 200 "OK\n";
    add_header Content-Type text/plain;
}

5. Regex for static assets

location ~* \.(css|js|png|jpg)$ {
    expires 1h;
}

6. Validate configuration

nginx -t

7. Reload nginx

systemctl reload nginx

8. Test with Host header

curl -H "Host: example.com" http://localhost

9. Check access log

tail -5 /var/log/nginx/access.log

10. Disable default site (Debian)

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

Visual

Server Block Selection

┌──────────────────────────────────────────────────────────────┐
│  NGINX SELECTS THE SERVER BLOCK BY Host HEADER               │
│                                                              │
│  Request: GET / HTTP/1.1                                     │
│  Host: example.com                                           │
│       │                                                      │
│       ▼                                                      │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ server { listen 80; server_name example.com; }         │  │
│  │       → MATCH                                          │  │
│  └────────────────────────────────────────────────────────┘  │
│                                                              │
│  Request: GET / HTTP/1.1                                     │
│  Host: unknown.com                                           │
│       │                                                      │
│       ▼                                                      │
│  ┌────────────────────────────────────────────────────────┐  │
│  │ No server_name match                                   │  │
│  │ → default_server for port 80                           │  │
│  │ → first block (or the one with default_server)         │  │
│  └────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────┘

Location Matching Order

┌──────────────────────────────────────────────────────────────┐
│  LOCATION MATCHING: EXACT, PREFIX, REGEX                     │
│                                                              │
│  1. Exact match (=)                                          │
│     location = /health → only /health                        │
│                                                              │
│  2. Longest prefix match                                     │
│     location /images/ → /images/, /images/logo.png           │
│     location / → everything else                             │
│                                                              │
│  3. Regex match (~ or ~*)                                    │
│     location ~* \.(css|js)$ → /app.css, /main.js             │
│                                                              │
│  The most specific match wins.                               │
└──────────────────────────────────────────────────────────────┘

Validation and Reload Flow

┌──────────────────────────────────────────────────────────────┐
│  VALIDATE BEFORE RELOADING                                   │
│                                                              │
│  Edit configuration                                          │
│       │                                                      │
│       ▼                                                      │
│  nginx -t                                                    │
│       │                                                      │
│       ├── Error → Fix file, repeat                           │
│       │                                                      │
│       └── Success → nginx -s reload                          │
│                           │                                  │
│                           ▼                                  │
│                     New workers start                        │
│                     Old workers finish requests              │
│                     No dropped connections                   │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
Server blockVirtual host container
listenAddress and port
default_serverFallback block for unmatched requests
server_nameHostnames matched against Host header
rootDocument root path
indexDefault file for directory requests
locationURI matching block
try_filesFile resolution order
Validationnginx -t
Reloadnginx -s reload or systemctl reload nginx

Key takeaways:

  • The server block is selected by the Host header. Nginx matches the request’s Host against the server_name directives in every block that listens on the matching address and port. If no block matches, the request goes to the default server for that port .
  • default_server is a property of the listen port. It designates the block that handles requests that match no other server name. Without it, the first block for that port is the default .
  • root and index determine what is served. The root directive sets the filesystem base path, and index sets the file served for directory requests. A wrong path produces a 404 even though the configuration is valid .
  • location matching is ordered. Exact matches are evaluated first, then the longest prefix match, then regular expressions. The = modifier forces an exact match, ~ uses a case-sensitive regex, and ~* uses a case-insensitive regex .
  • try_files prevents directory listings. The pattern try_files $uri $uri/ =404; tries the exact file, then the directory, and returns 404 if neither exists. Without it, a request for a directory with no index file may produce a listing.
  • nginx -t is mandatory before reloading. The validation command catches syntax errors before the new configuration is applied. If it fails, the reload is not attempted, and the running configuration remains in effect .

Remember: Nginx configuration is a set of nested blocks. The http block contains server blocks, and each server block contains location blocks. The listen and server_name directives determine which requests the block handles. The root and index directives determine what content is served. The location directives determine how different URI paths are handled. Validate with nginx -t, reload with nginx -s reload, and test with curl using the correct Host header. A configuration that parses is not necessarily a configuration that serves the right site; the test confirms both.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!