Wisdom
  • Welcome
  • core
    • Flyway
    • Bean Validation
    • Lombok
    • Webclient
      • Generic Webclient Api
      • SSL Certificate
    • Application Event Publisher
    • REST API's Design
      • Http Methods and Status Codes
      • Resource Naming and URL Structure
      • Request / Response Design
      • Versioning Strategies
      • Filtering and Searching In API
    • Spring Boot Mail Integration
      • Sendgrid
    • Template Engines
      • Java Template Engine [JTE]
  • security
    • Complete Guide to URL Matchers in Spring Security: Types, Examples, Pros, Cons, and Best Use Cases
    • Passwordless Login With Spring Security 6
      • Spring Security OneTimeToken
        • One Time Token with default configuration
        • One Time Token with custom configuration
        • One Time Token With Jwt
  • others
    • How to Integrate WhatsApp for Sending Messages in Your Application
  • java
    • Interview Questions
      • Constructor
      • Serialization
      • Abstract Class
    • GSON
      • Type Token
      • Joda Datetime Custom Serializer and Deserializer
  • Nginx
    • Core Concepts and Basics
    • Deep Dive on NGINX Configuration Blocks
    • Deep Dive on NGINX Directives
    • Deep Dive into Nginx Variables
    • Nginx as a Reverse Proxy and Load Balancer
    • Security Hardening in NGINX
    • Performance Optimization & Tuning in NGINX
    • Dynamic DNS Resolution in Nginx
    • Advanced Configuration & Use Cases in NGINX
    • Streaming & Media Delivery in NGINX
    • Final Configuration
  • Angular
    • How to Open a PDF or an Image in Angular Without Dependencies
    • Displaying Colored Logs with Search Highlighting in Angular 6
    • Implementing Auto-Suggestion in Input Field in Angular Template-Driven Forms
    • Creating an Angular Project Using npx Without Installing It Globally
    • Skip SCSS and Test Files in Angular with ng generate
  • Javascript
    • When JavaScript's Set Falls Short for Ensuring Uniqueness in Arrays of Objects
    • Demonstrating a Function to Get the Last N Months in JavaScript
    • How to Convert Numbers to Words in the Indian Numbering System Using JavaScript
    • Sorting Based on Multiple Criteria
  • TYPESCRIPT
    • Using Omit in TypeScript
Powered by GitBook
On this page
  • ssl_params.conf
  • proxy_params.conf
  • common-denied.conf
  • security_headers.conf
  • nginx.conf
  • app.conf
  1. Nginx

Final Configuration

Here is the detailed configuration of nginx with best practices , optimization and security concerns.

We have divided the configuration in multiple files, that are -

  1. nginx.conf - main configuration file for nginx

  2. app.conf - App specific file that need to be served.

  3. ssl_params.conf - Common file for SSL headers

  4. security_headers.conf - Reusable file for security related headers

  5. proxy_params.conf - Reusable file for proxy params.

  6. common-denied.conf - Reusable file for common file like 404 or 505 or access denied.

We need to create config folder manually . other wise change the path while including.

ssl_params.conf

To use this file we must need to configure ssl certificate first for the domain name.

# /config/ssl_params.conf
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols       TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
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_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

proxy_params.conf

Common params and headers used in reverse proxy

# /config/proxy_params.conf
proxy_pass_request_headers on;
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_hide_header Server;

proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 10s;
send_timeout 10s;

proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k

common-denied.conf

File that contains 404 , or redirect or access denied that we can reuse in multiple blocks

#/config/common-denied.conf

if ($request_uri ~* "\.\.") {
    return 403;
}


# Block access to sensitive directories and version control folders
location ~* ^/(\.well-known/|\.git|\.svn|\.hg|\.bzr|\._darcs|BitKeeper) {
    deny all;
    return 404;
}

# Custom error page configuration.
error_page 404 /404.html;
    location = /404.html {
    internal;                         # Ensure direct access is not allowed.
}

# Deny requests with suspicious User-Agents
if ($bad_user_agent) { #defined in nginx.conf
    return 403;
}

# Custom error pages for server errors
error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
}

security_headers.conf

common security headers that can be used in multiple blocks-

# /config/security_headers.conf

 # HSTS: Enforce HTTPS (1 year) and include subdomains; add preload if desired
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;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;

add_header X-Permitted-Cross-Domain-Policies "none" always;

# to disable permissions of camera, microphone as they are not needed in most of th apps
add_header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()";

nginx.conf

The main configuration file -

# /etc/nginx/nginx.conf

