The Complete NGINX on Ubuntu Series: Part 9 – Security Hardening and Access Control

The Complete NGINX on Ubuntu Series: Part 9 – Security Hardening and Access Control

Welcome to Part 9 of our comprehensive NGINX on Ubuntu series! We’ll implement security hardening measures, access controls, and protection mechanisms to safeguard your web server against threats and attacks.

NGINX Security Fundamentals

Security hardening involves multiple layers of protection including access controls, rate limiting, security headers, and monitoring to create a robust defense against various attack vectors.

graph TD
    A[Security Layers] --> B[Network Level]
    A --> C[Application Level]
    A --> D[Server Level]
    A --> E[Content Level]
    
    B --> F[Rate LimitingIP FilteringGeoBlocking]
    C --> G[AuthenticationAuthorizationInput Validation]
    D --> H[Server HardeningSSL/TLSSecurity Headers]
    E --> I[Content SecurityXSS ProtectionCSRF Protection]
    
    J[Common Threats] --> K[DDoS Attacks]
    J --> L[Brute Force]
    J --> M[SQL Injection]
    J --> N[XSS Attacks]
    J --> O[Data Breaches]
    
    style A fill:#e1f5fe
    style J fill:#ffebee
    style F fill:#e8f5e8
    style G fill:#e8f5e8
    style H fill:#e8f5e8
    style I fill:#e8f5e8

Basic Security Configuration

# Create security configuration snippet
sudo nano /etc/nginx/snippets/security-headers.conf
# Security headers configuration

# Hide NGINX version
server_tokens off;

# 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 Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Content Security Policy (adjust based on your needs)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self';" always;

# HSTS (HTTP Strict Transport Security) - only for HTTPS
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Remove potentially dangerous headers
more_clear_headers 'Server';
more_clear_headers 'X-Powered-By';

# Limit request methods
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$ ) {
    return 405;
}

# Block common exploit attempts
location ~* "(eval\(|javascript:|vbscript:|onload=)" {
    deny all;
}

# Block access to hidden files
location ~ /\. {
    deny all;
    access_log off;
    log_not_found off;
}

# Block access to backup files
location ~* \.(bak|backup|old|orig|save|swo|swp|tmp)$ {
    deny all;
    access_log off;
    log_not_found off;
}

Rate Limiting and DDoS Protection

# Configure rate limiting in nginx.conf
sudo nano /etc/nginx/nginx.conf
# Add to http block in nginx.conf
http {
    # Rate limiting zones
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
    limit_req_zone $binary_remote_addr zone=downloads:10m rate=5r/s;
    
    # Connection limiting
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
    limit_conn_zone $server_name zone=perserver:10m;
    
    # Request size limits
    client_max_body_size 64m;
    client_body_buffer_size 128k;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 4k;
    
    # Timeout settings for security
    client_body_timeout 12;
    client_header_timeout 12;
    keepalive_timeout 15;
    send_timeout 10;
    
    # Buffer overflow protection
    client_body_buffer_size 1K;
    client_header_buffer_size 1k;
    large_client_header_buffers 2 1k;
}

Rate Limited Virtual Host

