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:
- Create configuration in
sites-available/ - Enable with symlink to
sites-enabled/ - 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:
- Create .conf file in
conf.d/ - 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.


