Security Audit: How to Harden Your Homelab
Complete security audit checklist for homelabs. Identify vulnerabilities and implement fixes.
A security audit is a systematic evaluation of your homelab’s security posture. Unlike firewalls and intrusion detection that provide active defense, audits help you identify weaknesses before attackers exploit them. This guide walks you through conducting professional-grade security assessments on your own infrastructure.
Why Security Audits Matter
Your homelab faces constant threats:
- Exposed services: Misconfigured ports open to the internet
- Outdated software: Unpatched vulnerabilities waiting to be exploited
- Weak credentials: Default passwords and poor authentication
- Misconfigurations: Insecure settings that create attack vectors
- Lateral movement risks: Compromised container spreading to host
Regular audits help you:
- Identify vulnerabilities before attackers do
- Verify security controls are working as intended
- Meet compliance standards if running business services
- Maintain security hygiene through regular assessments
- Prioritize remediation based on risk levels
Audit Phases
A complete security audit has five phases:
- Discovery: Map your attack surface
- Scanning: Identify vulnerabilities automatically
- Analysis: Manual verification and deep inspection
- Exploitation: Test if vulnerabilities are exploitable
- Reporting: Document findings and create action plans
Phase 1: Discovery and Reconnaissance
External Attack Surface Mapping
Identify what’s exposed to the internet:
# Your public IP
curl ifconfig.me
# Scan your public IP from external scanner
# Use: https://pentest-tools.com/network-vulnerability-scanning/tcp-port-scanner-online-nmap Map open ports externally:
# Using nmap from external VPS (if you have one)
nmap -sS -sV -p- YOUR-PUBLIC-IP
# Or use online tools:
# - https://www.shodan.io/host/YOUR-IP
# - https://censys.io
# - https://www.zoomeye.org DNS reconnaissance:
# Check DNS records
dig ANY yourdomain.com
# Find subdomains
subfinder -d yourdomain.com
# Certificate transparency logs
curl -s "https://crt.sh/?q=%.yourdomain.com&output=json" | jq Internal Network Discovery
Map internal services:
# Quick host discovery
sudo nmap -sn 192.168.1.0/24
# Detailed service scan
sudo nmap -sS -sV -O 192.168.1.0/24 -oA internal-scan
# Find common services
sudo nmap -p 22,80,443,445,3389,5432,3306,6379,27017,9200 192.168.1.0/24 Docker container enumeration:
# List all containers
docker ps -a
# Inspect exposed ports
docker ps --format "table {{.Names}}\t{{.Ports}}"
# Check container networks
docker network ls
docker network inspect bridge List all listening services:
# All listening TCP/UDP ports
sudo ss -tulpn
# Or using netstat
sudo netstat -tulpn
# Check what's bound to 0.0.0.0 (all interfaces)
sudo ss -tulpn | grep "0.0.0.0" Create Network Inventory
Document your infrastructure:
Create /home/admin/security/inventory.yaml:
# Network Inventory
external:
public_ip: YOUR-PUBLIC-IP
domain: yourdomain.com
exposed_services:
- name: SSH
port: 22
protocol: tcp
protected_by: [fail2ban, crowdsec]
- name: HTTPS
port: 443
protocol: tcp
protected_by: [nginx, cloudflare]
- name: VPN
port: 51820
protocol: udp
service: wireguard
internal:
subnet: 192.168.1.0/24
hosts:
- ip: 192.168.1.10
hostname: proxmox
services: [ssh, web, api]
criticality: high
- ip: 192.168.1.20
hostname: docker-host
services: [ssh, docker]
containers: [nginx, portainer, heimdall]
criticality: high
containers:
- name: nginx-proxy
image: nginx:latest
ports: [80, 443]
volumes: [/var/www, /etc/nginx]
risk_level: high
- name: postgres-db
image: postgres:14
ports: [5432]
credentials: env_file
risk_level: critical Phase 2: Automated Vulnerability Scanning
Nmap Security Scripts
Comprehensive vulnerability scan:
# NSE vulnerability scripts
sudo nmap -sV --script vuln 192.168.1.10
# Specific vulnerability checks
sudo nmap --script ssl-poodle,ssl-heartbleed,ssl-ccs-injection 192.168.1.10
# Brute force detection
sudo nmap --script ssh-brute,http-brute,ftp-brute 192.168.1.10 OpenVAS/Greenbone Vulnerability Scanner
Deploy OpenVAS in Docker:
docker-compose.yml:
version: '3.8'
services:
openvas:
container_name: openvas
image: greenbone/openvas:latest
restart: unless-stopped
ports:
- "8080:8080"
- "9390:9390"
volumes:
- openvas-data:/var/lib/openvas
- openvas-plugins:/var/lib/openvas/plugins
environment:
- PASSWORD=SecurePassword123!
- RELAYHOST=smtp.gmail.com
- SMTPPORT=587
volumes:
openvas-data:
openvas-plugins: Deploy and scan:
docker-compose up -d
# Access web interface at http://localhost:8080
# Default credentials: admin / SecurePassword123!
# Wait for feed updates (can take 30-60 minutes)
docker logs -f openvas Create scan target:
- Configuration � Targets � New Target
- Add your network: 192.168.1.0/24
- Create � Tasks � New Task
- Select target and scan config
- Start scan
Nikto Web Scanner
Scan web applications:
# Install nikto
sudo apt install nikto
# Basic web scan
nikto -h http://192.168.1.10
# Comprehensive scan
nikto -h http://192.168.1.10 -Tuning x -Format htm -output nikto-report.html
# SSL/TLS scan
nikto -h https://192.168.1.10 -ssl
# Through proxy
nikto -h http://192.168.1.10 -useproxy http://127.0.0.1:8080 Lynis System Auditing
Comprehensive Linux hardening scan:
# Install lynis
sudo apt install lynis
# Run full system audit
sudo lynis audit system
# Save report
sudo lynis audit system --report-file /var/log/lynis-report.txt
# Check Docker security
sudo lynis audit dockerfile Dockerfile
# Review recommendations
sudo cat /var/log/lynis-report.txt | grep Suggestion Lynis key checks:
- Kernel hardening (sysctl settings)
- Boot and services
- Users and authentication
- File permissions
- Networking configuration
- Malware scanning
- Logging and auditing
Docker Security Scanning
Docker Bench for Security:
# Run Docker Bench
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker/docker-bench-security Trivy Image Scanner:
# Install trivy
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt update && sudo apt install trivy
# Scan Docker image
trivy image nginx:latest
# Scan specific container
docker inspect YOUR-CONTAINER | grep Image | awk '{print $2}' | xargs trivy image
# Generate report
trivy image --format json --output results.json nginx:latest Clair Scanner:
# Analyze image for vulnerabilities
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
arminc/clair-scanner:latest --ip YOUR-HOST-IP nginx:latest Phase 3: Manual Security Analysis
SSH Hardening Check
Verify SSH configuration:
# Check current config
sudo sshd -T
# Key settings to verify
sudo grep -E 'PermitRootLogin|PasswordAuthentication|PermitEmptyPasswords|PubkeyAuthentication|Port|Protocol' /etc/ssh/sshd_config Secure SSH configuration:
# /etc/ssh/sshd_config recommended settings
# Disable root login
PermitRootLogin no
# Use SSH keys only
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
# Modern protocol only
Protocol 2
# Limit users
AllowUsers admin deploy
# Change default port
Port 2222
# Disable unused features
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
# Use strong ciphers
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group-exchange-sha256
# Timeouts
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 60
MaxAuthTries 3 Test SSH from external:
# From external host/VPS
ssh -vv user@YOUR-PUBLIC-IP -p 2222
# Check for banner disclosure
nc YOUR-PUBLIC-IP 2222
# Test weak ciphers (should fail)
ssh -c aes128-cbc user@YOUR-PUBLIC-IP -p 2222 SSL/TLS Configuration
Scan SSL/TLS configuration:
# Install testssl.sh
git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
# Comprehensive SSL scan
./testssl.sh https://yourdomain.com
# Check specific vulnerabilities
./testssl.sh --vulnerable https://yourdomain.com
# Grade your SSL config
./testssl.sh --severity HIGH https://yourdomain.com Check certificate validity:
# Certificate info
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | openssl x509 -noout -dates
# Certificate chain
echo | openssl s_client -connect yourdomain.com:443 -showcerts
# Check for certificate transparency
curl -s "https://crt.sh/?q=yourdomain.com&output=json" | jq Nginx SSL best practices:
# /etc/nginx/sites-available/secure-ssl.conf
server {
listen 443 ssl http2;
server_name yourdomain.com;
# Modern SSL configuration
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem;
# TLS versions
ssl_protocols TLSv1.2 TLSv1.3;
# Strong ciphers
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 valid=300s;
resolver_timeout 5s;
# Security headers
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 "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# Diffie-Hellman parameter
ssl_dhparam /etc/nginx/dhparam.pem;
# SSL session
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
} Database Security Check
PostgreSQL hardening:
# Check PostgreSQL configuration
sudo -u postgres psql -c "SHOW config_file;"
sudo cat /etc/postgresql/*/main/postgresql.conf
# Key settings
grep -E 'listen_addresses|ssl|password_encryption' /etc/postgresql/*/main/postgresql.conf
# Check user permissions
sudo -u postgres psql -c "\du"
# Check for default passwords
sudo -u postgres psql -c "SELECT usename FROM pg_user WHERE passwd IS NULL;" Secure PostgreSQL configuration:
# /etc/postgresql/*/main/postgresql.conf
# Only listen on localhost (if no remote access needed)
listen_addresses = 'localhost'
# Enable SSL
ssl = on
ssl_cert_file = '/etc/postgresql/*/main/server.crt'
ssl_key_file = '/etc/postgresql/*/main/server.key'
# Use strong password encryption
password_encryption = scram-sha-256
# Connection limits
max_connections = 100 MySQL/MariaDB security:
# Run security script
sudo mysql_secure_installation
# Check for anonymous users
sudo mysql -e "SELECT User, Host FROM mysql.user WHERE User='';"
# Check for users without passwords
sudo mysql -e "SELECT User, Host FROM mysql.user WHERE authentication_string='';"
# Verify bind address
sudo grep bind-address /etc/mysql/mysql.conf.d/mysqld.cnf Docker Container Security
Audit container configurations:
# Check for privileged containers (BAD)
docker ps --filter="label=privileged=true" --format "{{.Names}}: {{.Labels}}"
# Check for containers with host network (RISKY)
docker ps --filter="network=host" --format "{{.Names}}: {{.Networks}}"
# Check for containers with root user
for container in $(docker ps -q); do
echo "Container: $(docker inspect --format='{{.Name}}' $container)"
docker exec $container whoami
done
# Check mounted volumes
docker ps --format "{{.Names}}" | xargs -I {} docker inspect {} | jq '.[].Mounts' Dockerfile security review:
# BAD - Running as root
FROM nginx:latest
COPY app /usr/share/nginx/html
# GOOD - Non-root user
FROM nginx:latest
RUN useradd -r -u 1001 appuser
USER appuser
COPY app /usr/share/nginx/html Docker Compose security:
# BAD - Overly permissive
version: '3.8'
services:
webapp:
image: myapp:latest
privileged: true
network_mode: host
volumes:
- /:/host
# GOOD - Restricted
version: '3.8'
services:
webapp:
image: myapp:latest
user: "1000:1000"
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
volumes:
- ./app:/app:ro Phase 4: Penetration Testing
Password Security Testing
Test password strength:
# Install john and hashcat
sudo apt install john hashcat
# Extract password hashes
sudo cat /etc/shadow > shadow.txt
# Crack with john
john shadow.txt
# Show cracked passwords
john --show shadow.txt SSH brute force testing:
# Install hydra
sudo apt install hydra
# Test SSH with common passwords (YOUR OWN SYSTEM ONLY)
hydra -l admin -P /usr/share/wordlists/rockyou.txt ssh://192.168.1.10
# Test web login
hydra -l admin -P passwords.txt http-post-form "//login.php:user=^USER^&pass=^PASS^:F=incorrect" Create strong password policy:
# Install pwquality
sudo apt install libpam-pwquality
# Configure password requirements
# /etc/security/pwquality.conf
minlen = 14
dcredit = -1
ucredit = -1
ocredit = -1
lcredit = -1
maxrepeat = 3
usercheck = 1 Web Application Testing
OWASP ZAP Scanner:
# Install ZAP
docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable zap-baseline.py \
-t http://192.168.1.10 -r zap-report.html
# Full scan
docker run -v $(pwd):/zap/wrk/:rw -t owasp/zap2docker-stable zap-full-scan.py \
-t http://192.168.1.10 -r zap-full-report.html SQLMap injection testing:
# Install sqlmap
sudo apt install sqlmap
# Test for SQL injection
sqlmap -u "http://192.168.1.10/page?id=1" --batch
# Enumerate databases
sqlmap -u "http://192.168.1.10/page?id=1" --dbs
# Extract data
sqlmap -u "http://192.168.1.10/page?id=1" -D database_name --tables XSS testing:
# Test common XSS vectors
curl "http://192.168.1.10/search?q=<script>alert('XSS')</script>"
# Test stored XSS
curl -X POST http://192.168.1.10/comment -d "text=<script>alert('XSS')</script>" Network Penetration Testing
Metasploit Framework:
# Install Metasploit
curl https://raw.githubusercontent.com/rapid7/metasploit-framework/master/msfupdate | sudo bash
# Launch msfconsole
msfconsole
# Search for exploits
search type:exploit platform:linux
# Example: Test EternalBlue (MS17-010)
use exploit/windows/smb/ms17_010_eternalblue
set RHOSTS 192.168.1.20
exploit Network vulnerability exploitation:
# Scan for vulnerabilities
nmap -sV --script=vulscan 192.168.1.0/24
# Test for weak SMB
nmap --script smb-vuln* 192.168.1.0/24
# Test for weak NFS
showmount -e 192.168.1.10 Phase 5: Reporting and Remediation
Security Audit Report Template
Create /home/admin/security/audit-report-YYYYMMDD.md:
# Security Audit Report
**Date**: 2025-12-17
**Auditor**: Your Name
**Scope**: Full homelab infrastructure
**Duration**: 4 hours
## Executive Summary
Total vulnerabilities found: 23
- Critical: 2
- High: 5
- Medium: 10
- Low: 6
**Overall Risk Level**: HIGH
Key findings:
1. Exposed SSH with default port and weak ciphers
2. Database accessible from WAN
3. Containers running as root
4. No backup encryption
5. Missing security headers on web services
## Detailed Findings
### Critical Vulnerabilities
#### CRIT-001: Database Exposed to Internet
- **Severity**: Critical
- **CVSS Score**: 9.8
- **Affected**: PostgreSQL on 192.168.1.15:5432
- **Description**: Database accessible from public internet with weak password
- **Impact**: Complete database compromise, data exfiltration
- **Remediation**:
1. Change bind address to 127.0.0.1
2. Implement firewall rules blocking external access
3. Rotate all database passwords
4. Enable SSL/TLS for connections
- **Timeline**: IMMEDIATE (within 24 hours)
- **Verification**: `nmap -p 5432 YOUR-PUBLIC-IP` should show filtered
#### CRIT-002: Unpatched Kernel Vulnerability
- **Severity**: Critical
- **CVE**: CVE-2024-XXXXX
- **Affected**: All hosts running kernel 5.15.0-52
- **Description**: Privilege escalation vulnerability in Linux kernel
- **Impact**: Local user can gain root access
- **Remediation**:
1. Update kernel: `sudo apt update && sudo apt upgrade linux-image-generic`
2. Reboot all affected hosts
- **Timeline**: IMMEDIATE (within 48 hours)
### High Vulnerabilities
#### HIGH-001: Docker Containers Running as Root
[Continue with detailed findings...]
## Recommendations
### Immediate Actions (0-7 days)
1. Patch critical vulnerabilities
2. Change all default passwords
3. Restrict database access
4. Update SSH configuration
5. Enable firewalls on all hosts
### Short-term Actions (1-4 weeks)
1. Implement automated patching
2. Deploy CrowdSec or Fail2ban
3. Enable audit logging
4. Set up security monitoring
5. Create backup encryption
### Long-term Actions (1-3 months)
1. Implement zero-trust networking
2. Deploy SIEM solution
3. Conduct quarterly security audits
4. Implement secrets management
5. Create incident response plan
## Technical Appendix
[Include raw scan outputs, screenshots, logs] Automated Audit Script
Create /usr/local/bin/security-audit.sh:
#!/bin/bash
# Homelab Security Audit Script
# Runs comprehensive security checks
set -e
REPORT_DIR="/var/log/security-audits/$(date +%Y%m%d)"
mkdir -p "$REPORT_DIR"
echo "=== Security Audit Started: $(date) ===" | tee "$REPORT_DIR/audit.log"
# System updates
echo "[*] Checking for system updates..." | tee -a "$REPORT_DIR/audit.log"
apt list --upgradable > "$REPORT_DIR/updates-available.txt" 2>&1
# Open ports
echo "[*] Scanning open ports..." | tee -a "$REPORT_DIR/audit.log"
ss -tulpn > "$REPORT_DIR/open-ports.txt"
# Failed login attempts
echo "[*] Checking failed logins..." | tee -a "$REPORT_DIR/audit.log"
grep "Failed password" /var/log/auth.log | tail -100 > "$REPORT_DIR/failed-logins.txt"
# World-writable files
echo "[*] Finding world-writable files..." | tee -a "$REPORT_DIR/audit.log"
find / -xdev -type f -perm -0002 2>/dev/null > "$REPORT_DIR/world-writable.txt"
# SUID/SGID files
echo "[*] Finding SUID/SGID files..." | tee -a "$REPORT_DIR/audit.log"
find / -type f \( -perm -4000 -o -perm -2000 \) -ls 2>/dev/null > "$REPORT_DIR/suid-sgid.txt"
# Docker security
if command -v docker &> /dev/null; then
echo "[*] Auditing Docker..." | tee -a "$REPORT_DIR/audit.log"
docker ps --format "{{.Names}}: {{.Ports}}" > "$REPORT_DIR/docker-containers.txt"
# Check for privileged containers
docker ps -q | xargs docker inspect --format '{{.Name}}: Privileged={{.HostConfig.Privileged}}' > "$REPORT_DIR/docker-privileged.txt"
fi
# SSL certificate expiry
echo "[*] Checking SSL certificates..." | tee -a "$REPORT_DIR/audit.log"
for domain in $(cat /etc/nginx/sites-enabled/* 2>/dev/null | grep server_name | awk '{print $2}' | tr -d ';'); do
echo "$domain:" >> "$REPORT_DIR/ssl-expiry.txt"
echo | openssl s_client -connect "$domain:443" -servername "$domain" 2>/dev/null | openssl x509 -noout -dates >> "$REPORT_DIR/ssl-expiry.txt" 2>&1 || true
done
# Firewall rules
echo "[*] Checking firewall..." | tee -a "$REPORT_DIR/audit.log"
if command -v ufw &> /dev/null; then
ufw status verbose > "$REPORT_DIR/firewall.txt"
else
iptables -L -n -v > "$REPORT_DIR/firewall.txt"
fi
# User accounts
echo "[*] Auditing user accounts..." | tee -a "$REPORT_DIR/audit.log"
awk -F: '$3 >= 1000 {print $1, $3, $6}' /etc/passwd > "$REPORT_DIR/users.txt"
# Check for users with empty passwords
awk -F: '($2 == "") {print $1}' /etc/shadow > "$REPORT_DIR/empty-passwords.txt"
# Sudoers
echo "[*] Checking sudo access..." | tee -a "$REPORT_DIR/audit.log"
grep -v '^#' /etc/sudoers | grep -v '^$' > "$REPORT_DIR/sudoers.txt"
# Running Lynis
if command -v lynis &> /dev/null; then
echo "[*] Running Lynis audit..." | tee -a "$REPORT_DIR/audit.log"
lynis audit system --quick --quiet > "$REPORT_DIR/lynis-audit.txt" 2>&1
fi
# Create summary
echo "
=== Security Audit Summary ===
Date: $(date)
Report directory: $REPORT_DIR
Key checks completed:
- System updates available: $(wc -l < $REPORT_DIR/updates-available.txt) packages
- Open ports: $(wc -l < $REPORT_DIR/open-ports.txt) services
- Failed logins: $(wc -l < $REPORT_DIR/failed-logins.txt) attempts
- World-writable files: $(wc -l < $REPORT_DIR/world-writable.txt) files
- SUID/SGID binaries: $(wc -l < $REPORT_DIR/suid-sgid.txt) files
- Users with empty passwords: $(wc -l < $REPORT_DIR/empty-passwords.txt) accounts
Review the detailed reports in: $REPORT_DIR
" | tee "$REPORT_DIR/summary.txt"
echo "[+] Audit complete!" | tee -a "$REPORT_DIR/audit.log" Make executable and schedule:
sudo chmod +x /usr/local/bin/security-audit.sh
# Run weekly via cron
sudo crontab -e
# Add: 0 2 * * 0 /usr/local/bin/security-audit.sh Hardening Checklist
System Hardening
Kernel hardening (/etc/sysctl.conf):
# IP forwarding (disable if not routing)
net.ipv4.ip_forward = 0
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_max_syn_backlog = 4096
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Ignore source routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Ignore ICMP pings
net.ipv4.icmp_echo_ignore_all = 1
# Protect against TCP time-wait assassination
net.ipv4.tcp_rfc1337 = 1
# Apply changes
sudo sysctl -p Disable unused services:
# List all services
systemctl list-unit-files --type=service
# Disable unnecessary services
sudo systemctl disable bluetooth.service
sudo systemctl disable cups.service
sudo systemctl disable avahi-daemon.service
# Stop and mask services
sudo systemctl stop bluetooth
sudo systemctl mask bluetooth File system security:
# Add secure mount options to /etc/fstab
/dev/sda1 / ext4 defaults,noatime,nodiratime 0 1
tmpfs /tmp tmpfs defaults,noatime,mode=1777,noexec,nodev,nosuid 0 0
tmpfs /var/tmp tmpfs defaults,noatime,mode=1777,noexec,nodev,nosuid 0 0
tmpfs /dev/shm tmpfs defaults,noexec,nodev,nosuid 0 0
# Remount
sudo mount -o remount /tmp
sudo mount -o remount /var/tmp Application Hardening
Nginx hardening:
# Hide version
server_tokens off;
# Disable unused methods
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}
# Rate limiting
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
limit_req zone=one burst=20;
# Buffer overflow protection
client_body_buffer_size 1K;
client_header_buffer_size 1k;
client_max_body_size 1m;
large_client_header_buffers 2 1k;
# Timeout protection
client_body_timeout 10;
client_header_timeout 10;
keepalive_timeout 5 5;
send_timeout 10; Docker hardening:
# Enable user namespaces
sudo nano /etc/docker/daemon.json
{
"userns-remap": "default",
"live-restore": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
sudo systemctl restart docker Compliance Standards
CIS Benchmarks
Apply CIS Docker Benchmark:
git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
sudo sh docker-bench-security.sh CIS Ubuntu/Debian:
# Download benchmark
wget https://downloads.cisecurity.org/
# Run automated checks
sudo lynis audit system --tests-from-group cis NIST Cybersecurity Framework
Map your security controls to NIST CSF:
- Identify: Asset inventory (completed in Phase 1)
- Protect: Implement controls (firewalls, authentication, encryption)
- Detect: Monitoring and logging (CrowdSec, Fail2ban, alerts)
- Respond: Incident response plan
- Recover: Backup and disaster recovery
Continuous Security Monitoring
Automated Daily Checks
Create /usr/local/bin/daily-security-check.sh:
#!/bin/bash
# Quick daily security check
LOG="/var/log/daily-security.log"
{
echo "=== Daily Security Check: $(date) ==="
# Check for new CVEs
echo "[*] Checking for security updates..."
apt list --upgradable 2>&1 | grep -i security
# Check listening ports
echo "[*] Unexpected open ports:"
diff <(sort /etc/security/known-ports.txt) <(ss -tulpn | awk '{print $5}' | cut -d: -f2 | sort -u) || true
# Check for failed logins
echo "[*] Failed login attempts (last 24h):"
grep "Failed password" /var/log/auth.log | grep "$(date +%b %d)" | wc -l
# Check disk usage
echo "[*] Disk usage:"
df -h | awk '$5 >= 80 {print "WARNING: " $0}'
# Check for rootkits
if command -v rkhunter &> /dev/null; then
echo "[*] Rootkit scan:"
rkhunter --check --skip-keypress --quiet
fi
echo ""
} | tee -a "$LOG" Schedule with cron:
sudo crontab -e
# Add: 0 8 * * * /usr/local/bin/daily-security-check.sh Conclusion
Security audits are not one-time eventsthey’re ongoing processes. Key takeaways:
- Regular schedule: Conduct full audits quarterly, quick checks weekly
- Automate what you can: Use scripts for repetitive tasks
- Document everything: Maintain audit reports and track remediation
- Prioritize by risk: Fix critical vulnerabilities first
- Test your defenses: Regular penetration testing validates controls
- Stay updated: Subscribe to security mailing lists and CVE feeds
A secure homelab requires continuous vigilance, but with proper tools and procedures, you can maintain professional-grade security without enterprise budgets.