Tutorial

Nginx Configuration for Beginners: Server Blocks, SSL, Redirects, and Security

March 26, 2026

Back to Blog

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.

nginx.conf
Main context
http { }
HTTP context
server { }
Virtual host
location { }
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:

DirectoryPurpose
/etc/nginx/nginx.confMain 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)
# /etc/nginx/nginx.conf — Main configuration
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:

server {
  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:

DirectivePurpose
listen 80Accept connections on port 80 (HTTP) for IPv4
listen [::]:80Accept connections on port 80 for IPv6
server_nameWhich domain names this block responds to
rootThe filesystem directory containing your website files
indexDefault files to serve when a directory is requested
try_filesTry 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:

server {
  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.

ModifierTypePriorityExample
=Exact matchHighestlocation = /favicon.ico
^~Prefix (stops regex search)2ndlocation ^~ /images/
~Case-sensitive regex3rdlocation ~ \.php$
~*Case-insensitive regex3rdlocation ~* \.(jpg|png)$
(none)Prefix matchLowestlocation /api/
How Nginx selects a location: First, it checks all exact matches (=). 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.
# Exact match — only /health, not /healthcheck
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:

server {
  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;
}
TLS 1.0 and 1.1 are dead. All major browsers dropped support in 2020. Only allow TLSv1.2 and TLSv1.3. Setting 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

server {
  listen 80;
  server_name example.com www.example.com;
  return 301 https://$host$request_uri;
}

www to non-www (or vice versa)

# www → non-www
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

# Single path redirect
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;
}
301 vs 302: Use 301 (permanent) for SEO redirects — browsers and search engines cache these aggressively. Use 302 (temporary) for maintenance pages or A/B testing — they are not cached by search engines.

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.

# Security headers — add to your server block

# 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;
HeaderProtects AgainstImpact
X-Frame-OptionsClickjacking attacksEssential
X-Content-Type-OptionsMIME confusion attacksEssential
HSTSSSL stripping / downgradeEssential
Content-Security-PolicyXSS, injection attacksEssential
Referrer-PolicyInformation leakageRecommended
Permissions-PolicyAPI 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 compression — add to http context
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;
70%
Average size reduction with gzip level 5
80%
Average size reduction with Brotli level 6

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:

# Inside server or location block
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:

location /api/ {
  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;
}
Trailing slash matters! 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:

# In http context — custom log format
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:

$ sudo nginx -t
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
Always use 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

Panelica auto-generates optimized Nginx configurations for every domain using battle-tested templates. When you create a domain in Panelica, the system generates a complete server block with SSL, security headers, gzip/brotli compression, and logging — all pre-configured. PHP-FPM socket paths are automatically matched to the domain's selected PHP version. Reverse proxy mode, custom redirects, and per-domain access logs are configurable from the domain settings panel. Every configuration change is validated before being applied, ensuring that a misconfigured domain never affects others.

Configuration Checklist

  • Use sites-available and sites-enabled symlinks 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_size for file uploads
  • Use proxy_pass with proper headers for backend applications
  • Configure per-domain access and error logs
  • Always run nginx -t before reloading
  • Keep redirect chains short — never chain more than one 301
Share: