Skip to content

Beginner Level Nginx Article Series

1: Introduction to Nginx: What It Is and Why Use It

What is Nginx?

Nginx (pronounced "engine-x") is a high-performance web server, reverse proxy server, and load balancer. Created by Igor Sysoev in 2004, it was designed to solve the C10K problem—handling 10,000 concurrent connections efficiently.

Why Use Nginx?

  • High Performance: Handles thousands of concurrent connections with minimal resource usage
  • Low Memory Footprint: Uses an asynchronous, event-driven architecture
  • Versatility: Acts as a web server, reverse proxy, load balancer, and HTTP cache
  • Scalability: Powers some of the world's busiest websites
  • Simple Configuration: Easy-to-read configuration syntax

Common Use Cases

  1. Serving static content (HTML, CSS, JavaScript, images)
  2. Reverse proxy for application servers
  3. Load balancing across multiple servers
  4. SSL/TLS termination
  5. HTTP caching
  6. Media streaming

Who Uses Nginx?

Major companies including Netflix, Airbnb, Dropbox, WordPress.com, and GitHub rely on Nginx for their infrastructure.

Conclusion

Nginx is an essential tool for modern web infrastructure, offering performance, reliability, and flexibility for websites and applications of all sizes.


2: Installing Nginx on Different Operating Systems

Installing on Ubuntu/Debian

# Update package list
sudo apt update

# Install Nginx
sudo apt install nginx

# Start Nginx
sudo systemctl start nginx

# Enable Nginx to start on boot
sudo systemctl enable nginx

# Check status
sudo systemctl status nginx

Installing on CentOS/RHEL/Rocky Linux

# Install Nginx
sudo yum install nginx

# Or for newer versions
sudo dnf install nginx

# Start Nginx
sudo systemctl start nginx

# Enable on boot
sudo systemctl enable nginx

# Check status
sudo systemctl status nginx

Installing on macOS

# Using Homebrew
brew install nginx

# Start Nginx
brew services start nginx

# Or manually
nginx

Installing on Windows

  1. Download Nginx from nginx.org
  2. Extract the zip file to C:\\nginx
  3. Open Command Prompt as Administrator
  4. Navigate to the Nginx directory:

    cd C:\\nginx
    
  5. Start Nginx:

    start nginx
    

Verifying Installation

Open a browser and navigate to <http://localhost>. You should see the Nginx welcome page.


3: Understanding Nginx Configuration Files: Structure and Syntax

Main Configuration File Location

  • Linux: /etc/nginx/nginx.conf
  • macOS (Homebrew): /usr/local/etc/nginx/nginx.conf
  • Windows: C:\\nginx\\conf\\nginx.conf

Configuration Structure

Nginx configuration uses a simple, hierarchical structure with directives and blocks.

# Simple directive
worker_processes 1;

# Block directive (context)
events {
    worker_connections 1024;
}

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

        location / {
            root /var/www/html;
            index index.html;
        }
    }
}

Key Contexts (Blocks)

  1. Main Context: Top-level directives
  2. Events Context: Connection processing configuration
  3. HTTP Context: HTTP server configuration
  4. Server Context: Virtual host configuration
  5. Location Context: URI-specific configuration

Important Directives

# Worker processes (typically matches CPU cores)
worker_processes auto;

# Events block
events {
    worker_connections 1024;
}

# HTTP block
http {
    # MIME types
    include mime.types;
    default_type application/octet-stream;

    # Logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # Performance
    sendfile on;
    keepalive_timeout 65;

    # Server blocks
    server {
        listen 80;
        server_name localhost;

        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
        }
    }
}

Configuration Syntax Rules

  1. Directives end with semicolons (;)
  2. Blocks use curly braces ({ })
  3. Comments start with #
  4. Include files with include directive
  5. Variables start with $

Testing Configuration

# Test configuration syntax
sudo nginx -t

# Reload configuration
sudo nginx -s reload

4: Your First Nginx Server: Serving Static HTML Pages

