Security advanced ⏱ 90 minutes

Security Audit: How to Harden Your Homelab

Complete security audit checklist for homelabs. Identify vulnerabilities and implement fixes.

By homelabbing.io • 2025-12-17
#security-audit#hardening#checklist#vulnerability#best-practices

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:

  1. Discovery: Map your attack surface
  2. Scanning: Identify vulnerabilities automatically
  3. Analysis: Manual verification and deep inspection
  4. Exploitation: Test if vulnerabilities are exploitable
  5. 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:

  1. Configuration � Targets � New Target
  2. Add your network: 192.168.1.0/24
  3. Create � Tasks � New Task
  4. Select target and scan config
  5. 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 --chown=appuser:appuser 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:

  1. Identify: Asset inventory (completed in Phase 1)
  2. Protect: Implement controls (firewalls, authentication, encryption)
  3. Detect: Monitoring and logging (CrowdSec, Fail2ban, alerts)
  4. Respond: Incident response plan
  5. 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.