- Document interactive installation process - Add complete EU country whitelist configuration - Include setup commands and wizard prompts - Clarify that geoip-shell was used instead of manual iptables setup
23 KiB
Network & Security Configuration
Complete guide for securing and networking your homelab.
SSH Security Setup
Initial Configuration
# Generate SSH key pair
ssh-keygen -t ed25519 -C "homelab-key" -f ~/.ssh/homelab_ed25519
# Copy public key to target
ssh-copy-id -i ~/.ssh/homelab_ed25519.pub user@target
SSH Hardening
Status: ✅ Complete - Port changed to 2222
Edit /etc/ssh/sshd_config:
# Disable root login
PermitRootLogin no
# Use key-based authentication only
PasswordAuthentication no
PubkeyAuthentication yes
# Change default port (CRITICAL - currently still on 22)
Port 2222
# Restrict users
AllowUsers hoborg
# Security settings
Protocol 2
X11Forwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
MaxStartups 2
Completed:
- ✅ Changed SSH port from 22 to 2222
- ✅ Updated router port forwarding rules
- ✅ External access via ak-homelab.duckdns.org:2222 working
Restart SSH: sudo systemctl restart sshd
Mosh Alternative (Investigation Needed)
Issue: SSH can be unreliable on WiFi connections with packet loss.
Mosh Benefits:
- Maintains connection during network switches (ethernet ↔ WiFi)
- Handles poor WiFi connections better
- Local echo for responsive typing
- Roaming support (IP changes don't break connection)
Installation:
# Server side
sudo pacman -S mosh
# Client side
mosh user@server
Requirements:
- UDP ports 60000-61000 open on router
- SSH still needed for initial authentication
Status: ✅ Local use working ❌ External blocked by ISP
Key Findings:
- Local mosh: Works perfectly (
mosh localhost,mosh 192.168.0.100) - External mosh: Blocked by ISP UDP port filtering on ports 60000-61000
- SSH still needed: Mosh uses SSH for initial authentication, then switches to UDP
ISP UDP Blocking Issue:
- Most ISPs block UDP ports 60000-61000 for "security"
- SSH works fine (TCP port 2222) but mosh fails (UDP 60000-61000)
- Router port forwarding is correct, but ISP drops UDP packets
Current Recommendation:
- Use mosh for local/internal network connections
- Stick with SSH for external connections until VPN is set up
- VPN tunnel can bypass ISP UDP blocking
SSH Client Configuration
Create ~/.ssh/config:
Host homelab
HostName ak-homelab.duckdns.org
User hoborg
Port 2222
IdentityFile ~/.ssh/homelab_ed25519
ServerAliveInterval 60
Usage:
# Connect via SSH
ssh homelab
# Connect via Mosh (uses SSH config automatically)
mosh homelab
Dynamic DNS with DuckDNS
Account Setup
- Create account at duckdns.org
- Create subdomain:
ak-homelab.duckdns.org - Get token from dashboard
Automatic IP Updates
Update script at ~/.local/scripts/duckdns.py (Python implementation)
Cron job for automatic updates:
# Update every 5 minutes
*/5 * * * * /home/hoborg/.local/scripts/duckdns.py >/dev/null 2>&1
Current Setup (Router-based)
Status: ✅ Migrated from script to router DynDNS
Changes made:
- ✅ Disabled cron job script (
*/5 * * * *entry removed) - ✅ Enabled router Dynamic DNS for ak-homelab.duckdns.org
- ⏳ Testing pending - Cannot force public IP change to verify
Router DynDNS Benefits:
- Immediate updates on IP change (vs 5-minute delay)
- Works when server is down
- Lower resource usage
Limitations:
- Likely IPv4-only (Sagemcom router limitation)
- Less control over update process
Testing
# Check current IP
curl -s https://ipinfo.io/ip
# Verify DNS resolution
nslookup ak-homelab.duckdns.org
# Check IPv6 (likely not updated by router)
nslookup -type=AAAA ak-homelab.duckdns.org
Testing will occur naturally when ISP changes public IP address.
VPN Setup with WireGuard
What is WireGuard?
WireGuard is a modern, lightweight VPN protocol that creates secure tunnels between devices. It encrypts all network traffic and routes it through a VPN server, making your internet connection private and secure.
Key benefits:
- Privacy: Hides your IP address and encrypts traffic
- Security: Protects against man-in-the-middle attacks on public WiFi
- Access: Bypass geo-restrictions and enables remote homelab access
- Performance: Much faster than OpenVPN with lower battery drain
- Simplicity: Easy to configure compared to other VPN protocols
When you need VPN:
- Accessing homelab remotely over internet
- Working from public WiFi frequently
- Need to bypass ISP restrictions
- Running public-facing services
Costs: WireGuard itself is free. Self-hosted VPN costs $5-20/month for VPS hosting.
Use cases:
- Access homelab services remotely (SSH, web interfaces, file shares)
- Secure connection on public WiFi
- Bypass ISP restrictions or geo-blocks
Performance: Much faster and lighter than OpenVPN, better battery life on mobile devices.
Server Configuration
Install WireGuard: pacman -S wireguard-tools
Generate keys:
wg genkey | tee server_private.key | wg pubkey > server_public.key
Server config /etc/wireguard/wg0.conf:
[Interface]
PrivateKey = <SERVER_PRIVATE_KEY>
Address = 10.0.0.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
PublicKey = <CLIENT_PUBLIC_KEY>
AllowedIPs = 10.0.0.2/32
Client Configuration
Generate client keys:
wg genkey | tee client_private.key | wg pubkey > client_public.key
Client config:
[Interface]
PrivateKey = <CLIENT_PRIVATE_KEY>
Address = 10.0.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = ak-homelab.duckdns.org:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Enable VPN
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
Firewall Configuration
UFW (Uncomplicated Firewall)
What it does: Controls what network traffic is allowed in/out of your server.
Key functions:
- Default deny: Blocks all incoming connections by default
- Port control: Open only specific ports you need (SSH, HTTP, etc.)
- Rate limiting: Prevent brute force attacks
- Application profiles: Pre-configured rules for common services
Why needed: Without firewall, all services are exposed to network attacks.
UFW Setup
# Install and enable UFW
pacman -S ufw
sudo ufw enable
# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# SSH access
sudo ufw allow 2222/tcp
# WireGuard
sudo ufw allow 51820/udp
# HTTP/HTTPS for services
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Advanced Rules
# Rate limiting for SSH
sudo ufw limit 2222/tcp
# Allow from specific networks
sudo ufw allow from 192.168.1.0/24 to any port 22
# Log denied connections
sudo ufw logging on
Network Security Best Practices
Port Management
- Change default ports: SSH (2222), WireGuard (51820)
- Close unused ports: Regular port scans with nmap
- Port forwarding: Only forward necessary ports
Access Control
- VPN-first approach: Access services through VPN tunnel
- IP whitelisting: Restrict access to known IPs when possible
- Rate limiting: Prevent brute force attacks
Monitoring
- Log analysis: Monitor
/var/log/auth.logfor SSH attempts - Network monitoring: Use netstat/ss to check listening ports
- Intrusion detection: Use fail2ban for automated blocking
fail2ban - Intrusion Prevention
What is fail2ban?
What it does: Automatically blocks IP addresses that show malicious behavior.
Key functions:
- Log monitoring: Watches system logs for suspicious activity
- Pattern detection: Identifies failed login attempts, scanning, etc.
- Automatic blocking: Temporarily bans offending IP addresses
- Customizable rules: Configure what triggers a ban and for how long
Common protections:
- SSH brute force attempts
- Web server attacks (404 scanning, etc.)
- Email server abuse
- Custom application attacks
Example: After 5 failed SSH login attempts in 10 minutes, ban IP for 1 hour.
Why important: Reduces server load and prevents automated attacks from succeeding through persistence.
fail2ban Installation & Setup
# Install fail2ban
sudo pacman -S fail2ban
# Enable and start service
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Basic SSH Protection Configuration
Edit /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
Configuration explained:
maxretry = 5: Ban after 5 failed attemptsbantime = 3600: Ban for 1 hour (3600 seconds)findtime = 600: 5 attempts within 10 minutes triggers banport = 2222: Monitor custom SSH port
Restart and Monitor
# Restart fail2ban to apply changes
sudo systemctl restart fail2ban
# Check status
sudo fail2ban-client status
sudo fail2ban-client status sshd
# View banned IPs
sudo fail2ban-client get sshd banned
Geographic IP Blocking with geoip-shell
Overview
Status: ✅ Implemented - Whitelist mode protecting all interfaces
Tool: geoip-shell v0.7.5 Repository: https://github.com/friendly-bits/geoip-shell Purpose: Geographic IP blocking for enhanced security
Current Configuration
- Firewall Backend: iptables
- IP Lists Source: RIPE (Réseaux IP Européens)
- Network Interfaces: All interfaces protected
- LAN Detection: Automatic subnet detection enabled
- Mode: Whitelist (only specified countries allowed)
- IP Families: IPv4 and IPv6 supported
- Update Schedule: Daily at 4:18 AM
- Last Update: September 17, 2025 at 00:57:41
Whitelisted Countries
Configuration: All EU member states plus European territories (full list accepted by geoip-shell)
AL (Albania), AD (Andorra), AM (Armenia), AT (Austria), AX (Åland Islands), AZ (Azerbaijan)
BY (Belarus), BE (Belgium), BA (Bosnia and Herzegovina), BG (Bulgaria)
HR (Croatia), CY (Cyprus), CZ (Czech Republic), DK (Denmark)
EE (Estonia), FO (Faroe Islands), FI (Finland), FR (France)
GE (Georgia), DE (Germany), GI (Gibraltar), GR (Greece)
GG (Guernsey), HU (Hungary), IS (Iceland), IE (Ireland)
IM (Isle of Man), IT (Italy), JE (Jersey), KZ (Kazakhstan)
LV (Latvia), LI (Liechtenstein), LT (Lithuania), LU (Luxembourg)
MT (Malta), MD (Moldova), MC (Monaco), ME (Montenegro)
NL (Netherlands), MK (North Macedonia), NO (Norway), PL (Poland)
PT (Portugal), RO (Romania), RU (Russia), SM (San Marino)
RS (Serbia), SK (Slovakia), SI (Slovenia), ES (Spain)
SE (Sweden), CH (Switzerland), TR (Turkey), UA (Ukraine)
GB (United Kingdom), VA (Vatican City)
Network Exceptions (Always Allowed)
IPv4 Networks:
172.18.0.0/16- Docker network172.17.0.0/16- Docker network169.254.0.0/16- Link-local addresses192.168.0.0/24- Local LAN172.20.0.0/16- Docker network172.19.0.0/16- Docker network
IPv6 Networks:
fdaa:bbcc:ddee::/64- Custom networkfe80::/10- Link-local addresses
Security Impact
- Geographic Blocking: Blocks all traffic from countries not in whitelist
- Comprehensive Coverage: Both IPv4 and IPv6 protection
- Full Protocol Coverage: TCP and UDP traffic controlled
- Network Awareness: Automatically detects and allows local networks
- Persistence: Rules survive system reboots
- Automatic Updates: IP lists updated daily
Integration with Existing Security
- Complements fail2ban: Provides geographic layer above intrusion detection
- Works with UFW: Uses iptables backend compatible with UFW
- Docker Compatible: Automatically allows Docker networks
- LAN Friendly: Preserves local network access
Monitoring
# Check geoip-shell status
geoip-shell status
# View logs
journalctl -u geoip-shell
tail -f /var/log/geoip-shell.log
Installation and Setup
Installation Method: Interactive CLI setup using geoip-shell installer
The tool was configured using its interactive setup wizard, which prompted for configuration choices and automatically applied all necessary changes to the firewall. This approach ensures proper integration with the existing firewall backend.
Key Configuration Decisions:
- Whitelist Mode: Chosen over blacklist for better control
- European Focus: All EU member states plus European territories
- Country List: Complete list of countries accepted by geoip-shell
- Network Exceptions: Docker and LAN networks automatically detected
- Dual Stack: Both IPv4 and IPv6 protection enabled
- Full Protocol Coverage: TCP and UDP both protected
Setup Command:
# Download and run installer
curl -fsSL https://raw.githubusercontent.com/friendly-bits/geoip-shell/main/install.sh | bash
# Run configuration wizard (prompted automatically after install)
geoip-shell configure
During the interactive setup, the wizard prompted for:
- Firewall backend selection (iptables chosen)
- IP list source (RIPE selected)
- Mode selection (whitelist chosen)
- Country selection (all EU countries entered)
- Network interface configuration (all interfaces selected)
Detailed Documentation
For complete setup details, see docs/geoip-blocking.md
Permanent Ban System for Repeat Offenders
Overview
Status: ✅ Implemented - Automated permanent banning of persistent attackers
Script: scripts/permanent-ban-repeat-offenders.sh
Purpose: Automatically identify and permanently ban IPs that have been banned by fail2ban more than a threshold number of times
How It Works
Detection Logic
- Log Analysis: Scans
/var/log/fail2ban.log*for ban entries - IP Extraction: Extracts IP addresses from ban log entries
- Frequency Counting: Counts how many times each IP has been banned
- Threshold Check: Identifies IPs banned more than the threshold (4 times)
Permanent Banning Process
For each repeat offender:
- Country Lookup: Uses
whoisto determine the country of origin - Banlist Update: Adds IP to
/etc/fail2ban/permanent-banlist.conf - Firewall Rule: Creates permanent iptables DROP rule
- Persistence: Saves iptables rules to
/etc/iptables/iptables.rules - Service Reload: Reloads fail2ban to recognize the updated banlist
Configuration
Threshold Settings
THRESHOLD=4 # Ban after 4 fail2ban bans
File Locations
- Log File:
/var/log/permanent-ban.log - Banlist:
/etc/fail2ban/permanent-banlist.conf - Iptables Rules:
/etc/iptables/iptables.rules
Cron Schedule
- Frequency: Daily at 2:00 AM (
0 2 * * *) - User: root
- Command:
/home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh
Security Benefits
Multi-Layer Protection
- fail2ban: Temporary bans for suspicious activity
- Permanent Bans: Long-term blocking of persistent attackers
- Geographic Blocking: Country-level filtering via geoip-shell
- Network-Level: iptables rules at the firewall level
Attack Prevention
- Brute Force: Blocks IPs that repeatedly attempt attacks
- Botnets: Prevents automated attacks from compromised hosts
- Persistence: Maintains bans across system reboots
- Resource Protection: Reduces server load from repeat offenders
Monitoring and Maintenance
Log Analysis
# View permanent ban activity
tail -f /var/log/permanent-ban.log
# Check current permanent bans
cat /etc/fail2ban/permanent-banlist.conf
# View iptables permanent rules
iptables -L | grep DROP
Manual Execution
# Run the script manually (requires root)
sudo /home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh
Unban Procedure
To manually unban a permanently banned IP:
# Remove from banlist
sudo sed -i "/^192\.168\.1\.100/d" /etc/fail2ban/permanent-banlist.conf
# Remove iptables rule (find the rule number first)
sudo iptables -L --line-numbers | grep "192.168.1.100"
sudo iptables -D INPUT <rule_number>
# Save iptables rules
sudo iptables-save > /etc/iptables/iptables.rules
# Reload fail2ban
sudo systemctl reload fail2ban
Integration with Security Stack
Complementary Tools
- fail2ban: Provides temporary bans that feed into permanent ban detection
- geoip-shell: Geographic blocking at the network level
- UFW: Additional firewall layer
- SSH Hardening: Reduces initial attack surface
Workflow
Attack Attempt → fail2ban Detection → Temporary Ban → Repeat Offense → Permanent Ban → Geographic Block
Troubleshooting
Common Issues
- Script Not Running: Check cron job configuration
- Permission Errors: Ensure script is executable and paths are correct
- whois Failures: Some IPs may not return country information
- iptables-save Issues: Check if iptables-persistent is installed
Diagnostic Commands
# Check cron service
sudo systemctl status cron
# Test script manually
sudo bash -x /home/hoborg/homelab/scripts/permanent-ban-repeat-offenders.sh
# Verify iptables rules
sudo iptables -L -n | grep DROP
# Check fail2ban integration
sudo fail2ban-client status
Router Configuration
Port Forwarding
Forward these ports to your homelab server:
- SSH: External port → Internal 2222
- WireGuard: 51820 → 51820
- Web services: 80/443 → 80/443 (if needed)
Security Settings
- Disable WPS: Turn off WiFi Protected Setup
- Strong WiFi password: WPA3 with complex passphrase
- Guest network: Separate network for guests
- Firmware updates: Keep router firmware current
Network Planning
Dual Network Interface Issue (Critical)
Problem: Server has both ethernet and WiFi interfaces. When switching between connections, IP address changes from ethernet (192.168.0.22) to different WiFi IP, breaking SSH connections and port forwards.
Limitation: Most routers don't allow DHCP reservation of same IP for multiple MAC addresses.
Solutions:
Option 1: Static IP Configuration (Recommended) Configure both interfaces with same static IP:
# Check interface names
ip link show
# Configure ethernet interface
sudo systemctl edit --full systemd-networkd
# Create /etc/systemd/network/20-ethernet.network
[Match]
Name=enp*
[Network]
DHCP=no
Address=192.168.0.100/24
Gateway=192.168.0.1
DNS=192.168.0.1
# Create /etc/systemd/network/25-wifi.network
[Match]
Name=wlp*
[Network]
DHCP=no
Address=192.168.0.100/24
Gateway=192.168.0.1
DNS=192.168.0.1
Option 2: Hostname-based Access Use local hostname resolution instead of IP:
# Access via hostname (works for both interfaces)
ssh hoborg@ak-homelab.local
# or configure local DNS/mDNS
Option 3: Bridge Networking Create bridge combining both interfaces for automatic failover:
# Advanced: Bridge both interfaces
ip link add name br0 type bridge
ip link set enp3s0 master br0
ip link set wlp2s0 master br0
Current Setup:
- Router: 192.168.0.1
- Ethernet: 192.168.0.100 (static IP achieved)
- WiFi: Static IP needed (same .100)
- External: ak-homelab.duckdns.org ✅
- SSH: Port 2222 ✅
Network Boot Issues Troubleshooting
Problem 1: Network not available after reboot until GUI login
Initial diagnosis: Thought to be NetworkManager sleep behavior.
Actual root cause: System auto-suspend due to closed laptop lid (HandleLidSwitch=suspend).
Symptoms:
- SSH inaccessible after reboot
- Network comes up only when laptop lid is opened
- System logs show "Suspending..." followed by "Lid opened"
Solution Applied:
- systemd-logind configuration: Disable lid switch handling for headless server operation
- Static IP configuration: Eliminate DHCP negotiation delays
- High connection priority: Ensure ethernet connects first
Configuration files created:
/etc/systemd/logind.conf.d/01-server-logind.conf:
[Login]
# Disable all power management triggers for headless server
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore
# Prevent automatic suspend/hibernate
IdleAction=ignore
# Keep system running even when no users logged in
KillUserProcesses=no
NetworkManager commands:
nmcli connection modify "Wired connection 2" connection.autoconnect-priority 10
nmcli connection modify "Wired connection 2" ipv4.method manual ipv4.addresses 192.168.0.100/24 ipv4.gateway 192.168.0.1 ipv4.dns "84.2.44.8 84.2.46.8"
Deployment:
sudo cp config/systemd/01-server-logind.conf /etc/systemd/logind.conf.d/
sudo systemctl restart systemd-logind
sudo cp config/networkmanager/01-homelab.conf /etc/NetworkManager/conf.d/
sudo systemctl reload NetworkManager
Diagnostic Commands
Check system power state:
systemctl status systemd-logind # Check for suspend/lid events
loginctl show-session # Current power management settings
Check network connectivity:
systemctl status NetworkManager
nmcli device status
nmcli connection show "Wired connection 2"
ip addr show enp4s0
Monitor boot process:
journalctl -b -u NetworkManager # Network startup logs
journalctl -b -u sshd # SSH service logs
journalctl -b -u systemd-logind # Power management events
Result: ✅ RESOLVED - SSH accessible immediately on boot, lid closure no longer suspends system.
Network Interface Identification:
- enp3s0f0: First ethernet port (98:fa:9b:f1:06:d5)
- enp4s0: Second ethernet port (98:fa:9b:f1:06:d4) ← Use this one
- wlp1s0: WiFi interface (0c:dd:24:e6:0f:87)
Issue Solved: Dual ethernet ports caused MAC address confusion when cable was moved between ports. Stick to enp4s0 consistently.
IP Address Scheme
- Router: 192.168.0.1
- Homelab server: 192.168.0.100 (target static IP)
- Current ethernet: 192.168.0.22 (can migrate to .100)
- DHCP range: 192.168.0.10-99 (excluding static IPs)
- VPN subnet: 10.0.0.0/24
DNS Configuration
- Primary DNS: Router (192.168.1.1)
- Secondary DNS: 1.1.1.1, 8.8.8.8
- Local domain: homelab.local
- Dynamic DNS: ak-homelab.duckdns.org
Service Architecture
Internet → Router → Homelab Server
↓
┌─────────────────────────────┐
│ SSH (2222) │
│ WireGuard VPN (51820) │
│ Web Services (80/443) │
│ Monitoring & Logging │
└─────────────────────────────┘
Backup and Recovery
Configuration Backups
- SSH keys: Store securely, separate from server
- WireGuard configs: Document peer configurations
- Firewall rules: Export UFW rules with
ufw status numbered
Network Documentation
- IP mappings: Document static assignments
- Port forwards: List all forwarded ports and purposes
- Access credentials: Secure storage of passwords/keys