Creating Your First Web Page

  1. Create a directory for your website:

    sudo mkdir -p /var/www/mysite
    
  2. Create an HTML file:

    sudo nano /var/www/mysite/index.html
    
  3. Add content:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>My First Nginx Site</title>
    </head>
    <body>
        <h1>Welcome to My Nginx Server!</h1>
        <p>This is my first static website served by Nginx.</p>
    </body>
    </html>
    

Configuring Nginx

  1. Create a server block configuration:

    sudo nano /etc/nginx/sites-available/mysite
    
  2. Add configuration:

    server {
        listen 80;
        server_name localhost;
    
        root /var/www/mysite;
        index index.html;
    
        location / {
            try_files $uri $uri/ =404;
        }
    }
    
  3. Enable the site (Ubuntu/Debian):

    sudo ln -s /etc/nginx/sites-available/mysite /etc/nginx/sites-enabled/
    
  4. Remove default site (optional):

    sudo rm /etc/nginx/sites-enabled/default
    
  5. Test and reload:

    sudo nginx -t
    sudo systemctl reload nginx
    

Setting Proper Permissions

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

Accessing Your Site

Open a browser and visit:

<http://localhost>

Adding More Pages

Create additional HTML files:

sudo nano /var/www/mysite/about.html

Access them at:

<http://localhost/about.html>

5: Nginx vs Apache: A Comprehensive Comparison

Architecture

Nginx

  • Event-driven, asynchronous architecture
  • Single thread handles multiple connections
  • Low memory footprint

Apache

  • Process-driven or thread-driven (depending on MPM)
  • Each connection can be a separate process/thread
  • Higher memory usage under heavy load

Performance

Nginx

  • Excels at serving static content
  • Handles high concurrent connections efficiently
  • Better for high-traffic sites

Apache

  • Good performance with dynamic content
  • Can slow down with many concurrent connections
  • Excellent for low-to-medium traffic sites

Configuration

Nginx

server {
    listen 80;
    server_name example.com;

    location / {
        root /var/www/html;
    }
}

Apache

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html
</VirtualHost>

Feature Comparison

Feature Nginx Apache
Static Content Excellent Good
Dynamic Content Good Excellent
Concurrent Connections Excellent Good
Memory Usage Low Higher
.htaccess Support No Yes
Module System Dynamic (newer) Extensive
Learning Curve Moderate Easy
Market Share ~33% ~31%

When to Use Nginx

  • High-traffic websites
  • Serving static content
  • Reverse proxy/load balancer
  • Microservices architecture
  • Resource-constrained environments

When to Use Apache

  • Shared hosting environments
  • Need .htaccess functionality
  • Extensive module requirements
  • Legacy applications
  • Dynamic content-heavy sites

Can You Use Both?

Yes! A common setup:

  • Nginx as reverse proxy (frontend)
  • Apache handles dynamic content (backend)
server {
    listen 80;

    location / {
        proxy_pass <http://localhost:8080>;  # Apache backend
    }

    location ~* \\.(jpg|jpeg|png|css|js)$ {
        root /var/www/static;  # Nginx serves static
    }
}

6: Understanding Nginx's Event-Driven Architecture

Traditional Web Server Model

Traditional servers like Apache (with prefork MPM) use a process-per-connection model:

  • Each connection requires a new process or thread
  • Context switching overhead
  • Higher memory consumption
  • Limited concurrent connections

Nginx's Approach

Nginx uses an asynchronous, event-driven, non-blocking architecture:

  • Single master process
  • Multiple worker processes
  • Each worker handles thousands of connections
  • Uses event notification systems (epoll, kqueue, select)

The Master-Worker Model

Master Process
├── Worker Process 1 (handles connections)
├── Worker Process 2 (handles connections)
├── Worker Process 3 (handles connections)
└── Worker Process N (handles connections)

Master Process:

  • Reads configuration
  • Manages worker processes
  • Handles signals

Worker Processes:

  • Handle actual connections
  • Process requests
  • Non-blocking I/O operations

Event Loop Explained

1. Accept new connection
2. Add to event queue
3. Process non-blocking operation
4. Move to next event
5. Return to completed operations

No waiting! While one request reads from disk, others are processed.

Configuration for Optimal Performance

# Set to number of CPU cores
worker_processes auto;

