Skip to main content

Security Hardening Pro Tips

From CIS Benchmark-based security audits to vulnerability scanner usage and zero-day response — practical know-how you can immediately apply in production environments.


CIS Benchmark-Based Security Audit Checklist

CIS (Center for Internet Security) Benchmark provides industry-standard security configuration guides.

Nginx CIS Benchmark Key Items

#!/bin/bash
# nginx-security-check.sh — CIS Nginx Benchmark automated audit

echo "=== Nginx Security Audit ==="

# 1. Check Nginx version exposure
echo -n "[CHECK] server_tokens: "
grep -r "server_tokens" /etc/nginx/ 2>/dev/null | grep -v "#" | grep "off" \
&& echo "PASS" || echo "FAIL — need to set server_tokens off"

# 2. Directory listing disabled
echo -n "[CHECK] autoindex: "
grep -r "autoindex on" /etc/nginx/ 2>/dev/null | grep -v "#" \
&& echo "FAIL — need to set autoindex off" || echo "PASS"

# 3. Upload size limit
echo -n "[CHECK] client_max_body_size: "
grep -r "client_max_body_size" /etc/nginx/ 2>/dev/null | grep -v "#" \
|| echo "WARN — using default (1MB), explicit setting recommended"

# 4. Timeout settings
echo -n "[CHECK] client_header_timeout: "
grep -r "client_header_timeout" /etc/nginx/ 2>/dev/null | grep -v "#" \
|| echo "WARN — timeout settings recommended for Slowloris defense"

# 5. Rate Limiting settings
echo -n "[CHECK] limit_req_zone: "
grep -r "limit_req_zone" /etc/nginx/ 2>/dev/null | grep -v "#" \
&& echo "PASS" || echo "WARN — Rate Limiting configuration recommended"

