# Comprehensive Linux Server Hardening Guide A complete guide to securing and hardening a Linux server with practical commands and configurations. ## Table of Contents - [Initial Setup](#initial-setup) - [System Updates](#system-updates) - [User Management](#user-management) - [SSH Hardening](#ssh-hardening) - [Firewall Configuration (UFW)](#firewall-configuration-ufw) - [Fail2Ban - Intrusion Prevention](#fail2ban---intrusion-prevention) - [Apache Web Server Security](#apache-web-server-security) - [DDoS Protection](#ddos-protection) - [Mail Server Security](#mail-server-security) - [Additional Security Measures](#additional-security-measures) - [Monitoring and Logging](#monitoring-and-logging) --- # Installation Follow the guide below. - If you are willing to risk it run: `minimal-priming.sh` for minimal installation. - Or when daring: `advanced-priming.sh` for complete from the ground up installation and hardening. Copy a script to a locaton on your server (usually: /usr/local/bin/ or /root/) and then make it executable: `chmod +x yourscript.sh` Check the scripts, some things might break if you are not careful. Enter all details in both .sh scripts before running it. No warranty is given. **Things might break.** Always test configurations in a staging environment before applying to production servers. Keep backups before making significant changes. Also, be sure to generate proper SSH keys before running the scripts. It is always better to do all of this *manually*, but sometimes .sh files can be useful for whatever reason. > Along the way, in a terminal window if you need to copy important information: `Ctrl+Shift+C` this will not kill a running command nor the terminal window, instead of just `Ctrl+C`. ## Initial Setup ### Update Package Lists ```bash sudo apt update sudo apt upgrade -y sudo apt dist-upgrade -y sudo apt autoremove -y ``` ### Set Hostname ```bash sudo hostnamectl set-hostname your-server-name echo "127.0.0.1 your-server-name" | sudo tee -a /etc/hosts ``` --- ## System Updates ### Enable Automatic Security Updates ```bash sudo apt install unattended-upgrades -y sudo dpkg-reconfigure --priority=low unattended-upgrades ``` ### Configure Automatic Updates ```bash sudo nano /etc/apt/apt.conf.d/50unattended-upgrades ``` Add or verify these lines: ```conf Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; }; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; ``` --- ## User Management ### Create Non-Root User with Sudo Privileges ```bash # Create new user sudo adduser deployuser # Add to sudo group sudo usermod -aG sudo deployuser # Switch to new user su - deployuser ``` ### Disable Root Login ```bash sudo passwd -l root ``` ### Set Strong Password Policy ```bash sudo apt install libpam-pwquality -y sudo nano /etc/security/pwquality.conf ``` Configure password requirements: ```conf minlen = 14 dcredit = -1 ucredit = -1 ocredit = -1 lcredit = -1 ``` --- ## SSH Hardening ### Install and Configure SSH ```bash sudo apt install openssh-server -y sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup sudo nano /etc/ssh/sshd_config ``` ### Generate and Deploy SSH Keys On your local machine: ```bash ssh-keygen -t ed25519 -C "your_email@example.com" ssh-copy-id -i ~/.ssh/id_ed25519.pub deployuser@your_server_ip -p 2222 ``` Or manually: ```bash # On server mkdir -p ~/.ssh chmod 700 ~/.ssh nano ~/.ssh/authorized_keys # Paste your public key chmod 600 ~/.ssh/authorized_keys ``` ### Recommended SSH Configuration ```conf # Change default port (choose something between 1024-65535) Port 2222 # Disable root login PermitRootLogin no # Use SSH Protocol 2 only Protocol 2 # Limit authentication attempts MaxAuthTries 3 MaxSessions 2 # Disable password authentication (IMPORTANT: ! after setting up SSH keys !) PasswordAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no # Disable X11 forwarding X11Forwarding no # Set login grace time LoginGraceTime 30 # Allow specific users only AllowUsers deployuser # Disable host-based authentication HostbasedAuthentication no # Client timeout ClientAliveInterval 300 ClientAliveCountMax 2 # Disable tunneling PermitTunnel no AllowTcpForwarding no ``` ### Restart SSH Service ```bash sudo systemctl restart sshd sudo systemctl enable sshd ``` --- ## Firewall Configuration (UFW) > TIP: By restricting SSH access only to your home or work IP you greatly reduce unauthorized access. Redundancy: If you have a dynamic IP, but own a fixed VPN, also set the fixed VPN IP to allow the SSH port to be accesible. In this way, you always have access without locking yourself out permanently. ### Install and Configure UFW ```bash sudo apt install ufw -y # Default policies sudo ufw default deny incoming sudo ufw default allow outgoing # IMPORTANT: Allow SSH from specific IP/range ONLY (prevents lockout) # Replace with your actual IP address or range sudo ufw allow from 777.7.777.7/24 to any port 2222 proto tcp comment 'YOUR IP: 777.7.777.7 SSH from office' # OR for a single IP: sudo ufw allow from 777.7.777.7 to any port 2222 proto tcp comment 'YOUR IP: 777.7.777.7 SSH from admin' # OR for standard port 22: sudo ufw allow from 777.7.777.7 to any port 22 proto tcp comment 'YOUR IP: 777.7.777.7 SSH from admin' # Allow HTTP and HTTPS (publicly accessible) sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Allow mail server ports (if needed) sudo ufw allow 25/tcp # SMTP sudo ufw allow 587/tcp # Submission sudo ufw allow 993/tcp # IMAPS sudo ufw allow 995/tcp # POP3S # Enable firewall sudo ufw enable # Check status sudo ufw status verbose sudo ufw status numbered # Shows rule numbers ``` ### Advanced UFW Rules ```bash # Deny specific IP from accessing anything sudo ufw deny from 192.0.2.100 # Allow specific IP to specific port only sudo ufw allow from 777.7.777.7 to any port 3306 proto tcp comment 'MySQL from app server' # Log denied connections (useful for monitoring) sudo ufw logging on sudo ufw logging medium # or 'high' for more detail ``` ### UFW Rate Limiting (DDoS Protection) ```bash # Limit SSH connections sudo ufw limit 2222/tcp # For web servers sudo ufw limit 80/tcp sudo ufw limit 443/tcp ``` --- ## Fail2Ban - Intrusion Prevention ### Install Fail2Ban ```bash sudo apt install fail2ban -y ``` ### Configure Fail2Ban ```bash sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local sudo nano /etc/fail2ban/jail.local ``` ### Basic Fail2Ban Configuration ```ini [DEFAULT] bantime = 3600 findtime = 600 maxretry = 3 destemail = your-email@example.com sendername = Fail2Ban action = %(action_mwl)s [sshd] enabled = true port = 2222 logpath = /var/log/auth.log maxretry = 3 [apache-auth] enabled = true port = http,https logpath = /var/log/apache*/*error.log [apache-badbots] enabled = true port = http,https logpath = /var/log/apache*/*access.log maxretry = 2 [apache-noscript] enabled = true [apache-overflows] enabled = true [apache-nohome] enabled = true [apache-botsearch] enabled = true ``` ### Start Fail2Ban ```bash sudo systemctl enable fail2ban sudo systemctl start fail2ban sudo fail2ban-client status ``` --- ## Apache Web Server Security ### Install Apache ```bash sudo apt install apache2 -y ``` ### Hide Apache Version ```bash sudo nano /etc/apache2/conf-available/security.conf ``` ```apache ServerTokens Prod ServerSignature Off TraceEnable Off ``` ### Enable Security Modules ```bash sudo a2enmod headers sudo a2enmod ssl sudo a2enmod rewrite sudo systemctl restart apache2 ``` ### Security Headers Configuration ```bash sudo nano /etc/apache2/conf-available/security-headers.conf ``` ```apache # Prevent clickjacking Header always set X-Frame-Options "SAMEORIGIN" # XSS Protection Header always set X-XSS-Protection "1; mode=block" # Prevent MIME type sniffing Header always set X-Content-Type-Options "nosniff" # Referrer Policy Header always set Referrer-Policy "strict-origin-when-cross-origin" # Content Security Policy Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" # HSTS (enable after testing) # Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" ``` ```bash sudo a2enconf security-headers sudo systemctl restart apache2 ``` ### Disable Directory Listing ```bash sudo nano /etc/apache2/apache2.conf ``` ```apache Options -Indexes +FollowSymLinks AllowOverride All Require all granted ``` ### Install ModSecurity (Web Application Firewall) ```bash sudo apt install libapache2-mod-security2 -y sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf sudo nano /etc/modsecurity/modsecurity.conf ``` Change: ``` SecRuleEngine On ``` ### Install OWASP Core Rule Set ```bash cd /tmp wget https://github.com/coreruleset/coreruleset/archive/v3.3.0.tar.gz tar -xvzf v3.3.0.tar.gz sudo mv coreruleset-3.3.0 /etc/modsecurity/crs sudo cp /etc/modsecurity/crs/crs-setup.conf.example /etc/modsecurity/crs/crs-setup.conf ``` ```bash sudo nano /etc/apache2/mods-enabled/security2.conf ``` Add: ```apache IncludeOptional /etc/modsecurity/crs/crs-setup.conf IncludeOptional /etc/modsecurity/crs/rules/*.conf ``` ```bash sudo systemctl restart apache2 ``` --- ## DDoS Protection ### Install and Configure ModEvasive (Apache DDoS Protection) ```bash sudo apt install libapache2-mod-evasive -y sudo mkdir -p /var/log/mod_evasive sudo chown -R www-data:www-data /var/log/mod_evasive ``` ```bash sudo nano /etc/apache2/mods-available/evasive.conf ``` ```apache # Hash table size (increase for busy servers) DOSHashTableSize 3097 # Maximum requests from same IP to same page within interval DOSPageCount 5 DOSPageInterval 1 # Maximum requests from same IP to entire site within interval DOSSiteCount 100 DOSSiteInterval 1 # How long to block offending IPs (seconds) DOSBlockingPeriod 60 # Email notifications DOSEmailNotify your-email@example.com # Log directory DOSLogDir /var/log/mod_evasive # Whitelist IPs (optional - add trusted IPs) # DOSWhitelist 127.0.0.1 # DOSWhitelist 192.168.1.* # System command to run when IP is blocked (optional) # DOSSystemCommand "sudo /usr/local/bin/ban_ip.sh %s" ``` ```bash sudo a2enmod evasive sudo systemctl restart apache2 ``` ### Test ModEvasive ```bash # Install testing tool sudo apt install apache2-utils -y # Test (this should trigger blocking after several requests) for i in {1..100}; do curl -I http://localhost/ ; done # Check logs sudo tail -f /var/log/mod_evasive/dos-*.log ``` ### Kernel Parameter Tuning for DDoS Protection ```bash sudo nano /etc/sysctl.conf ``` Add: ```conf # IP Spoofing protection net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # Ignore ICMP ping requests net.ipv4.icmp_echo_ignore_all = 1 # Ignore Directed pings net.ipv4.icmp_echo_ignore_broadcasts = 1 # Disable source packet routing net.ipv4.conf.all.accept_source_route = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 net.ipv6.conf.default.accept_source_route = 0 # Ignore send redirects net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # Block SYN attacks net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_syn_retries = 5 # Log Martians net.ipv4.conf.all.log_martians = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 # Ignore ICMP redirects net.ipv4.conf.all.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv6.conf.default.accept_redirects = 0 # Increase system file descriptor limit fs.file-max = 65535 # Connection tracking net.netfilter.nf_conntrack_max = 1000000 ``` Apply changes: ```bash sudo sysctl -p ``` --- ## Mail Server Security ### Install Postfix and Dovecot ```bash sudo apt install postfix dovecot-core dovecot-imapd dovecot-pop3d -y ``` ### Configure Postfix Security ```bash sudo nano /etc/postfix/main.cf ``` ```conf # Basic settings myhostname = mail.yourdomain.com mydomain = yourdomain.com myorigin = $mydomain inet_interfaces = all mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain # TLS settings smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key smtpd_use_tls = yes smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache smtpd_tls_security_level = may smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1 # Authentication smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes smtpd_sasl_security_options = noanonymous smtpd_sasl_local_domain = $myhostname broken_sasl_auth_clients = yes # Restrictions smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, reject_invalid_hostname, reject_non_fqdn_hostname, reject_non_fqdn_sender, reject_non_fqdn_recipient, reject_unknown_sender_domain, reject_unknown_recipient_domain, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net, permit smtpd_helo_restrictions = permit_mynetworks, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname # Rate limiting smtpd_client_connection_rate_limit = 10 smtpd_client_message_rate_limit = 20 ``` ### Configure Dovecot Security ```bash sudo nano /etc/dovecot/conf.d/10-auth.conf ``` ```conf disable_plaintext_auth = yes auth_mechanisms = plain login ``` ```bash sudo nano /etc/dovecot/conf.d/10-ssl.conf ``` ```conf ssl = required ssl_cert =