# Create secure virtual host with rate limiting
sudo nano /etc/nginx/sites-available/secure.example.com
server {
    listen 80;
    server_name secure.example.com;
    root /var/www/secure/public_html;
    index index.html index.php;
    
    # Include security headers
    include /etc/nginx/snippets/security-headers.conf;
    
    # Connection limits
    limit_conn conn_limit 10;
    limit_conn perserver 100;
    
    # General rate limiting
    limit_req zone=general burst=20 nodelay;
    limit_req_status 429;
    
    # Main location
    location / {
        try_files $uri $uri/ =404;
        
        # Additional security for root
        location = / {
            limit_req zone=general burst=5 nodelay;
        }
    }
    
    # Login/Admin areas with strict rate limiting
    location ~ ^/(admin|login|wp-login|wp-admin) {
        limit_req zone=login burst=3 nodelay;
        
        # IP whitelist for admin (optional)
        # allow 192.168.1.0/24;
        # allow 203.0.113.0/24;
        # deny all;
        
        try_files $uri $uri/ =404;
    }
    
    # API endpoints
    location /api/ {
        limit_req zone=api burst=50 nodelay;
        
        # API key validation (implement as needed)
        # if ($http_x_api_key = "") {
        #     return 401 "API key required";
        # }
        
        proxy_pass http://backend_api/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
    # File downloads with rate limiting
    location /downloads/ {
        limit_req zone=downloads burst=2 nodelay;
        limit_rate 1m;  # Limit download speed to 1MB/s
        
        # Log downloads
        access_log /var/log/nginx/downloads.log;
    }
    
    # PHP processing with security
    location ~ \.php$ {
        limit_req zone=general burst=10 nodelay;
        
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        
        # Security parameters
        fastcgi_param PHP_ADMIN_VALUE "open_basedir=$document_root:/tmp/";
        fastcgi_param PHP_VALUE "upload_max_filesize=64M";
        fastcgi_param PHP_VALUE "post_max_size=64M";
    }
    
    # Block access to sensitive files
    location ~* \.(htaccess|htpasswd|ini|log|sh|sql|conf)$ {
        deny all;
    }
    
    # Prevent access to version control
    location ~ /\.(git|svn|hg|bzr) {
        deny all;
    }
}

IP Access Control and Geo-blocking

# Create IP access control configuration
sudo nano /etc/nginx/snippets/ip-access-control.conf
# IP Access Control Configuration

# Whitelist trusted IPs
geo $trusted_ip {
    default 0;
    127.0.0.1 1;
    192.168.1.0/24 1;
    203.0.113.0/24 1;  # Replace with your office IP range
}

# Blacklist known bad IPs
geo $blocked_ip {
    default 0;
    # Add known malicious IPs
    # 198.51.100.100 1;
    # 203.0.113.200 1;
}

# Map user agents to block bad bots
map $http_user_agent $blocked_agent {
    default 0;
    ~*bot 0;  # Allow legitimate bots
    ~*googlebot 0;
    ~*bingbot 0;
    ~*slurp 0;
    
    # Block bad bots and scrapers
    ~*scanner 1;
    ~*scraper 1;
    ~*harvester 1;
    ~*nikto 1;
    ~*sqlmap 1;
    ~*masscan 1;
    ~*nmap 1;
    "" 1;  # Block empty user agents
}

# Country-based blocking (requires GeoIP module)
# Install: sudo apt install nginx-module-geoip
# geo $blocked_country {
#     default 0;
#     CN 1;  # Block China
#     RU 1;  # Block Russia
#     KP 1;  # Block North Korea
# }

IP Protected Virtual Host

# Create IP protected virtual host
sudo nano /etc/nginx/sites-available/ip-protected.example.com
server {
    listen 80;
    server_name ip-protected.example.com;
    root /var/www/protected/public_html;
    
    # Include IP access control
    include /etc/nginx/snippets/ip-access-control.conf;
    include /etc/nginx/snippets/security-headers.conf;
    
    # Block based on various criteria
    if ($blocked_ip) {
        return 403 "Access denied - IP blocked";
    }
    
    if ($blocked_agent) {
        return 403 "Access denied - User agent blocked";
    }
    
    # if ($blocked_country) {
    #     return 403 "Access denied - Country blocked";
    # }
    
    # Admin area - trusted IPs only
    location /admin/ {
        if ($trusted_ip = 0) {
            return 403 "Admin access restricted";
        }
        
        try_files $uri $uri/ =404;
    }
    
    # API with IP restrictions
    location /api/ {
        # Allow API access from trusted IPs
        if ($trusted_ip = 0) {
            return 403 "API access restricted";
        }
        
        proxy_pass http://backend_api/;
        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-Trusted-IP $trusted_ip;
    }
    
    # Public area with bot protection
    location / {
        # Rate limiting for public access
        limit_req zone=general burst=10 nodelay;
        
        try_files $uri $uri/ =404;
    }
}

Authentication and Authorization

graph TD
    A[Authentication Methods] --> B[Basic Auth]
    A --> C[JWT Tokens]
    A --> D[Client Certificates]
    A --> E[OAuth Integration]
    
    B --> F[Username/PasswordSimple Implementation]
    C --> G[Token ValidationStateless Auth]
    D --> H[Certificate ValidationHigh Security]
    E --> I[Third-party AuthSSO Integration]
    
    J[Authorization Levels] --> K[Public Access]
    J --> L[Authenticated Users]
    J --> M[Admin Users]
    J --> N[API Clients]
    
    style A fill:#e1f5fe
    style J fill:#e8f5e8
    style F fill:#fff3e0
    style G fill:#fff3e0
    style H fill:#fff3e0
    style I fill:#fff3e0

HTTP Basic Authentication

# Create password file for basic auth
sudo nano /etc/nginx/auth/.htpasswd

# Add users (replace username and use strong passwords)
sudo sh -c "echo -n 'admin:' >> /etc/nginx/auth/.htpasswd"
sudo sh -c "openssl passwd -apr1 >> /etc/nginx/auth/.htpasswd"

sudo sh -c "echo -n 'user1:' >> /etc/nginx/auth/.htpasswd"
sudo sh -c "openssl passwd -apr1 >> /etc/nginx/auth/.htpasswd"

# Set permissions
sudo chmod 600 /etc/nginx/auth/.htpasswd
sudo chown www-data:www-data /etc/nginx/auth/.htpasswd
# Create authenticated virtual host
sudo nano /etc/nginx/sites-available/auth.example.com
server {
    listen 80;
    server_name auth.example.com;
    root /var/www/auth/public_html;
    
    include /etc/nginx/snippets/security-headers.conf;
    
    # Public area (no auth required)
    location /public/ {
        try_files $uri $uri/ =404;
    }
    
    # Protected area with basic auth
    location /protected/ {
        auth_basic "Restricted Area";
        auth_basic_user_file /etc/nginx/auth/.htpasswd;
        
        try_files $uri $uri/ =404;
    }
    
    # Admin area with stricter controls
    location /admin/ {
        # IP restrictions + basic auth
        allow 192.168.1.0/24;
        deny all;
        
        auth_basic "Admin Area";
        auth_basic_user_file /etc/nginx/auth/.htpasswd;
        
        # Additional rate limiting for admin
        limit_req zone=login burst=3 nodelay;
        
        try_files $uri $uri/ =404;
    }
    
    # API with token-based auth
    location /api/ {
        # Check for API key in header
        if ($http_x_api_key = "") {
            return 401 "API key required";
        }
        
        # Validate API key (implement your logic)
        # You can use auth_request for external validation
        
        proxy_pass http://backend_api/;
        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-API-Key $http_x_api_key;
    }
}

Fail2Ban Integration

# Install Fail2Ban
sudo apt update
sudo apt install fail2ban -y

# Create NGINX filter for Fail2Ban
sudo nano /etc/fail2ban/filter.d/nginx-limit-req.conf
# Fail2Ban filter for NGINX rate limiting
[Definition]
failregex = limiting requests, excess: \S+ by zone "\S+", client: 
ignoreregex =
# Create NGINX auth failure filter
sudo nano /etc/fail2ban/filter.d/nginx-auth.conf
# Fail2Ban filter for NGINX auth failures
[Definition]
failregex = user "\S*" (password mismatch|was not found), client: 
            no user/password was provided for basic authentication, client: 
            user "\S*": password mismatch, client: 
ignoreregex =
# Configure Fail2Ban jail
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
# Ban time (in seconds)
bantime = 3600
findtime = 600
maxretry = 3

# Email notifications (optional)
# destemail = admin@example.com
# sender = fail2ban@example.com
# action = %(action_mwl)s

[nginx-rate-limit]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 1800

[nginx-auth]
enabled = true
port = http,https
filter = nginx-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600

[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
bantime = 86400

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 86400
# Start and enable Fail2Ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

# Check Fail2Ban status
sudo fail2ban-client status

# Check specific jail status
sudo fail2ban-client status nginx-rate-limit

# Unban an IP if needed
# sudo fail2ban-client set nginx-rate-limit unbanip 192.168.1.100

Security Monitoring and Logging

# Create security monitoring script
sudo nano /usr/local/bin/nginx-security-monitor.sh
#!/bin/bash

# NGINX Security Monitor
LOG_DIR="/var/log/nginx"
SECURITY_LOG="/var/log/nginx/security.log"

log_security_event() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] SECURITY: $1" | tee -a "$SECURITY_LOG"
}