# 6. Check security headers
HEADERS=$(curl -sI https://localhost 2>/dev/null)
for header in "X-Frame-Options" "X-Content-Type-Options" "Strict-Transport-Security"; do
echo -n "[CHECK] $header: "
echo "$HEADERS" | grep -i "$header" && echo "" || echo "FAIL — header needs to be added"
done

# 7. Block hidden file access
echo -n "[CHECK] Hidden file blocking: "
grep -r "location ~ /\\\\." /etc/nginx/ 2>/dev/null | grep -v "#" \
&& echo "PASS" || echo "WARN — recommend blocking .env, .git access"

echo ""
echo "=== Audit Complete ==="

Apache CIS Benchmark Key Items

#!/bin/bash
# apache-security-check.sh

echo "=== Apache Security Audit ==="

# Check ServerTokens
echo -n "[CHECK] ServerTokens: "
apache2ctl -t -D DUMP_RUN_CFG 2>&1 | grep -i "ServerTokens" \
|| grep -ri "ServerTokens" /etc/apache2/ 2>/dev/null | grep -v "#" \
|| echo "configuration needs verification"

# Check ServerSignature
echo -n "[CHECK] ServerSignature: "
grep -ri "ServerSignature Off" /etc/apache2/ 2>/dev/null | grep -v "#" \
&& echo "PASS" || echo "FAIL — ServerSignature Off required"

# Check Directory Options
echo -n "[CHECK] Options -Indexes: "
grep -ri "Options.*-Indexes" /etc/apache2/ 2>/dev/null | grep -v "#" \
&& echo "PASS" || echo "WARN — disabling Indexes recommended"

# Check mod_security activation
echo -n "[CHECK] ModSecurity: "
apache2ctl -M 2>/dev/null | grep security \
&& echo "" || echo "WARN — ModSecurity not installed"

Using Vulnerability Scanners

Nikto — Web Server Vulnerability Scanner

# Install Nikto
sudo apt install nikto

# Basic scan
nikto -h https://example.com

# Scan specific vulnerability types only
# -Tuning options: 0=info disclosure, 1=file inclusion, 2=other files, 3=XSS, 4=injection,
# 5=remote inclusion, 6=DoS, 7=RCE, 8=SQL, 9=file upload, 0=auth bypass
nikto -h https://example.com -Tuning 0,3,4,8 # Only info disclosure+XSS+SQL

# Specify output format
nikto -h https://example.com -Format html -o report.html
nikto -h https://example.com -Format csv -o report.csv

# Scan paths requiring authentication
nikto -h https://example.com -id admin:password

# Result interpretation:
# + OSVDB-xxx: Vulnerability ID
# - : Normal (no such vulnerability)
# + : Found vulnerability or warning

OWASP ZAP — Web Application Security Testing

# Run ZAP via Docker (easiest method)
docker pull zaproxy/zap-stable

# Baseline scan — automated without manual intervention
docker run -t zaproxy/zap-stable zap-baseline.py \
-t https://example.com \
-r zap-report.html

# Full scan (includes active attacks, takes more time)
docker run -t zaproxy/zap-stable zap-full-scan.py \
-t https://example.com \
-r zap-full-report.html

# API scan (supports OpenAPI/Swagger)
docker run -t zaproxy/zap-stable zap-api-scan.py \
-t https://api.example.com/openapi.json \
-f openapi \
-r zap-api-report.html

sqlmap — SQL Injection Testing

# SQL Injection test for specific parameter (authorized environments only)
sqlmap -u "https://example.com/api/users?id=1" \
--level=3 --risk=2 \
--dbs # Extract database list

# POST request testing
sqlmap -u "https://example.com/login" \
--data="username=test&password=test" \
--level=5

# Include cookie-based authentication session
sqlmap -u "https://example.com/profile" \
--cookie="session=abc123" \
--tables

Security Headers Bulk Configuration Template

Complete security header configuration ready for immediate production deployment:

# /etc/nginx/conf.d/security-headers.conf
# Include in all server blocks for reuse

# Clickjacking prevention
add_header X-Frame-Options "SAMEORIGIN" always;

# MIME sniffing prevention
add_header X-Content-Type-Options "nosniff" always;

# XSS protection (for older browsers, replaced by CSP in modern browsers)
add_header X-XSS-Protection "1; mode=block" always;

# HSTS — force HTTPS (1 year, including subdomains)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Restrict referrer information
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permissions policy — disable unnecessary browser features
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;

# CSP — Content Security Policy (adjust to your site)
# Test in report-only mode before production deployment
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;

# Remove server information
server_tokens off;

# Change server name (requires headers-more module)
# more_set_headers 'Server: WebServer';

Regular Security Audit Automation

#!/bin/bash
# /usr/local/bin/weekly-security-audit.sh
# cron: 0 2 * * 0 (every Sunday at 2 AM)

DATE=$(date +%Y%m%d)
REPORT_DIR="/var/reports/security"
mkdir -p "$REPORT_DIR"

echo "=== Weekly Security Audit — $DATE ===" > "$REPORT_DIR/audit-$DATE.txt"

# 1. Check SSL certificate expiry
echo "--- SSL Certificate ---" >> "$REPORT_DIR/audit-$DATE.txt"
CERT_EXPIRY=$(echo | openssl s_client -connect example.com:443 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null)
echo "$CERT_EXPIRY" >> "$REPORT_DIR/audit-$DATE.txt"

# Alert if expiring within 30 days
EXPIRY_EPOCH=$(echo "$CERT_EXPIRY" | cut -d= -f2 | xargs -I{} date -d '{}' +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$DAYS_LEFT" -lt 30 ]; then
echo "⚠️ SSL certificate expires in $DAYS_LEFT days!" | mail -s "SSL Expiry Warning" admin@example.com
fi

# 2. fail2ban block status
echo "--- fail2ban Blocked IPs ---" >> "$REPORT_DIR/audit-$DATE.txt"
fail2ban-client status 2>/dev/null >> "$REPORT_DIR/audit-$DATE.txt"

# 3. Error log summary from last week
echo "--- Nginx Error Log Summary ---" >> "$REPORT_DIR/audit-$DATE.txt"
grep "$(date -d '7 days ago' '+%Y/%m/%d')" /var/log/nginx/error.log 2>/dev/null \
| grep -v "^$" | sort | uniq -c | sort -rn | head -20 \
>> "$REPORT_DIR/audit-$DATE.txt"

# 4. Top ModSecurity blocking rules
echo "--- Top ModSecurity Block Rules ---" >> "$REPORT_DIR/audit-$DATE.txt"
grep "Access denied" /var/log/modsec_audit.log 2>/dev/null \
| grep -o 'id "[0-9]*"' | sort | uniq -c | sort -rn | head -10 \
>> "$REPORT_DIR/audit-$DATE.txt"

# 5. Check open ports (detect unexpected ports)
echo "--- Open Ports ---" >> "$REPORT_DIR/audit-$DATE.txt"
ss -tlnp >> "$REPORT_DIR/audit-$DATE.txt"

# 6. Check for security updates
echo "--- Pending Security Updates ---" >> "$REPORT_DIR/audit-$DATE.txt"
apt list --upgradable 2>/dev/null | grep -i security \
>> "$REPORT_DIR/audit-$DATE.txt"

echo "Audit complete: $REPORT_DIR/audit-$DATE.txt"

Defense in Depth Architecture

Client


CDN / DDoS Defense (Cloudflare / AWS Shield)


Firewall (iptables / UFW / AWS Security Group)


Nginx (WAF/ModSecurity, Rate Limiting, IP blocking)


Application Server (Spring Boot input validation, auth/authz)


Database (least privilege, encryption)

Role of each layer:

LayerDefense TargetKey Tools
CDNLarge-scale DDoS, globally distributed attacksCloudflare, AWS CloudFront
FirewallPort access, network-level attacksiptables, UFW, AWS SG
Web ServerHTTP Flood, malicious requests, information exposureNginx + ModSecurity
ApplicationInjection, auth bypass, business logicSpring Security, Validation
DatabaseSQL Injection, unauthorized accessLeast privilege, Prepared Statement

Zero-Day Vulnerability Response

# CVE monitoring — automated
# Subscribe to new CVE alerts for Nginx, Apache, Tomcat

# RSS feed monitoring (using cron)
cat > /usr/local/bin/check-cve.sh << 'EOF'
#!/bin/bash
# Check Nginx CVEs (NVD RSS feed)
NGINX_VERSION=$(nginx -v 2>&1 | grep -o '[0-9.]*')
echo "Current Nginx version: $NGINX_VERSION"

# Check for latest security updates
apt-cache policy nginx 2>/dev/null | head -5

# Output manual security blog check links
echo "Links to check:"
echo " https://nginx.org/en/security_advisories.html"
echo " https://httpd.apache.org/security/vulnerabilities_24.html"
echo " https://tomcat.apache.org/security.html"
EOF
chmod +x /usr/local/bin/check-cve.sh

# Daily morning CVE check (cron)
# 0 9 * * * /usr/local/bin/check-cve.sh | mail -s "Daily CVE Check" admin@example.com

Security Configuration Version Control (Infrastructure as Code)

# Manage Nginx configuration with Git
cd /etc/nginx
git init
git add .
git commit -m "initial: nginx security baseline"

# Track changes
git diff

# Integrate with deployment scripts
cat > /usr/local/bin/nginx-deploy.sh << 'EOF'
#!/bin/bash
# 1. Validate configuration
nginx -t || exit 1

# 2. Commit changes
cd /etc/nginx
git add -A
git commit -m "config update: $(date +%Y%m%d-%H%M)"

# 3. Reload
systemctl reload nginx

# 4. Health check
sleep 2
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://localhost)
if [ "$HTTP_CODE" != "200" ]; then
echo "⚠️ Health check failed: $HTTP_CODE — restoring previous configuration"
git stash
systemctl reload nginx
fi

echo "✅ Deployment complete"
EOF
chmod +x /usr/local/bin/nginx-deploy.sh

Production Security Operations Checklist

New Server Deployment (Required Pre-Deployment Checks)

□ server_tokens off / ServerTokens Prod configured
□ Directory listing disabled (autoindex off / Options -Indexes)
□ Unnecessary HTTP methods blocked (TRACE, OPTIONS)
□ 6 security headers configured (X-Frame-Options, CSP, HSTS, etc.)
□ SSL/TLS 1.3 configured, weak ciphers removed
□ Rate Limiting configured (API, login endpoints)
□ Upload directory script execution blocked
□ Sensitive file access blocked (.env, .git, *.conf)
□ Error pages customized (no server information)
□ fail2ban installed and enabled
□ ModSecurity + OWASP CRS installed (start in DetectionOnly mode)
□ Firewall rules verified (only necessary ports open)
□ SSH access restricted (public key only, root login disabled)

Monthly Audit Items

□ SSL certificate expiry check (renew within 30 days)
□ Security updates applied (nginx, apache, openssl)
□ fail2ban blocked IP log review
□ ModSecurity false positive rule adjustments
□ Access log anomalous traffic analysis
□ Vulnerability scanner run (Nikto or ZAP)
□ Firewall rule review (remove unnecessary rules)
□ Backup restoration test

Incident Response Procedure

# Phase 1: Situation assessment (attack in progress)
# Check current connection status
ss -tn state established | awk '{print $5}' | cut -d: -f1 | \
sort | uniq -c | sort -rn | head -20

# Check abnormal processes
ps aux | sort -k3 -rn | head -20 # High CPU processes
netstat -tlnp # Open ports

# Phase 2: Immediate blocking
# Block attacking IP (temporary)
iptables -I INPUT -s <attack_IP> -j DROP

# Block in Nginx as well
echo "deny <attack_IP>;" >> /etc/nginx/blocklist.conf
nginx -t && nginx -s reload

# Phase 3: Evidence preservation
# Backup logs (prevent overwriting)
cp /var/log/nginx/access.log /tmp/incident-$(date +%Y%m%d)-access.log
cp /var/log/nginx/error.log /tmp/incident-$(date +%Y%m%d)-error.log
cp /var/log/modsec_audit.log /tmp/incident-$(date +%Y%m%d)-modsec.log

# Phase 4: Root cause analysis
# Identify which URLs were attacked
grep "<attack_IP>" /var/log/nginx/access.log | \
awk '{print $7}' | sort | uniq -c | sort -rn | head -20

# Phase 5: Recurrence prevention
# Apply permanent blocking rules, strengthen ModSecurity rules