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:
| Layer | Defense Target | Key Tools |
|---|---|---|
| CDN | Large-scale DDoS, globally distributed attacks | Cloudflare, AWS CloudFront |
| Firewall | Port access, network-level attacks | iptables, UFW, AWS SG |
| Web Server | HTTP Flood, malicious requests, information exposure | Nginx + ModSecurity |
| Application | Injection, auth bypass, business logic | Spring Security, Validation |
| Database | SQL Injection, unauthorized access | Least 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