check_suspicious_activity() {
    echo "=== Security Monitoring Report ==="
    echo "Generated: $(date)"
    echo
    
    # Check for 4xx errors (potential attacks)
    echo "--- Recent 4xx Errors ---"
    tail -1000 "$LOG_DIR/access.log" | grep " 4[0-9][0-9] " | tail -5
    echo
    
    # Check for rate limiting hits
    echo "--- Rate Limiting Events ---"
    grep "limiting requests" "$LOG_DIR/error.log" | tail -5
    echo
    
    # Check for auth failures
    echo "--- Authentication Failures ---"
    grep "password mismatch\|was not found" "$LOG_DIR/error.log" | tail -5
    echo
    
    # Check for suspicious user agents
    echo "--- Suspicious User Agents ---"
    tail -1000 "$LOG_DIR/access.log" | grep -i "bot\|crawler\|scanner\|nikto" | grep -v "googlebot\|bingbot" | tail -5
    echo
    
    # Top attacking IPs
    echo "--- Top Error-generating IPs ---"
    tail -1000 "$LOG_DIR/access.log" | grep " 4[0-9][0-9] " | awk '{print $1}' | sort | uniq -c | sort -nr | head -5
    echo
    
    # Large request attempts
    echo "--- Large Request Attempts ---"
    grep "413\|client intended to send too large body" "$LOG_DIR/error.log" | tail -3
}