# Maximum connections per worker
events {
    worker_connections 1024;
    # Use efficient event method (auto-selected)
    use epoll;  # Linux
}

# Total concurrent connections = worker_processes × worker_connections

Real-World Example

With 4 worker processes and 1024 connections each:

  • Maximum concurrent connections: 4,096
  • Memory efficient
  • Low CPU usage

Benefits of Event-Driven Architecture

  1. Scalability: Handle thousands of concurrent connections
  2. Low Memory: No process per connection
  3. Fast: No context switching overhead
  4. Predictable: Consistent performance under load

Comparison Visualization

Apache (Process Model):

Connection 1 → Process 1 (4MB)
Connection 2 → Process 2 (4MB)
...
Connection 1000 → Process 1000 (4GB total!)

Nginx (Event Model):

Connections 1-1000 → Worker 1 (10MB)
Connections 1001-2000 → Worker 2 (10MB)
Total: 20MB for 2000 connections

7: Basic Nginx Commands and Service Management

Starting, Stopping, and Reloading

Linux (systemd)

# Start Nginx
sudo systemctl start nginx

# Stop Nginx
sudo systemctl stop nginx

# Restart Nginx
sudo systemctl restart nginx

# Reload configuration (no downtime)
sudo systemctl reload nginx

# Check status
sudo systemctl status nginx

# Enable on boot
sudo systemctl enable nginx

# Disable on boot
sudo systemctl disable nginx

Using Nginx Binary

# Start
sudo nginx

# Stop (fast shutdown)
sudo nginx -s stop

# Stop (graceful shutdown)
sudo nginx -s quit

# Reload configuration
sudo nginx -s reload

# Reopen log files
sudo nginx -s reopen

macOS (Homebrew)

# Start
brew services start nginx

# Stop
brew services stop nginx

# Restart
brew services restart nginx

Windows

# Start
start nginx

# Stop (graceful)
nginx -s quit

# Stop (fast)
nginx -s stop

# Reload
nginx -s reload

Testing Configuration

# Test configuration syntax
sudo nginx -t

# Test and print configuration
sudo nginx -T

# Test configuration in specific file
sudo nginx -t -c /path/to/nginx.conf

Viewing Nginx Information

# Version
nginx -v

# Version and configure options
nginx -V

# Help
nginx -h

Managing Logs

# View access log (real-time)
sudo tail -f /var/log/nginx/access.log

# View error log (real-time)
sudo tail -f /var/log/nginx/error.log

# View last 100 lines
sudo tail -n 100 /var/log/nginx/access.log

# Reopen log files (after log rotation)
sudo nginx -s reopen

Checking Active Connections

# Show active connections
ps aux | grep nginx

# Detailed process information
sudo nginx -T | grep worker_processes

Process Signals

# Get master process ID
cat /var/run/nginx.pid

# Send signal to master process
kill -SIGNAL <PID>

Common Signals:

  • TERM, INT: Fast shutdown
  • QUIT: Graceful shutdown
  • HUP: Reload configuration
  • USR1: Reopen log files
  • USR2: Upgrade binary
  • WINCH: Graceful shutdown of workers

Quick Reference Commands

# Daily operations
sudo nginx -t && sudo systemctl reload nginx  # Test and reload
sudo systemctl status nginx                    # Check if running
sudo tail -f /var/log/nginx/error.log         # Monitor errors

# Troubleshooting
sudo nginx -T                                  # Dump full config
ps aux | grep nginx                            # Check processes
netstat -tlnp | grep :80                      # Check port 80

Best Practices

  1. Always test before reloading: sudo nginx -t
  2. Use reload, not restart: Avoids downtime
  3. Monitor logs: Check for errors after changes
  4. Graceful shutdown: Use quit instead of stop when possible

8: Configuring Your First Virtual Host (Server Block)

What is a Virtual Host (Server Block)?

A server block allows you to host multiple websites on a single server. Each site has its own configuration while sharing the same Nginx installation.

Directory Structure Setup

# Create directories for two sites
sudo mkdir -p /var/www/site1.com/html
sudo mkdir -p /var/www/site2.com/html

# Set permissions
sudo chown -R $USER:$USER /var/www/site1.com/html
sudo chown -R $USER:$USER /var/www/site2.com/html

