Nginx is much more than a web server. In a production setup, it becomes a control point for:
In your environment, Nginx is used as a reverse proxy for public websites and protected with ModSecurity, which is exactly the right direction for an internet-facing stack.
A practical pattern for your domains:
Public clients hit Nginx first, then Nginx forwards traffic to internal upstream targets. This lets you enforce policy once at the edge instead of configuring security separately in every backend service.
Internet Clients
|
v
Nginx Edge (TLS + WAF + Rate Control + Logging)
|
+--> blog upstream (internal app)
|
+--> home upstream (internal app)
Use this baseline flow on Ubuntu/Debian:
# Install Nginx
sudo apt update
sudo apt install -y nginx
# Allow web traffic through firewall
sudo ufw allow 'Nginx Full'
# Install Certbot and Nginx plugin
sudo apt install -y certbot python3-certbot-nginx
# Request and configure certificates for both sites
sudo certbot --nginx -d Site1.mouradcloud.com -d site2.mouradcloud.com
After issuance, Certbot updates Nginx server blocks and can add HTTP to HTTPS redirects automatically.
Useful SSL operations:
# Test auto-renewal
sudo certbot renew --dry-run
# Validate and reload Nginx safely
sudo nginx -t
sudo systemctl reload nginx
Install ModSecurity packages and OWASP Core Rule Set:
sudo apt update
sudo apt install -y libnginx-mod-http-modsecurity modsecurity-crs
Enable and tune ModSecurity:
# Enable engine
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo sed -i 's/SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/modsecurity/modsecurity.conf
# Optional: include unicode mapping if not already present
sudo test -f /etc/modsecurity/unicode.mapping && echo "unicode mapping present"
Create a focused Nginx ModSecurity include file:
# /etc/nginx/modsec/modsecurity.conf
modsecurity on;
modsecurity_rules_file /etc/modsecurity/modsecurity.conf;
modsecurity_rules_file /usr/share/modsecurity-crs/owasp-crs.load;
Attach it to your HTTPS server blocks:
server {
listen 443 ssl http2;
server_name Site1.mouradcloud.com;
include /etc/nginx/modsec/modsecurity.conf;
location / {
proxy_pass http://site1_backend;
}
}
Then validate and reload:
sudo nginx -t
sudo systemctl reload nginx
Operational tip: start in DetectionOnly for a short baseline period, inspect false positives, then switch to On for active blocking.
Keep ModSecurity enabled per virtual host:
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;
Benefit: blocks known malicious payload patterns before they reach backend apps.
Always redirect port 80 to HTTPS:
server {
listen 80;
server_name Site1.mouradcloud.com site2.mouradcloud.com;
return 301 https://$host$request_uri;
}
Benefit: protects credentials and session traffic in transit.
Preserve source and protocol context:
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;
Benefit: accurate client identity for logs, app logic, and incident response.
Use a tuned reverse proxy server block per site:
server {
server_name Site1.mouradcloud.com;
location / {
proxy_pass http://blog_backend;
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_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_cache site_cache;
proxy_cache_valid 200 301 302 1h;
proxy_cache_valid any 5m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Proxy-Cache $upstream_cache_status always;
}
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/Site1.mouradcloud.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/Site1.mouradcloud.com/privkey.pem;
}
Benefit: faster repeat responses, reduced backend pressure, and better behavior during transient upstream failures.
When you have multiple backend instances, move to upstream pools:
upstream blog_backend {
least_conn;
server 192.168.251.20:80 max_fails=3 fail_timeout=15s;
server 192.168.251.21:80 max_fails=3 fail_timeout=15s;
keepalive 64;
}
upstream home_backend {
ip_hash;
server 192.168.251.30:80;
server 192.168.251.31:80;
}
Routing strategy notes:
Add these controls to reduce attack surface even more:
# Basic burst control
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=20r/s;
# Connection control
limit_conn_zone $binary_remote_addr zone=conn_limit_per_ip:10m;
# Security headers (example baseline)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
You can also enforce geographic policy (for example allowing specific countries) if that matches your business traffic profile.
Nginx UI is a great operational layer for day-2 operations:
Recommended practice:
Nginx UI provides a visual dashboard for easier operations and safer configuration management:
# Download and install Nginx UI (example for Linux x86_64)
cd /opt
sudo wget https://github.com/uengine-oss/nginx-ui/releases/download/v2.0.0-beta.6/nginx-ui-linux-amd64.tar.gz
sudo tar xzf nginx-ui-linux-amd64.tar.gz
sudo chown -R www-data:www-data /opt/nginx-ui
Create a SystemD service for Nginx UI:
# /etc/systemd/system/nginx-ui.service
[Unit]
Description=Nginx UI
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/nginx-ui
ExecStart=/opt/nginx-ui/nginx-ui
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable nginx-ui
sudo systemctl start nginx-ui
Nginx UI typically listens on port 7788 by default. Access it via http://localhost:7788 (or your server IP).
Key features:
Best practice: use Nginx UI for visibility and controlled changes, keep CLI as source of truth for automation and backups.
# Validate config before applying
sudo nginx -t
# Reload safely after successful validation
sudo systemctl reload nginx
# Watch Nginx errors in real time
sudo tail -f /var/log/nginx/error.log
# Check WAF-related events (if routed to error log)
sudo grep -iE "modsecurity|denied|access forbidden" /var/log/nginx/error.log
One of the biggest improvements I made in this setup was moving suspicious-IP handling to Nginx at Layer 7 instead of relying only on Layer 4 blocking.
Why that matters:
.env, wp-config.php, config.php, .git, traversal attempts, and framework scans.A practical workflow is:
/etc/nginx/snippets/ip-blocklist.conf.Example automation pattern:
#!/usr/bin/env bash
set -euo pipefail
ACCESS_LOG=/var/log/nginx/access.log
ERROR_LOG=/var/log/nginx/error.log
BLOCKLIST=/etc/nginx/snippets/ip-blocklist.conf
STATE=/tmp/nginx-threat-monitor.pos
# Read new log entries, score suspicious behavior, and emit one decision per IP.
# If the IP is new and clearly malicious, append: deny x.x.x.x;
# Then run: nginx -t && systemctl reload nginx
This approach gives you faster reaction time at the edge, better visibility into the attack pattern, and less risk of blocking legitimate users than a broad Layer 4 rule set.
Nginx becomes "on steriods" when you combine these layers together:
That combination gives you stronger security, cleaner operations, and better uptime for both Site1.mouradcloud.com and site2.mouradcloud.com.