# Run security check
check_suspicious_activity

# Make executable: sudo chmod +x /usr/local/bin/nginx-security-monitor.sh

SSL/TLS Security Enhancement

# Create enhanced SSL configuration
sudo nano /etc/nginx/snippets/ssl-security.conf
# Enhanced SSL/TLS Security Configuration

# SSL Protocols and Ciphers
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;

# SSL Session Settings
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
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;

# SSL Security Headers
add_header Strict-Transport-Security "max-age=63072000; 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;

# SSL Buffer Settings
ssl_buffer_size 4k;

Testing Security Configuration

# Enable secure sites
sudo ln -s /etc/nginx/sites-available/secure.example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/auth.example.com /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload NGINX
sudo systemctl reload nginx

# Test rate limiting
for i in {1..15}; do curl -H "Host: secure.example.com" http://localhost/ -w "%{http_code}\n" -o /dev/null -s; done

# Test authentication
curl -H "Host: auth.example.com" http://localhost/protected/ -w "%{http_code}\n" -o /dev/null -s
curl -u admin:password -H "Host: auth.example.com" http://localhost/protected/ -w "%{http_code}\n" -o /dev/null -s

# Test security headers
curl -I -H "Host: secure.example.com" http://localhost/

# Check Fail2Ban status
sudo fail2ban-client status

# Run security monitor
/usr/local/bin/nginx-security-monitor.sh

# Test with security scanner (if available)
# nikto -h http://secure.example.com

What’s Next?

Excellent! You’ve implemented comprehensive security hardening for your NGINX server. Your setup now includes rate limiting, access controls, authentication, monitoring, and protection against common attack vectors.

Coming up in Part 10: NGINX Logging, Monitoring, and Analytics

References


This is Part 9 of our 22-part NGINX series. Your server is now properly secured and hardened! Next, we’ll set up comprehensive monitoring and analytics. Questions about security? Share them in the comments!

Written by:

348 Posts

View All Posts
Follow Me :
How to whitelist website on AdBlocker?

How to whitelist website on AdBlocker?

  1. 1 Click on the AdBlock Plus icon on the top right corner of your browser
  2. 2 Click on "Enabled on this site" from the AdBlock Plus option
  3. 3 Refresh the page and start browsing the site