# Set proper read permissions
sudo chmod -R 755 /var/www

Create Sample Content

Site 1:

nano /var/www/site1.com/html/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Welcome to Site 1</title>
</head>
<body>
    <h1>Success! Site1.com is working!</h1>
</body>
</html>

Site 2:

nano /var/www/site2.com/html/index.html
<!DOCTYPE html>
<html>
<head>
    <title>Welcome to Site 2</title>
</head>
<body>
    <h1>Success! Site2.com is working!</h1>
</body>
</html>

Create Server Block for Site 1

sudo nano /etc/nginx/sites-available/site1.com
server {
    listen 80;
    listen [::]:80;

    root /var/www/site1.com/html;
    index index.html index.htm;

    server_name site1.com www.site1.com;

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

    access_log /var/log/nginx/site1.com.access.log;
    error_log /var/log/nginx/site1.com.error.log;
}

Create Server Block for Site 2

sudo nano /etc/nginx/sites-available/site2.com
server {
    listen 80;
    listen [::]:80;

    root /var/www/site2.com/html;
    index index.html index.htm;

    server_name site2.com www.site2.com;

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

    access_log /var/log/nginx/site2.com.access.log;
    error_log /var/log/nginx/site2.com.error.log;
}

Enable Server Blocks

# Create symbolic links
sudo ln -s /etc/nginx/sites-available/site1.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site2.com /etc/nginx/sites-enabled/

Adjust server_names_hash_bucket_size (if needed)

sudo nano /etc/nginx/nginx.conf

Uncomment or add in the http block:

http {
    ...
    server_names_hash_bucket_size 64;
    ...
}

Test and Reload

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Configure Local Testing (Optional)

Edit your hosts file for local testing:

sudo nano /etc/hosts

Add:

127.0.0.1 site1.com www.site1.com
127.0.0.1 site2.com www.site2.com

Server Block Directives Explained

server {
    # Port to listen on
    listen 80;
    listen [::]:80;  # IPv6

    # Document root
    root /var/www/site1.com/html;

    # Default files to serve
    index index.html index.htm index.php;

    # Domain names this server block responds to
    server_name site1.com www.site1.com;

    # URI matching
    location / {
        # Try files in order, return 404 if none found
        try_files $uri $uri/ =404;
    }

    # Custom logs
    access_log /var/log/nginx/site1.com.access.log;
    error_log /var/log/nginx/site1.com.error.log;
}

Testing Your Sites

Open a browser and visit:

Common Issues and Solutions

Issue: "Could not build optimal server_names_hash" Solution: Increase server_names_hash_bucket_size in nginx.conf

Issue: All sites show the same content Solution: Check server_name directives and DNS/hosts file

Issue: 403 Forbidden Solution: Check file permissions and ownership

Best Practices

  1. Use separate log files for each site
  2. Keep configurations in sites-available and symlink to sites-enabled
  3. Use meaningful names for configuration files
  4. Always test before reloading: nginx -t
  5. Document your server blocks with comments

9: Understanding Nginx Logs: Access and Error Logs

Log File Locations

Default Locations:

  • Access Log: /var/log/nginx/access.log
  • Error Log: /var/log/nginx/error.log

macOS (Homebrew):

  • /usr/local/var/log/nginx/

Windows:

  • C:\\nginx\\logs\\

Access Log Format

Default combined format example:

192.168.1.100 - - [24/Mar/2026:10:15:30 +0000] "GET /index.html HTTP/1.1" 200 1234 "-" "Mozilla/5.0..."

Breakdown:

  1. 192.168.1.100 - Client IP address
    • Remote user (usually empty)
    • Authenticated user (usually empty)
  2. [24/Mar/2026:10:15:30 +0000] - Timestamp
  3. "GET /index.html HTTP/1.1" - Request method, URI, and protocol
  4. 200 - HTTP status code
  5. 1234 - Response size in bytes
  6. "-" - Referer (page that linked here)
  7. "Mozilla/5.0..." - User agent (browser)

