Server security isn't just about applying patches and setting passwords. Years of supporting hosting environments have shown me that even experienced administrators make predictable, dangerous mistakes—often not from ignorance, but from rushed deployments, inherited configurations, or outdated habits. This guide walks through the most common server security mistakes I've seen repeatedly and shows you the correct approach for each.
Authentication and Access Control Mistakes
Mistake 1: Using Password Authentication for SSH
Password authentication remains enabled on countless production servers. Attackers run automated brute-force attempts against SSH ports constantly, and weak passwords fall quickly.
The correct approach:
Disable password authentication entirely and use SSH key pairs:
# Generate a strong SSH key pair (on your local machine)
ssh-keygen -t ed25519 -C "admin@yourserver"
# Copy the public key to your server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip
# On the server, edit SSH configuration
sudo nano /etc/ssh/sshd_config
Set these directives:
PasswordAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart sshd
Test your key-based login from a separate terminal session before closing your current one.
Mistake 2: Allowing Root Login
Permitting direct root SSH access creates unnecessary risk. If an attacker compromises your credentials, they immediately have full system control.
The correct approach:
Create a dedicated user account with sudo privileges:
# Create a new user
sudo adduser adminuser
# Add to sudo group (Debian/Ubuntu)
sudo usermod -aG sudo adminuser
# Or wheel group (CentOS/RHEL/AlmaLinux)
sudo usermod -aG wheel adminuser
Then disable root login in /etc/ssh/sshd_config:
PermitRootLogin no
This creates an audit trail—you'll see which user account performed administrative actions.
Mistake 3: Not Using Two-Factor Authentication
Relying solely on SSH keys or passwords leaves accounts vulnerable if credentials are compromised or keys are stolen.
The correct approach:
Implement two-factor authentication using Google Authenticator:
# Install required packages (Ubuntu/Debian)
sudo apt install libpam-google-authenticator
# Run as your user account (not root)
google-authenticator
Follow the prompts to generate a QR code, then edit /etc/pam.d/sshd and add:
auth required pam_google_authenticator.so
In /etc/ssh/sshd_config, set:
ChallengeResponseAuthentication yes
UsePAM yes
Restart SSH and test thoroughly before closing your session.
Firewall and Network Security Mistakes
Mistake 4: Running Without a Firewall
Many administrators assume their hosting provider's network firewall is sufficient and skip configuring host-based firewalls. This leaves services exposed if network-level protections fail or are misconfigured.
The correct approach:
Implement a host-based firewall using ufw (Uncomplicated Firewall) or firewalld:
# Ubuntu/Debian with ufw
sudo apt install ufw
# Allow SSH first (critical!)
sudo ufw allow 22/tcp
# Allow other required services
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable the firewall
sudo ufw enable
# Check status
sudo ufw status verbose
For CentOS/RHEL/AlmaLinux with firewalld:
sudo systemctl start firewalld
sudo systemctl enable firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Implement default-deny policies: block everything except explicitly allowed services.
Mistake 5: Leaving Unnecessary Ports Open
Default server configurations often expose services like MySQL or Redis to the internet when they should only accept local connections.
The correct approach:
Audit listening services:
sudo ss -tulpn
For database services that don't need remote access, bind them to localhost only. In MySQL's /etc/mysql/mysql.conf.d/mysqld.cnf:
bind-address = 127.0.0.1
For Redis in /etc/redis/redis.conf:
bind 127.0.0.1
Restart services after changes and verify they're no longer exposed:
sudo systemctl restart mysql
sudo systemctl restart redis
Mistake 6: Using Default SSH Port
While security through obscurity isn't a complete defense, changing the default SSH port dramatically reduces automated attack attempts in server logs.
The correct approach:
Change the SSH port in /etc/ssh/sshd_config:
Port 2222
Update your firewall rules before restarting SSH:
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
sudo systemctl restart sshd
Document the new port and update your SSH client configuration in ~/.ssh/config:
Host yourserver
HostName server-ip
Port 2222
User adminuser
Update and Patch Management Mistakes
Mistake 7: Delaying Security Updates
Postponing security updates "until a maintenance window" allows known vulnerabilities to remain exploitable. I've seen servers compromised because critical patches sat unapplied for weeks.
The correct approach:
Enable automatic security updates for critical patches:
# Ubuntu/Debian
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboots if needed:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
For CentOS/RHEL/AlmaLinux, configure yum-cron or dnf-automatic:
sudo yum install yum-cron
sudo systemctl enable yum-cron
sudo systemctl start yum-cron
Edit /etc/yum/yum-cron.conf and set:
apply_updates = yes
Manually review updates weekly and schedule maintenance for major version upgrades.
Mistake 8: Running Outdated Operating Systems
Continuing to run operating system versions past their end-of-life date leaves servers without security patches. This is especially common with older CentOS 6 or Ubuntu 16.04 installations.
The correct approach:
Plan upgrades well before EOL dates:
- Review your distribution's support lifecycle
- Test application compatibility on newer versions
- Schedule migrations during low-traffic periods
- Never skip major versions during upgrades
For in-place upgrades (Ubuntu example):
# Ensure current system is fully updated first
sudo apt update && sudo apt upgrade
sudo apt dist-upgrade
# Then upgrade
sudo do-release-upgrade
For production systems, provision a new server with the current OS version and migrate rather than upgrading in place.
File System and Permission Mistakes
Mistake 9: Incorrect File Permissions
Overly permissive file permissions (777, world-writable directories) are common in rushed troubleshooting but create security holes.
The correct approach:
Follow the principle of least privilege:
# Web application files
sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
# Configuration files with sensitive data
sudo chmod 600 /etc/myapp/config.ini
sudo chown appuser:appuser /etc/myapp/config.ini
# SSH keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
Audit for dangerous permissions:
# Find world-writable files
sudo find / -type f -perm -002 -ls 2>/dev/null
# Find files with SUID bit set
sudo find / -perm -4000 -ls 2>/dev/null
Mistake 10: Not Securing Sensitive Configuration Files
Leaving database credentials, API keys, and other secrets in world-readable files or version control repositories is surprisingly common.
The correct approach:
# Store credentials in protected files
sudo mkdir -p /etc/myapp
sudo nano /etc/myapp/.env
# Set strict permissions
sudo chmod 600 /etc/myapp/.env
sudo chown appuser:appuser /etc/myapp/.env
Add sensitive files to .gitignore:
.env
*.key
*.pem
config/secrets.yml
For committed secrets, rotate them immediately—removing them from history isn't sufficient since they may have been cloned.
Logging and Monitoring Mistakes
Mistake 11: Not Monitoring Authentication Logs
Failing to review authentication logs means you won't notice brute-force attempts, successful breaches, or suspicious access patterns until damage is done.
The correct approach:
Regularly review authentication logs:
# Recent SSH logins
sudo lastlog
# Failed login attempts
sudo grep "Failed password" /var/log/auth.log | tail -20
# Successful logins
sudo grep "Accepted" /var/log/auth.log | tail -20
Implement fail2ban to automatically block repeated failed attempts:
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Create /etc/fail2ban/jail.local:
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
findtime = 600
Mistake 12: Insufficient Disk Space for Logs
Log rotation failures due to full disks can mask security events and cause service disruptions.
The correct approach:
Configure proper log rotation in /etc/logrotate.d/:
/var/log/myapp/*.log {
daily
rotate 14
compress
delaycompress
missingok
notifempty
create 0640 appuser appuser
}
Monitor disk usage:
# Check disk space
df -h
# Find largest directories
sudo du -sh /var/log/* | sort -h
Set up alerting when disk usage exceeds thresholds.
Service Configuration Mistakes
Mistake 13: Using Default Credentials
Default passwords for databases, control panels, and applications remain unchanged in countless deployments.
The correct approach:
Immediately change all default credentials during initial setup:
# MySQL root password
sudo mysql_secure_installation
# Create application-specific database users
mysql -u root -p
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'strong-random-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
Use a password manager to generate and store strong unique passwords. Never reuse passwords across services.
Mistake 14: Running Services as Root
Running application processes as the root user means any vulnerability in the application grants full system access.
The correct approach:
Create dedicated service accounts:
# Create a system user for the application
sudo useradd -r -s /bin/false appuser
# Set ownership
sudo chown -R appuser:appuser /opt/myapp
Configure systemd service files to use the dedicated user:
[Service]
User=appuser
Group=appuser
ExecStart=/opt/myapp/bin/start
Drop privileges in application code if the service must bind to privileged ports.
Backup and Recovery Mistakes
Mistake 15: Not Testing Backup Restoration
Creating backups without verifying you can restore them is like having a parachute you've never tested. I've seen admins discover corrupted backups only during emergencies.
The correct approach:
Schedule regular restoration tests:
# Automate backup verification
#!/bin/bash
BACKUP_FILE="/backups/db-$(date +%Y%m%d).sql.gz"
# Attempt restoration to test database
gunzip -c $BACKUP_FILE | mysql -u root -p test_restore_db
if [ $? -eq 0 ]; then
echo "Backup verification successful"
mysql -u root -p -e "DROP DATABASE test_restore_db;"
else
echo "ALERT: Backup verification failed" | mail -s "Backup Failure" [email protected]
fi
Document restoration procedures and practice them quarterly.
Mistake 16: Storing Backups Only on the Same Server
Keeping backups exclusively on the server they're backing up means ransomware, hardware failure, or compromise destroys both production data and backups simultaneously.
The correct approach:
Implement the 3-2-1 backup rule: three copies, two different media types, one offsite.
# Sync backups to remote storage
rsync -avz --delete /backups/ user@backup-server:/remote-backups/
# Or use cloud storage
aws s3 sync /backups/ s3://mybucket/backups/
Encrypt backups before offsite transfer:
tar czf - /var/www | gpg --encrypt --recipient [email protected] > backup.tar.gz.gpg
Conclusion
Server security failures rarely result from sophisticated zero-day exploits—they come from mundane configuration mistakes: weak authentication, missing updates, overly permissive access, and inadequate monitoring. Every mistake in this guide represents real security incidents I've helped investigate and remediate.
The correct approaches outlined here aren't theoretical best practices—they're practical, proven configurations that significantly reduce your attack surface. Start with authentication hardening and firewall rules, then systematically address monitoring, updates, and backups. Security isn't a one-time checklist but an ongoing practice of verification, testing, and improvement.
Your server security is only as strong as your weakest configuration. Review this checklist, identify the gaps in your current setup, and address them before attackers do.
