Server Blocks Configuration in Nginx: Complete Guide

Introduction

Server blocks in Nginx are the equivalent of Apache's virtual hosts, enabling a single Nginx installation to host multiple websites, applications, and services on one physical server. This powerful feature allows you to serve different content based on the domain name requested by the client, making it possible to run unlimited websites on a single IP address efficiently.

Unlike Apache's virtual host system, Nginx uses server blocks with a more streamlined configuration syntax. Nginx's event-driven architecture combined with well-configured server blocks can handle thousands of concurrent connections while serving multiple websites simultaneously with minimal resource overhead.

This comprehensive guide covers everything you need to know about configuring server blocks in Nginx. You'll learn how to set up name-based and IP-based server blocks, configure SSL/TLS for HTTPS, implement advanced routing rules, optimize performance, secure your configurations, and troubleshoot common issues. Whether you're hosting a simple blog or managing a complex multi-site infrastructure, mastering server blocks is essential for effective Nginx administration.

Prerequisites

Before configuring server blocks, ensure you have:

  • Nginx installed and running on Ubuntu/Debian or CentOS/Rocky Linux
  • Root or sudo access to your server
  • Basic understanding of Nginx configuration syntax
  • Domain names with DNS configured to point to your server's IP
  • Familiarity with Linux file permissions and directory structure
  • Text editor knowledge (nano, vim, or other)
  • Understanding of basic networking and HTTP concepts
  • SSL/TLS certificates (if configuring HTTPS server blocks)

Understanding Server Blocks

Name-Based Server Blocks

Name-based server blocks allow multiple domains on a single IP address:

Client Request Flow:
1. Client requests http://example.com/page.html
2. Browser sends HTTP request with "Host: example.com" header
3. Nginx receives request on IP address 192.168.1.100:80
4. Nginx matches Host header against configured server_name directives
5. Nginx serves content from matching server block
6. If no match found, serves from default server block

Basic example:

# Both server blocks share the same IP
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
}

server {
    listen 80;
    server_name another.com www.another.com;
    root /var/www/another.com;
}

IP-Based Server Blocks

IP-based server blocks assign each site a unique IP address:

# Each server block has a different IP
server {
    listen 192.168.1.100:80;
    server_name example.com;
    root /var/www/example.com;
}

server {
    listen 192.168.1.101:80;
    server_name another.com;
    root /var/www/another.com;
}

Default Server Block

The default server block handles requests that don't match any configured server_name:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;  # Close connection without response
}

Server Block Directory Structure

Ubuntu and Debian

Nginx on Ubuntu/Debian uses sites-available/sites-enabled structure:

/etc/nginx/
├── nginx.conf                # Main configuration
├── sites-available/          # All server block configs
│   ├── default              # Default server block
│   ├── example.com          # Custom server block
│   └── another.com          # Another server block
├── sites-enabled/            # Enabled server blocks (symlinks)
│   ├── default -> ../sites-available/default
│   └── example.com -> ../sites-available/example.com
└── snippets/                 # Reusable configuration snippets

Workflow:

  1. Create configuration in sites-available/
  2. Enable with symlink to sites-enabled/
  3. Test and reload Nginx

CentOS, Rocky Linux, and AlmaLinux

Red Hat-based systems use a simpler approach:

/etc/nginx/
├── nginx.conf                # Main configuration
└── conf.d/                   # All *.conf files loaded
    ├── default.conf
    ├── example.com.conf
    └── another.com.conf

Workflow:

  1. Create .conf file in conf.d/
  2. Test and reload Nginx

Creating Your First Server Block

Step 1: Create Document Root Directory

Create the directory structure for your website:

# Ubuntu/Debian
sudo mkdir -p /var/www/example.com/html
sudo mkdir -p /var/www/example.com/logs

# CentOS/Rocky/AlmaLinux
sudo mkdir -p /usr/share/nginx/example.com/html
sudo mkdir -p /var/log/nginx/example.com

Step 2: Set Proper Permissions

Configure ownership and permissions:

# Ubuntu/Debian
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

# CentOS/Rocky/AlmaLinux
sudo chown -R nginx:nginx /usr/share/nginx/example.com
sudo chmod -R 755 /usr/share/nginx/example.com