Configuring Access Logs

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

    # Default access log
    access_log /var/log/nginx/access.log main;

    server {
        listen 80;
        server_name example.com;

        # Site-specific access log
        access_log /var/log/nginx/example.com.access.log main;

        # Disable logging for specific location
        location /health-check {
            access_log off;
        }
    }
}

Custom Log Formats

# Detailed format with response time
log_format detailed '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct=$upstream_connect_time '
                    'uht=$upstream_header_time urt=$upstream_response_time';

# JSON format for log aggregation tools
log_format json escape=json '{'
    '"time": "$time_local",'
    '"remote_addr": "$remote_addr",'
    '"request": "$request",'
    '"status": $status,'
    '"body_bytes_sent": $body_bytes_sent,'
    '"request_time": $request_time,'
    '"http_referer": "$http_referer",'
    '"http_user_agent": "$http_user_agent"'
'}';

access_log /var/log/nginx/access.log json;

Error Log Levels

# Syntax
error_log /path/to/error.log [level];

Levels (from least to most verbose):

  1. emerg - Emergency (system unusable)
  2. alert - Alert (action must be taken)
  3. crit - Critical
  4. error - Error (default)
  5. warn - Warning
  6. notice - Notice
  7. info - Informational
  8. debug - Debug (very verbose)
# Different error logs for different purposes
error_log /var/log/nginx/error.log error;
error_log /var/log/nginx/debug.log debug;

server {
    # Server-specific error log
    error_log /var/log/nginx/example.com.error.log warn;
}

Viewing Logs in Real-Time

# View access log (live)
sudo tail -f /var/log/nginx/access.log

# View error log (live)
sudo tail -f /var/log/nginx/error.log

# View last 50 lines
sudo tail -n 50 /var/log/nginx/access.log

# View both simultaneously
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log

# Follow logs with highlighting (using multitail)
sudo multitail /var/log/nginx/access.log /var/log/nginx/error.log

Analyzing Logs

Count HTTP status codes:

awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

Top 10 IP addresses:

awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

Top 10 requested URLs:

awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

Find 404 errors:

grep ' 404 ' /var/log/nginx/access.log

Requests by hour:

awk '{print $4}' /var/log/nginx/access.log | cut -d: -f2 | sort | uniq -c

Bandwidth by IP:

awk '{ip[$1]+=$ 10} END {for (i in ip) print ip[i], i}' /var/log/nginx/access.log | sort -rn | head

Log Rotation

Nginx automatically supports log rotation. Configure with logrotate:

sudo nano /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
        fi
    endscript
}

Options explained:

  • daily - Rotate logs daily
  • rotate 14 - Keep 14 days of logs
  • compress - Compress old logs
  • postrotate - Reopen log files after rotation

Conditional Logging

# Don't log successful health checks
map $request_uri $loggable {
    /health-check 0;
    default 1;
}

server {
    access_log /var/log/nginx/access.log combined if=$loggable;
}
# Log only errors (4xx, 5xx)
map $status $loggable {
    ~^[23] 0;
    default 1;
}

access_log /var/log/nginx/errors.log combined if=$loggable;

Useful Variables for Logging

$remote_addr        # Client IP
$remote_user        # Authenticated username
$time_local         # Local time
$request            # Full request line
$status             # Response status
$body_bytes_sent    # Bytes sent to client
$http_referer       # Referer header
$http_user_agent    # User agent
$request_time       # Request processing time
$upstream_response_time  # Backend response time
$request_id         # Unique request ID (1.11.0+)

Best Practices

  1. Use separate logs for each virtual host
  2. Set appropriate log levels (don't use debug in production)
  3. Implement log rotation to prevent disk space issues
  4. Monitor error logs regularly
  5. Use structured formats (JSON) for automated parsing
  6. Disable logging for health checks to reduce noise
  7. Consider log aggregation tools for multiple servers

Common Error Log Entries

# Permission denied
[error] open() "/var/www/html/file.html" failed (13: Permission denied)

# File not found
[error] open() "/var/www/html/file.html" failed (2: No such file or directory)

# Upstream connection refused
[error] connect() to 127.0.0.1:8080 failed (111: Connection refused)

# Too many open files
[emerg] socket() failed (24: Too many open files)