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¶
- Serving static content (HTML, CSS, JavaScript, images)
- Reverse proxy for application servers
- Load balancing across multiple servers
- SSL/TLS termination
- HTTP caching
- 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¶
Installing on Windows¶
- Download Nginx from nginx.org
- Extract the zip file to
C:\\nginx - Open Command Prompt as Administrator
-
Navigate to the Nginx directory:
-
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)
- Main Context: Top-level directives
- Events Context: Connection processing configuration
- HTTP Context: HTTP server configuration
- Server Context: Virtual host configuration
- 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¶
- Directives end with semicolons (
;) - Blocks use curly braces (
{ }) - Comments start with
# - Include files with
includedirective - Variables start with
$
Testing Configuration¶
4: Your First Nginx Server: Serving Static HTML Pages¶
Creating Your First Web Page¶
-
Create a directory for your website:
-
Create an HTML file:
-
Add content:
Configuring Nginx¶
-
Create a server block configuration:
-
Add configuration:
-
Enable the site (Ubuntu/Debian):
-
Remove default site (optional):
-
Test and reload:
Setting Proper Permissions¶
Accessing Your Site¶
Open a browser and visit:
Adding More Pages¶
Create additional HTML files:
Access them at:
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
Apache
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¶
- Scalability: Handle thousands of concurrent connections
- Low Memory: No process per connection
- Fast: No context switching overhead
- 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¶
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¶
Common Signals:
TERM,INT: Fast shutdownQUIT: Graceful shutdownHUP: Reload configurationUSR1: Reopen log filesUSR2: Upgrade binaryWINCH: 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
- Always test before reloading:
sudo nginx -t - Use reload, not restart: Avoids downtime
- Monitor logs: Check for errors after changes
- Graceful shutdown: Use
quitinstead ofstopwhen 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:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Site 1</title>
</head>
<body>
<h1>Success! Site1.com is working!</h1>
</body>
</html>
Site 2:
<!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¶
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¶
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)¶
Uncomment or add in the http block:
Test and Reload¶
Configure Local Testing (Optional)¶
Edit your hosts file for local testing:
Add:
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
- Use separate log files for each site
- Keep configurations in sites-available and symlink to sites-enabled
- Use meaningful names for configuration files
- Always test before reloading:
nginx -t - 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:
192.168.1.100- Client IP address-
- Remote user (usually empty)
-
- Authenticated user (usually empty)
[24/Mar/2026:10:15:30 +0000]- Timestamp"GET /index.html HTTP/1.1"- Request method, URI, and protocol200- HTTP status code1234- Response size in bytes"-"- Referer (page that linked here)"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¶
Levels (from least to most verbose):
emerg- Emergency (system unusable)alert- Alert (action must be taken)crit- Criticalerror- Error (default)warn- Warningnotice- Noticeinfo- Informationaldebug- 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:
Top 10 IP addresses:
Top 10 requested URLs:
Find 404 errors:
Requests by hour:
Bandwidth by IP:
Log Rotation¶
Nginx automatically supports log rotation. Configure with logrotate:
/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 dailyrotate 14- Keep 14 days of logscompress- Compress old logspostrotate- 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
- Use separate logs for each virtual host
- Set appropriate log levels (don't use debug in production)
- Implement log rotation to prevent disk space issues
- Monitor error logs regularly
- Use structured formats (JSON) for automated parsing
- Disable logging for health checks to reduce noise
- 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)