On SELinux-enabled systems (CentOS/Rocky/AlmaLinux):

# Set correct SELinux context
sudo chcon -R -t httpd_sys_content_t /usr/share/nginx/example.com/html

# Make it permanent
sudo semanage fcontext -a -t httpd_sys_content_t "/usr/share/nginx/example.com/html(/.*)?"
sudo restorecon -Rv /usr/share/nginx/example.com

Step 3: Create Test Content

Create a simple index.html file:

# Ubuntu/Debian
sudo nano /var/www/example.com/html/index.html

# CentOS/Rocky/AlmaLinux
sudo nano /usr/share/nginx/example.com/html/index.html

Add the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome to Example.com</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        .container {
            background: white;
            padding: 60px 40px;
            border-radius: 12px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            max-width: 600px;
            text-align: center;
        }
        h1 {
            color: #009639;
            font-size: 2.5em;
            margin-bottom: 20px;
        }
        .info {
            background: #f0f8ff;
            padding: 20px;
            border-left: 4px solid #009639;
            margin: 30px 0;
            text-align: left;
        }
        .info strong {
            color: #009639;
        }
        p {
            color: #666;
            line-height: 1.6;
            margin: 15px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Welcome to Example.com</h1>
        <p>Your Nginx server block is configured correctly!</p>
        <div class="info">
            <strong>Server Information:</strong><br>
            Server Block: example.com<br>
            Document Root: /var/www/example.com/html<br>
            Status: Active and Running<br>
            Powered by: Nginx
        </div>
        <p>You can now upload your website content to this directory.</p>
    </div>
</body>
</html>

Step 4: Create Server Block Configuration

Ubuntu/Debian Configuration

Create the server block configuration file:

sudo nano /etc/nginx/sites-available/example.com

Add the basic configuration:

server {
    # Listen on IPv4 and IPv6
    listen 80;
    listen [::]:80;

    # Server name
    server_name example.com www.example.com;

    # Document root
    root /var/www/example.com/html;
    index index.html index.htm index.nginx-debian.html;

    # Logging
    access_log /var/www/example.com/logs/access.log;
    error_log /var/www/example.com/logs/error.log;

    # Main location block
    location / {
        try_files $uri $uri/ =404;
    }

    # Deny access to hidden files
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Enable the server block:

# Create symlink
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

CentOS/Rocky/AlmaLinux Configuration

Create the configuration file:

sudo nano /etc/nginx/conf.d/example.com.conf

Add the configuration:

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

    server_name example.com www.example.com;

    root /usr/share/nginx/example.com/html;
    index index.html index.htm;

    access_log /var/log/nginx/example.com/access.log;
    error_log /var/log/nginx/example.com/error.log;

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

    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Test and reload:

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Advanced Server Block Configuration

Multiple Domain Names

Host multiple domains pointing to the same content:

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

    # Multiple server names
    server_name example.com www.example.com example.net www.example.net example.org;

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

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

    # Log which domain was accessed
    access_log /var/log/nginx/example-access.log combined;
}

Wildcard Server Names

Match all subdomains:

server {
    listen 80;
    server_name *.example.com .example.com;

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

    # Log subdomain in access logs
    log_format subdomains '$host $remote_addr - $remote_user [$time_local] '
                         '"$request" $status $body_bytes_sent '
                         '"$http_referer" "$http_user_agent"';

    access_log /var/log/nginx/subdomains-access.log subdomains;

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

Subdomain Server Blocks

Create separate server blocks for different subdomains:

# Main domain
server {
    listen 80;
    server_name example.com www.example.com;

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

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

# Blog subdomain
server {
    listen 80;
    server_name blog.example.com;

    root /var/www/example.com/blog;
    index index.html index.php;

    access_log /var/log/nginx/blog.example-access.log;
    error_log /var/log/nginx/blog.example-error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

# API subdomain
server {
    listen 80;
    server_name api.example.com;

    root /var/www/example.com/api;

    access_log /var/log/nginx/api.example-access.log;
    error_log /var/log/nginx/api.example-error.log;

    # CORS headers for API
    add_header 'Access-Control-Allow-Origin' '*' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always;

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

Dynamic Subdomain Routing

Route subdomains dynamically based on directory structure:

server {
    listen 80;
    server_name ~^(?<subdomain>.+)\.example\.com$;

    root /var/www/subdomains/$subdomain;
    index index.html;

    # If directory doesn't exist, show error
    if (!-d /var/www/subdomains/$subdomain) {
        return 404;
    }

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

URL Redirection

Redirect www to non-www (or vice versa):

# Redirect www to non-www
server {
    listen 80;
    listen [::]:80;
    server_name www.example.com;
    return 301 http://example.com$request_uri;
}

server {
    listen 80;
    listen [::]:80;
    server_name example.com;

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

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

Alternative using if statement (less preferred):

server {
    listen 80;
    server_name example.com www.example.com;

    # Redirect www to non-www
    if ($host = www.example.com) {
        return 301 http://example.com$request_uri;
    }

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

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

Custom Error Pages

Configure custom error pages per server block:

server {
    listen 80;
    server_name example.com;

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

    # Custom error pages
    error_page 400 /errors/400.html;
    error_page 401 /errors/401.html;
    error_page 403 /errors/403.html;
    error_page 404 /errors/404.html;
    error_page 500 502 503 504 /errors/50x.html;

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

    # Location for error pages
    location ^~ /errors/ {
        internal;
        root /var/www/example.com/html;
    }
}

Create custom error pages:

sudo mkdir -p /var/www/example.com/html/errors
sudo nano /var/www/example.com/html/errors/404.html

Example 404 page:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 - Page Not Found</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            margin: 0;
            padding: 20px;
        }
        .error-container {
            text-align: center;
            color: white;
        }
        h1 {
            font-size: 120px;
            margin: 0;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
        }
        h2 {
            font-size: 36px;
            margin: 20px 0;
        }
        p {
            font-size: 18px;
            margin: 20px 0;
        }
        a {
            display: inline-block;
            padding: 15px 30px;
            background: white;
            color: #667eea;
            text-decoration: none;
            border-radius: 30px;
            font-weight: bold;
            margin-top: 20px;
            transition: transform 0.3s;
        }
        a:hover {
            transform: scale(1.05);
        }
    </style>
</head>
<body>
    <div class="error-container">
        <h1>404</h1>
        <h2>Page Not Found</h2>
        <p>The page you're looking for doesn't exist or has been moved.</p>
        <a href="/">Return to Homepage</a>
    </div>
</body>
</html>

SSL/TLS Server Blocks

Configure HTTPS server blocks:

Basic HTTPS Server Block

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com www.example.com;

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

    # SSL certificates
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # SSL protocols and ciphers
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # SSL session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # HSTS header
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

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

    access_log /var/log/nginx/example-ssl-access.log;
    error_log /var/log/nginx/example-ssl-error.log;
}

# HTTP to HTTPS redirect
server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    return 301 https://$server_name$request_uri;
}

Let's Encrypt Configuration

After obtaining Let's Encrypt certificates:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com www.example.com;

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

    # Let's Encrypt certificates
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Strong SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # SSL session settings
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

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

    # Let's Encrypt ACME challenge
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/example.com/html;
    }
}

# HTTP to HTTPS redirect
server {
    listen 80;
    listen [::]:80;

    server_name example.com www.example.com;

    # Allow Let's Encrypt challenges
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/example.com/html;
    }

    # Redirect everything else to HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

SSL Configuration Snippet

Create reusable SSL configuration:

sudo nano /etc/nginx/snippets/ssl-params.conf

Add:

# SSL protocols
ssl_protocols TLSv1.2 TLSv1.3;

# SSL ciphers
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

# SSL session
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;

Use in server block:

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    # Include SSL parameters
    include snippets/ssl-params.conf;

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

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

Performance Optimization

Enable Compression

server {
    listen 80;
    server_name example.com;

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

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml
        image/svg+xml;
    gzip_disable "msie6";

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

Browser Caching

server {
    listen 80;
    server_name example.com;

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

    # Cache static files
    location ~* \.(jpg|jpeg|png|gif|ico|svg)$ {
        expires 365d;
        add_header Cache-Control "public, no-transform, immutable";
        access_log off;
    }

    location ~* \.(css|js)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    location ~* \.(woff|woff2|ttf|eot)$ {
        expires 365d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

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

FastCGI Cache for PHP

# Define FastCGI cache zone
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2
                   keys_zone=PHPCACHE:100m
                   inactive=60m
                   max_size=1g;

server {
    listen 80;
    server_name example.com;

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

    # Skip cache for certain conditions
    set $skip_cache 0;

    # POST requests and URLs with query strings
    if ($request_method = POST) {
        set $skip_cache 1;
    }
    if ($query_string != "") {
        set $skip_cache 1;
    }

    # Don't cache admin pages
    if ($request_uri ~* "/wp-admin/|/admin/") {
        set $skip_cache 1;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;

        # FastCGI cache
        fastcgi_cache PHPCACHE;
        fastcgi_cache_valid 200 60m;
        fastcgi_cache_valid 404 10m;
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;

        # Add cache status header
        add_header X-Cache-Status $upstream_cache_status;
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }
}

Security Configuration

Rate Limiting

# Define rate limit zone
limit_req_zone $binary_remote_addr zone=limitbyaddr:10m rate=10r/s;
limit_req_status 429;

server {
    listen 80;
    server_name example.com;

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

    location / {
        # Apply rate limit
        limit_req zone=limitbyaddr burst=20 nodelay;
        try_files $uri $uri/ =404;
    }

    # Different limit for API
    location /api/ {
        limit_req zone=limitbyaddr burst=10 nodelay;
        try_files $uri $uri/ =404;
    }
}

Connection Limiting

# Define connection limit zone
limit_conn_zone $binary_remote_addr zone=connlimit:10m;

server {
    listen 80;
    server_name example.com;

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

    # Limit concurrent connections
    limit_conn connlimit 10;

    location /download/ {
        # More restrictive for downloads
        limit_conn connlimit 2;
        limit_rate 500k;  # Bandwidth limit per connection
    }

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

IP-Based Access Control

server {
    listen 80;
    server_name admin.example.com;

    root /var/www/example.com/admin;

    # Allow specific IPs only
    allow 192.168.1.0/24;
    allow 10.0.0.100;
    allow 203.0.113.50;
    deny all;

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

Basic Authentication

server {
    listen 80;
    server_name secure.example.com;

    root /var/www/example.com/secure;

    # Basic authentication
    auth_basic "Restricted Area";
    auth_basic_user_file /etc/nginx/.htpasswd;

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

    # Disable auth for specific location
    location /public/ {
        auth_basic off;
    }
}

Create password file:

# Install htpasswd utility
sudo apt install apache2-utils  # Ubuntu/Debian
sudo yum install httpd-tools    # CentOS/Rocky

# Create password file
sudo htpasswd -c /etc/nginx/.htpasswd username

Security Headers

server {
    listen 80;
    server_name example.com;

    root /var/www/example.com/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;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

    # Remove server signature
    server_tokens off;

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

Reverse Proxy Server Block

Configure Nginx as reverse proxy:

upstream backend {
    least_conn;
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

server {
    listen 80;
    server_name app.example.com;

    access_log /var/log/nginx/app-access.log;
    error_log /var/log/nginx/app-error.log;

    location / {
        proxy_pass http://backend;

        # Proxy headers
        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;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # Buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        proxy_busy_buffers_size 8k;
    }

    # Static files served by Nginx
    location /static/ {
        alias /var/www/example.com/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

Logging Configuration

Custom Log Formats

# Define custom log formats
log_format main '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent"';

log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    '$request_time $upstream_response_time';

log_format json escape=json '{'
                '"time": "$time_local",'
                '"remote_addr": "$remote_addr",'
                '"request": "$request",'
                '"status": $status,'
                '"body_bytes_sent": $body_bytes_sent,'
                '"http_referer": "$http_referer",'
                '"http_user_agent": "$http_user_agent"'
                '}';

server {
    listen 80;
    server_name example.com;

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

    # Use custom log format
    access_log /var/log/nginx/example-access.log detailed;
    error_log /var/log/nginx/example-error.log warn;

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

Conditional Logging

server {
    listen 80;
    server_name example.com;

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

    # Map to determine if should log
    map $request_uri $loggable {
        ~*\.(gif|jpg|jpeg|png|css|js|ico|svg)$ 0;
        default 1;
    }

    # Don't log health checks
    map $http_user_agent $log_ua {
        ~*pingdom|uptime 0;
        default 1;
    }

    # Combine conditions
    set $log 0;
    if ($loggable) {
        set $log 1;
    }
    if ($log_ua = 0) {
        set $log 0;
    }

    access_log /var/log/nginx/example-access.log combined if=$log;

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

Troubleshooting

Test Configuration

Always test configuration before reloading:

sudo nginx -t

Expected output:

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

Check Server Block Configuration

# View all server blocks and their configurations
sudo nginx -T

# Check for specific server_name matches
sudo nginx -T | grep -A 10 "server_name example.com"

Common Issues

Issue: "conflicting server name"

# Check for duplicate server_name directives
sudo nginx -T | grep "server_name" | sort | uniq -d

# Solution: Ensure unique server_name or use default_server

Issue: Wrong server block served

# Check server block order and default_server
sudo nginx -T | grep -A 5 "listen.*default_server"

# Test with curl
curl -H "Host: example.com" http://your_ip/

Issue: Permission denied errors

# Check file permissions
ls -la /var/www/example.com/

# Fix permissions
sudo chown -R www-data:www-data /var/www/example.com  # Ubuntu/Debian
sudo chown -R nginx:nginx /usr/share/nginx/example.com  # CentOS/Rocky

# Check SELinux (CentOS/Rocky/AlmaLinux)
sudo getenforce
sudo chcon -R -t httpd_sys_content_t /usr/share/nginx/example.com/html

Issue: SSL certificate errors

# Verify certificate files exist
sudo ls -la /etc/letsencrypt/live/example.com/

# Test SSL configuration
sudo nginx -t

# Check certificate validity
sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -text -noout

Best Practices

Organized Configuration

Keep server block files organized:

# Ubuntu/Debian naming convention
/etc/nginx/sites-available/
├── default                # Default catch-all
├── example.com            # Main domain
├── blog.example.com       # Subdomain
└── api.example.com        # API subdomain

Use Include for Common Settings

# /etc/nginx/snippets/common-locations.conf
location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

location = /favicon.ico {
    access_log off;
    log_not_found off;
}

location = /robots.txt {
    access_log off;
    log_not_found off;
}

# Use in server block
server {
    listen 80;
    server_name example.com;
    root /var/www/example.com/html;

    include snippets/common-locations.conf;

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

Documentation

Document each server block:

#
# Server Block: example.com
# Created: 2024-01-15
# Modified: 2024-01-20
# Purpose: Main company website
# Contact: [email protected]
#

server {
    listen 80;
    server_name example.com www.example.com;
    # ...
}

Regular Backups

Backup server block configurations:

# Ubuntu/Debian
sudo tar -czf nginx-sites-backup-$(date +%F).tar.gz /etc/nginx/sites-available/

# CentOS/Rocky/AlmaLinux
sudo tar -czf nginx-conf-backup-$(date +%F).tar.gz /etc/nginx/conf.d/

Monitoring

Monitor server block access and errors:

# Real-time access log
sudo tail -f /var/log/nginx/example-access.log

# Real-time error log
sudo tail -f /var/log/nginx/example-error.log

# Count requests by status code
sudo awk '{print $9}' /var/log/nginx/example-access.log | sort | uniq -c | sort -nr

Conclusion

Server blocks are the foundation of multi-site Nginx hosting, enabling efficient management of multiple websites on a single server. This guide has covered everything from basic name-based server blocks to advanced SSL/TLS configurations, security hardening, performance optimization, and troubleshooting.

Key takeaways:

  • Name-based server blocks allow unlimited websites on one IP address with minimal resource overhead
  • Proper directory structure and permissions are critical for security and functionality
  • SSL/TLS server blocks with HTTP/2 provide modern, secure web hosting
  • Performance optimization through caching and compression improves user experience
  • Security features like rate limiting, access control, and security headers protect your sites
  • Regular testing, monitoring, and backups ensure reliable operation

Master server block configuration to efficiently host multiple websites, implement advanced routing patterns, and build scalable web infrastructures. Continue exploring advanced topics like dynamic server blocks with Lua, integration with load balancing, advanced caching strategies, and automated SSL certificate management with Certbot.