Understanding the Nginx Configuration File Structure
Nginx uses a hierarchical configuration model built around nested contexts. Every directive lives inside a specific context, and understanding this hierarchy is fundamental to writing correct configurations. If you have ever stared at an Nginx config file wondering why your changes do not take effect, the answer is almost always a misplaced directive.
Main context
HTTP context
Virtual host
URL matching
The main configuration file is typically /etc/nginx/nginx.conf. It defines global settings and includes other configuration files. On Ubuntu systems, the conventional layout uses two directories:
| Directory | Purpose |
|---|---|
/etc/nginx/nginx.conf | Main config — worker processes, events, HTTP defaults |
/etc/nginx/sites-available/ | All server block configs (available but not necessarily active) |
/etc/nginx/sites-enabled/ | Symlinks to active configs in sites-available |
/etc/nginx/conf.d/ | Additional config snippets (always loaded) |
/etc/nginx/snippets/ | Reusable config fragments (SSL, security headers) |
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Server Blocks: Virtual Hosts
Server blocks are Nginx's equivalent of Apache's VirtualHost. Each server block defines how Nginx handles requests for a specific domain or IP address. This is how a single server can host multiple websites.
Basic Server Block
Here is the simplest possible server block that serves static files for a domain:
listen 80;
listen [::]:80; # IPv6
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
Every directive here has a specific purpose. Let us examine each one:
| Directive | Purpose |
|---|---|
listen 80 | Accept connections on port 80 (HTTP) for IPv4 |
listen [::]:80 | Accept connections on port 80 for IPv6 |
server_name | Which domain names this block responds to |
root | The filesystem directory containing your website files |
index | Default files to serve when a directory is requested |
try_files | Try the URI as a file, then as a directory, then return 404 |
PHP Server Block
For PHP applications (WordPress, Laravel, etc.), you need to pass PHP files to PHP-FPM for processing:
listen 80;
server_name example.com;
root /var/www/example.com/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.ht {
deny all; # Block .htaccess files
}
}
Location Blocks: URL Matching
Location blocks are where Nginx makes its routing decisions. Understanding location matching priority is essential — incorrect ordering is one of the most common Nginx configuration mistakes.
| Modifier | Type | Priority | Example |
|---|---|---|---|
= | Exact match | Highest | location = /favicon.ico |
^~ | Prefix (stops regex search) | 2nd | location ^~ /images/ |
~ | Case-sensitive regex | 3rd | location ~ \.php$ |
~* | Case-insensitive regex | 3rd | location ~* \.(jpg|png)$ |
| (none) | Prefix match | Lowest | location /api/ |
=). If found, it stops. Next, it evaluates prefix matches and remembers the longest one. If that longest prefix has ^~, it stops. Otherwise, it checks all regex locations in order and uses the first match. If no regex matches, it uses the longest prefix match.
location = /health {
return 200 "OK";
add_header Content-Type text/plain;
}
# Prefix — stops regex evaluation for /static/*
location ^~ /static/ {
alias /var/www/myapp/static/;
expires 30d;
}
# Regex — case-insensitive match for image files
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
# Prefix — catch-all for everything else
location / {
try_files $uri $uri/ /index.php?$query_string;
}
SSL Configuration
In 2026, HTTPS is mandatory. Browsers mark HTTP sites as insecure, search engines penalize them, and modern web features (service workers, geolocation, WebRTC) require HTTPS. Here is a complete, modern SSL configuration:
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# Certificate files
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern TLS 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;
ssl_prefer_server_ciphers off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# Session optimization
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
root /var/www/example.com/public;
index index.html;
}
ssl_prefer_server_ciphers off is recommended with modern TLS because TLS 1.3 handles cipher negotiation securely on the client side.
Redirect Rules
Redirects are one of the most common Nginx tasks. Whether you are enforcing HTTPS, handling www/non-www normalization, or redirecting old URLs, here are the patterns you need:
HTTP to HTTPS Redirect
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
www to non-www (or vice versa)
server {
listen 443 ssl http2;
server_name www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
return 301 https://example.com$request_uri;
}
# non-www → www
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;
return 301 https://www.example.com$request_uri;
}
Path Redirects
location = /old-page {
return 301 /new-page;
}
# Regex redirect — all /blog/123 → /articles/123
location ~ ^/blog/(.*)$ {
return 301 /articles/$1;
}
# Temporary redirect (302) for maintenance
location / {
return 302 /maintenance.html;
}
Security Headers
Security headers instruct browsers to enable built-in protections against common attacks. Adding them to your Nginx configuration is one of the highest-impact, lowest-effort security improvements you can make.
# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;
# Enable XSS protection
add_header X-XSS-Protection "1; mode=block" always;
# Enforce HTTPS for 1 year
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Referrer policy
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Permissions policy
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;" always;
| Header | Protects Against | Impact |
|---|---|---|
| X-Frame-Options | Clickjacking attacks | Essential |
| X-Content-Type-Options | MIME confusion attacks | Essential |
| HSTS | SSL stripping / downgrade | Essential |
| Content-Security-Policy | XSS, injection attacks | Essential |
| Referrer-Policy | Information leakage | Recommended |
| Permissions-Policy | API abuse (camera, mic) | Recommended |
Gzip and Brotli Compression
Compression reduces the size of responses sent to the browser by 60-80%, dramatically improving page load times. Nginx supports both gzip (universal) and Brotli (modern, better compression).
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
font/woff2;
Client Body Size and Timeouts
By default, Nginx limits upload sizes to 1MB and has relatively conservative timeouts. For applications that handle file uploads, you need to adjust these:
client_max_body_size 100M; # Allow uploads up to 100MB
client_body_timeout 60s; # Wait 60s for request body
client_header_timeout 60s; # Wait 60s for request headers
send_timeout 60s; # Wait 60s between response writes
proxy_read_timeout 300s; # Wait 5min for backend response
Proxy Pass for Backend Applications
The proxy_pass directive is how Nginx forwards requests to backend applications running on Node.js, Python, Go, Ruby, or any other HTTP server:
proxy_pass http://127.0.0.1:3000/;
proxy_http_version 1.1;
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;
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
}
proxy_pass http://127.0.0.1:3000/; (with trailing slash) strips the /api/ prefix — a request to /api/users becomes /users on the backend. Without the trailing slash, the full path /api/users is forwarded.
Logging Configuration
Nginx logging provides visibility into every request your server handles. Customizing the log format and separating logs per domain makes debugging and monitoring significantly easier:
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
# In server block — per-domain logs
access_log /var/log/nginx/example.com.access.log detailed;
error_log /var/log/nginx/example.com.error.log warn;
Testing Your Configuration
Before reloading Nginx, always validate your configuration. A syntax error can take down every site on your server:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginx
nginx -t before systemctl reload. The reload command will fail silently if the configuration has errors, potentially leaving Nginx running with the old config — or worse, not running at all.
Nginx with Panelica
Configuration Checklist
- Use
sites-availableandsites-enabledsymlinks for clean organization - Understand location block priority: exact, prefix (^~), regex, prefix
- Always configure SSL with TLSv1.2+ and OCSP stapling
- Add security headers (HSTS, X-Frame-Options, CSP) to every server block
- Enable gzip compression for text-based content types
- Set appropriate
client_max_body_sizefor file uploads - Use
proxy_passwith proper headers for backend applications - Configure per-domain access and error logs
- Always run
nginx -tbefore reloading - Keep redirect chains short — never chain more than one 301