# user  nginx;
worker_processes auto;
error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 20M;

    # Disable version disclosure for security
    server_tokens off;

    # Performance Optimizations
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;

    #buffers
    client_body_buffer_size 16K;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

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

    # Enable Gzip compression for text-based content
    gzip on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_vary on;


    # Generate a CSP nonce based on the request ID (for inline scripts)
    map $request_id $csp_nonce {
        "~*" $request_id;
    }

    # Map to identify suspicious or malicious User Agents
    map $http_user_agent $bad_user_agent {
        default 0;
        ~*^$ 1;               # Empty User-Agent
        ~*bot 1;              # Bots (note: some legitimate bots may be affected)
        ~*spider 1;
        ~*crawl 1;
        ~*[<>]script 1;
        ~*(nmap|nikto|wikto|sf|sqlmap|bsqlbf|w3af|acunetix|havij|appscan) 1;
    }

    # Define a shared memory zone for rate limiting (DDoS protection)
    limit_req_zone $binary_remote_addr zone=ddos_zone:10m rate=10r/s;

    # Include additional server configurations from conf.d folder
     include /etc/nginx/conf.d/*.conf;
}

app.conf

Configuration file for app specific that we are deploying. This file is having two server blocks one is for normal backend url and another is when we are using dns address like AWS load balancer.

# /proj/ideal-config.conf


###############################################################################
# Upstream definitions
###############################################################################

# backend servers, evenly distibutes loads
upstream backend_servers {
    server example.com max_fails=3 fail_timeout=30s;
    server example.com; # localhost:8080
}

upstream websocket_server{
    server example.com; # loclhost:90
}

###############################################################################
# 1. HTTP Server Block - Redirect all HTTP traffic to HTTPS
###############################################################################
server {
    listen 8000;
    server_name example.com www.example.com app.example.com secure.example.com;
    
    # Permanent redirection to HTTPS improves security and SEO
    return 301 https://$host$request_uri;
}

###############################################################################
# 2. HTTPS Server Block - Main configuration for example.com and www.example.com
###############################################################################
server {

    listen 443 ssl;
    server_name example.com www.example.com;

    # Use a public DNS resolver for upstream lookups
    resolver 8.8.8.8 valid=10s;
    resolver_timeout 5s;

     # Include common SSL configuration
    include /etc/nginx/config/ssl_params.conf;
    
    # Include common security headers
    include /etc/nginx/config/security_headers.conf;

    root D:/work/company/myproj/dist/myapp/browser/;
    index index.html index.htm index.nginx-debian.html;

     # Apply standard rate limiting
    limit_req zone=ddos_zone burst=20 nodelay;

    # adjust csp headers as per need
    # Content Security Policy (CSP) tailored for your site and trusted third-party services
    add_header Content-Security-Policy "default-src 'self'; \
      script-src 'self' 'unsafe-eval' 'nonce-$csp_nonce' https://maps.googleapis.com https://cdnjs.cloudflare.com; \
      style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
      font-src 'self' data: https://fonts.gstatic.com; \
      frame-src 'self' https://www.google.com https://example.com; \
      connect-src 'self' https://*.googleapis.com; \
      img-src 'self' data:; \
      frame-ancestors 'self'; \
      worker-src 'self' data: blob:; \
      base-uri 'self'; \
      form-action 'self'; \
      upgrade-insecure-requests;" always;

    
    # Replace placeholder for nonce in files (if sub_filter is needed)
    sub_filter_once off;
    sub_filter_types *;
    sub_filter "**CSP_NONCE**" "$csp_nonce";

    # including common things that need to be restricted
    include /etc/nginx/config/common-denied.conf;    

    set $allowed_origin http://backend_servers;

    # Rate Limiting for DDoS protection applied globally via http block
    location / {
        try_files $uri $uri/ /index.html; # Check file existence, return index.html if not found.

        if ($request_uri ~* ".(ico|json|css|js|gif|jpe?g|png)$") {
            expires 30d;
            access_log off;
            add_header Pragma public;
            add_header Cache-Control "public";
            add_header X-Content-Type-Options nosniff always;

            add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval' 'nonce-$csp_nonce' https://maps.googleapis.com https://www.google.com https://www.gstatic.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com;
                    frame-src 'self' https://www.google.com $allowed_origin; connect-src 'self' data:  https://*.googleapis.com wss://websocket_server; object-src 'none'; img-src 'self' data: ; frame-ancestors 'self' $allowed_origin; worker-src 'self' data: blob: ; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;

            break;
        }
    }

    # Reverse Proxy for dynamic content under /app/ (or any designated URI)
    location /app/ {

        # Block suspicious SQL injection patterns in the query string
        if ($query_string ~* "union.*select.*\(") {
            return 403;
        }

        proxy_pass http://backend_servers;

        include /etc/nginx/config/proxy_params.conf;
        
        add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval' 'nonce-$csp_nonce' https://maps.googleapis.com https://www.google.com https://www.gstatic.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
        font-src 'self' data: https://fonts.gstatic.com ; frame-src 'self' https://www.google.com $allowed_origin ; connect-src 'self' data:  https://*.googleapis.com wss://websocket_server; object-src 'none'; img-src 'self' data: ; frame-ancestors 'self' $allowed_origin; worker-src 'self' data: blob: ; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;
        
        # must be handle from backend, if not then uncomment
        #add_header 'Access-Control-Allow-Origin' '*';
        #add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        #add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';

        #if ($request_method = 'OPTIONS') {
        #    return 204;
        #}

    }


    location /ws/ {
        proxy_pass http://websocket_server;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }

    # allowing specific ip for senstive content
    location /sensitive/ {
        allow 192.168.1.0/24; # whitelisted ips
        deny all;
    }

    # debug error log for troubleshooting
    error_log /var/log/nginx/app_error.log debug;
}

###############################################################################
# 3. HTTPS Server Block - Subdomain Example (app.example.com) with alb dns address
###############################################################################
server {

    listen 443 ssl;
    server_name example2.com www.example2.com;

    resolver 10.0.0.2 valid=30s ipv6=off;
    resolver_timeout 5s;

     # Include common SSL configuration
    include /etc/nginx/config/ssl_params.conf;
    
    # Include common security headers
    include /etc/nginx/config/security_headers.conf;

    root D:/work/company/myproj/dist/myapp/browser/;
    index index.html index.htm index.nginx-debian.html;

     # Apply standard rate limiting
    limit_req zone=ddos_zone burst=20 nodelay;
        
    # adjust csp headers as per need    

    add_header Content-Security-Policy "default-src 'self'; \
      script-src 'self' 'unsafe-eval' 'nonce-$csp_nonce' https://maps.googleapis.com https://cdnjs.cloudflare.com; \
      style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
      font-src 'self' data: https://fonts.gstatic.com; \
      frame-src 'self' https://www.google.com https://example.com; \
      connect-src 'self' https://*.googleapis.com; \
      img-src 'self' data:; \
      frame-ancestors 'self'; \
      worker-src 'self' data: blob:; \
      base-uri 'self'; \
      form-action 'self'; \
      upgrade-insecure-requests;" always;

    
    # Replace placeholder for nonce in files (if sub_filter is needed)
    sub_filter_once off;
    sub_filter_types *;
    sub_filter "**CSP_NONCE**" "$csp_nonce";

    # including common things that need to be restricted
    include /etc/nginx/config/common-denied.conf;    

    # creating the variable with alb dns address and port as my BE need it
    set $elb_dns "http://internal-prod-elb-12596.ap-south-1.elb.amazonaws.com:8000";
        
    # Rate Limiting for DDoS protection applied globally via http block
    location / {
        try_files $uri $uri/ /index.html; # Check file existence, return index.html if not found.

        if ($request_uri ~* ".(ico|json|css|js|gif|jpe?g|png)$") {
            expires 30d;
            access_log off;
            add_header Pragma public;
            add_header Cache-Control "public";
            add_header X-Content-Type-Options nosniff always;

            add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval' 'nonce-$csp_nonce' https://maps.googleapis.com https://www.google.com https://www.gstatic.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com;
                    frame-src 'self' https://www.google.com $elb_dns; connect-src 'self' data:  https://*.googleapis.com wss://websocket_server; object-src 'none'; img-src 'self' data: ; frame-ancestors 'self' $elb_dns; worker-src 'self' data: blob: ; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;

            break;
        }
    }

    # Proxy all requests to the backend for this subdomain
    location /api/ {
        
         # When you use a literal upstream URL with a trailing slash in proxy_pass, nginx automatically strips the matching location prefix. 
        # However, when you use a variable (like $elb_dns), this automatic URI rewriting isn’t performed.
         # Rewrite the URI: Remove the location prefix (/api) from the incoming request.
         # For example, /api/api/auth/verify becomes /api/auth/verify. [ because BE need /api]
        rewrite ^/api(/.*)$ $1 break;

        proxy_pass $elb_dns; # adding variable in proy pass

        include /etc/nginx/config/proxy_params.conf;

        add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval' 'nonce-$csp_nonce' https://maps.googleapis.com https://www.google.com https://www.gstatic.com https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
        font-src 'self' data: https://fonts.gstatic.com ; frame-src 'self' https://www.google.com $elb_dns ; connect-src 'self' data:  https://*.googleapis.com wss://websocket_server; object-src 'none'; img-src 'self' data: ; frame-ancestors 'self' $elb_dns; worker-src 'self' data: blob: ; base-uri 'self'; form-action 'self'; upgrade-insecure-requests;" always;
    }

    location /ws/ {
        proxy_pass http://websocket_server;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
    }

    # allowing specific ip for senstive content
    location /senstive/ {
        allow 192.168.1.0/24; #whitelisted ips
        deny all;
    }

    # debug error log for troubleshooting
    error_log /var/logs/nginx/api_error.log debug;
}

###############################################################################
# 4. Optional: Server Block with ModSecurity (WAF) for extra protection
###############################################################################

# corrently commenting as we do not have mobsecurity installed
# server {
#     listen 443 ssl http2;
#     server_name secure.example.com;

#     include /etc/nginx/config/ssl_params.conf
#     include /etc/nginx/config/security_headers.conf;

#     # Enable ModSecurity (ensure it is installed and configured)
#     modsecurity on;
#     modsecurity_rules_file /etc/nginx/modsec/main.conf;

#     location / {
#         proxy_pass http://backend.com:8080;
#         include /etc/nginx/config/proxy_params.conf
#     }
# }

Always check configuration nginx -t before running .

PreviousStreaming & Media Delivery in NGINXNextHow to Open a PDF or an Image in Angular Without Dependencies

Last updated 3 months ago