Debian, Ubuntu

Linux server security tips

Securing your Linux server is important to protect your data, intellectual property, and time, from the hands of crackers (hackers). The system administrator is responsible for security of the Linux box.

Step 1: Update your package list and upgrade your OS

Software updates and patches are often distributed to fix security vulnerabilities as they are discovered. Running outdated software puts you at risk as soon as the details of the vulnerability are published. For that reason, it is vital to make sure your packages and OS are constantly updated and as secure as they can be.


yum update && yum upgrade

Debian / Ubuntu:

apt-get update && apt-get upgrade

The update part updates your package list, and the upgrade part actually downloads and installs them.

To do this periodically, you can install the yum-cron (CentOS) or unattended-upgrades (Debian / Ubuntu) package.


yum -y install yum-cron

Debian / Ubuntu:

apt-get install unattended-upgrades

In the package’s config file, located at /etc/apt/apt.conf.d/50unattended-upgrades, you can enable or disable automatic updates for certain groups of packages. For example, you can just get security patches automatically by uncommenting “${distro_id}:${distro_codename}-security“; in the config file.

Step 2: Remove unnecessary packages

Packages that you do not need are a useless security liability on your system. They are one extra entry point for attacks that you can do without. To keep your server as lean as possible, you can either manually check through a list of packages and delete anything you do not need, or use a tool that checks for you.

The manual method:

yum autoremove

Debian / Ubuntu:

apt-get autoremove

Step 3: Verify no accounts have empty passwords

Accounts without passwords are vulnerable because the only thing a hacker that slipped through your defenses would need to do is key ‘enter’ along with the username, and they’d be in.

Empty passwords are easily detected; just run

awk -F: '($2 == "") {print}' /etc/shadow

This checks the whether the second element (encrypted password) is blank in your shadow file, and if it is it returns the username so you can go and give them a strong password.

To prevent this from happening again, you should set password rules for users.

Step 4: Set password rules

Type the following command to install libpam_cracklib on an Ubuntu or Debian Linux based system:

apt-get install libpam-cracklib

You need to edit the file /etc/pam.d/common-password, enter:

cp /etc/pam.d/common-password /root/
nano /etc/pam.d/common-password

Now you can force users to have strong passwords that contain complex characters including lowercase, digits, uppercase, spacial characters and punctuation. Locate the line:

password        requisite              retry=3 minlen=8 difok=3

And update it as follows:

password        requisite              retry=3 minlen=16 difok=3 ucredit=-1 lcredit=-2 dcredit=-2 ocredit=-2


  • retry=3: Prompt user at most 3 times before returning with error. The default is 1.
  • minlen=16: The minimum acceptable size for the new password.
  • difok=3: This argument will change the default of 5 for the number of character changes in the new password that differentiate it from the old password.
  • ucredit=-1: The new password must contain at least 1 uppercase characters.
  • lcredit=-2: The new password must contain at least 2 lowercase characters.
  • dcredit=-2: The new password must contain at least 2 digits.
  • ocredit=-2: The new password must contain at least 2 symbols.

Step 5: Set password expiration in login.defs

To ensure your users are regularly setting strong, unique passwords, now make sure to configure how often passwords expire.

You need to edit the file

nano /etc/login.defs

Is where a big chunk of the password configuration rules live. Open it in a text editor, and look for the password aging control line. You will see three parameters:

  • PASS_MAX_DAYS: Maximum number of days a password may be used. If the password is older than this, a password change will be forced.
  • PASS_MIN_DAYS: Minimum number of days allowed between password changes. Any password changes attempted sooner than this will be rejected
  • PASS_WARN_AGE: Number of days warning given before a password expires. A zero means warning is given only upon the day of expiration, a negative value means no warning is given. If not specified, no warning will be provided.
    It is recommended that password changes should be enforced at least every 90 days because if old backups are lost or misfiled you would not want the password file data on them to be still valid and able to be used to get into your server.

Step 6: Check which services are started at boot time

Until you check, you can not be sure that you do not have malicious services starting at boot time and running in the background. This is easy to fix by installing sysv-rc-conf:

apt-get install sysv-rc-conf

Check which services are started at boot time by inputting the following in a terminal:

sysv-rc-conf --list | grep '3:on'

This will highlight the names of every service that starts at boot time.

Now, you can disable a service by typing:

systemctl disable <service>

Step 7: Detect all world-writable files

World-writable files are modifiable by any user on the system. If you have files that only root or an admin should be able to access that are world-writable, you’re at risk.

Display a list of world-writable files with:

find / -xdev -type d \( -perm -0002 -a ! -perm -1000 \) -print

If anything should not be accessible to every single user, make sure to go in and modify the permissions.

Step 8: Configure iptables to block common attacks

Iptables is as powerful as it is complex. It deserves deeper exploration than it can be given in this article, but here are a few example commands you can use to block common attacks:



echo " * flushing old rules"
# flush the old rules
${IPTABLES} -F 				# clear all filter chains
#/sbin/iptables -F -t nat
${IPTABLES} -F -t mangle
#/sbin/iptables -X -t nat
${IPTABLES} -X -t mangle

echo " * setting default policies"
#set default policy
${IPTABLES} -P INPUT DROP			# default action DROP - blocking package
${IPTABLES} -P OUTPUT ACCEPT			# allow all outgoing packets

#create new chain (BAD_PACKETS)

echo " * BLACKLIST"
${IPTABLES} -N BANNED			#Blocked users


#jump to BAD_PACKETS

echo " * allowing loopback devices"
#allow the loopback

# to all the packets that belong to an established connection, the terminal applies the action of ACCEPT - skip

${IPTABLES} -A INPUT -m state --state NEW -p tcp --dport 139 -j ACCEPT
${IPTABLES} -A INPUT -m state --state NEW -p tcp --dport 445 -j ACCEPT

# you can allow the ports to which such a check is not necessary.
echo " * allowing ssh on port 22"
${IPTABLES} -A INPUT -m state --state NEW -p tcp --dport 22 -j ACCEPT

echo " * allowing http on port 80"
${IPTABLES} -A INPUT -m state --state NEW -p tcp --dport 80 -j ACCEPT

echo " * allowing https on port 443"
${IPTABLES} -A INPUT -m state --state NEW -p tcp --dport 443 -j ACCEPT

#/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport ftp -j ACCEPT
#/sbin/iptables -A INPUT -m state --state NEW -p tcp --sport ftp-data -j ACCEPT

#/sbin/iptables -A INPUT -m state --state NEW -p tcp --sport 1500:1505 -j ACCEPT

# mail server
/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 25 -j ACCEPT
#/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 110 -j ACCEPT
#/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 143 -j ACCEPT
#/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 993 -j ACCEPT
#/sbin/iptables -A INPUT -m state --state NEW -p tcp --dport 995 -j ACCEPT

# allow DHCP
echo " * allowing DHCP on port 67"
/sbin/iptables -A INPUT -p UDP --dport 68 --sport 67 -j ACCEPT

#allow ICMP replies from specified hosts (ping)
echo " * allow ICMP replies from specified hosts"
/sbin/iptables -A INPUT -p ICMP --icmp-type 8 -j ACCEPT

#allow DNS
# hex string
# MX - 00 0F (5 queries / 60 seconds)
# AAAA - 00 1C (2 queries / 10 seconds)
# A - 00 01 (100 queries / 10 seconds)
# PTR - 00 0C (50 queries / 10 seconds)
# CNAME - 00 05 (100 queries / 10 seconds)
# NS - 00 02 (10 queries / 60 seconds)
# SOA - 00 06 (5 queries / 60 seconds)
# SRV - 00 21 (5 quries / 60 seconds)

/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 0F 00 01|" -m recent --set --name MXFLOOD --rsource
/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 0F 00 01|" -m recent --update --seconds 60 --hitcount 5 --rttl --name MXFLOOD -j DROP
/sbin/iptables -A INPUT -p udp --dport 53 -m string --algo kmp --hex-string "|00 0F 00 01|" -j ACCEPT

/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 1C 00 01|" -m recent --set --name AAAAFLOOD --rsource
/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 1C 00 01|" -m recent --update --seconds 10 --hitcount 2 --rttl --name AAAAFLOOD -j DROP
/sbin/iptables -A INPUT -p udp --dport 53 -m string --algo kmp --hex-string "|00 1C 00 01|" -j ACCEPT

/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 01 00 01|" -m recent --set --name AFLOOD --rsource
/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 01 00 01|" -m recent --update --seconds 10 --hitcount 20 --rttl --name AFLOOD -j DROP
/sbin/iptables -A INPUT -p udp --dport 53 -m string --algo kmp --hex-string "|00 01 00 01|" -j ACCEPT

/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 05 00 01|" -m recent --set --name CNAMEFLOOD --rsource
/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 05 00 01|" -m recent --update --seconds 10 --hitcount 20 --rttl --name CNAMEFLOOD -j DROP
/sbin/iptables -A INPUT -p udp --dport 53 -m string --algo kmp --hex-string "|00 05 00 01|" -j ACCEPT

/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW -m string --algo kmp --hex-string "|00 06 00 01|" -m recent --set --name SOAFLOOD --rsource
/sbin/iptables -A INPUT -p udp --dport 53 -m state --state NEW  -m string --algo kmp --hex-string "|00 06 00 01|" -m recent --update --seconds 60 --hitcount 5 --rttl --name SOAFLOOD -j DROP
/sbin/iptables -A INPUT -p udp --dport 53 -m string --algo kmp --hex-string "|00 06 00 01|" -j ACCEPT

# log
#/sbin/iptables -A INPUT -j LOG --log-prefix "INPUT DROP: "

/sbin/iptables -A BAD_PACKETS -p TCP ! --syn -m state --state NEW -j DROP
/sbin/iptables -A BAD_PACKETS -p TCP --tcp-flags ALL ALL -j DROP
/sbin/iptables -A BAD_PACKETS -p TCP --tcp-flags ALL NONE -j DROP
/sbin/iptables -A BAD_PACKETS -p TCP --tcp-flags ALL SYN \-m state --state ESTABLISHED -j DROP
/sbin/iptables -A BAD_PACKETS -p ICMP --fragment -j DROP
/sbin/iptables -A BAD_PACKETS -m state --state INVALID -j DROP
/sbin/iptables -A BAD_PACKETS -d -j DROP
/sbin/iptables -A BAD_PACKETS -j RETURN

#drop BANNED
#/sbin/iptables -A BANNED -s -j DROP

echo "Rules written."

Make sure to read the iptables manual (man iptables) for more information on blocking connections from particular IP addresses or to certain hosts.

Step 9: Secure any Apache servers

Apache servers make for a wonderfully predictable entry point for attackers — you only have to check the number of Apache vulnerabilities that have surfaced over the years to be wary about the security of yours.

Open your Apache config file — located at

nano /etc/apache2/apache2/conf

and modify the defaults to reflect the following lines:

ServerTokens Prod
ServerSignature Off
Header always unset X-Powered-By

These three lines above prevent Apache from broadcasting its version number and other identifiable details, which makes it harder for attackers to exploit known vulnerabilities.

TraceEnable Off

Turning off TRACE protects you against known cross-site tracking vulnerabilities.

Step 10: Configure SSH securely

Even though SSH may have been compromised by the U.S. government in some roundabout way, it is still the standard protocol for controlling a server remotely. One of the biggest dangers with SSH — especially for large organizations with thousands of servers — is managing SSH keys. If an attacker gains access to your servers through a misplaced SSH key, you want to be sure that you minimize the damage they can do, or lock them out entirely.

To achieve this, add the restrictions below to your SSH config file, located at /etc/ssh/ssh_config:

  • PermitRootLogin no # disallows root access via SSH
  • AllowUsers [username] # limits SSH access to the stated users
  • IgnoreRhosts yes # disallows SSH from trusting a host based only on its IP
  • HostbasedAuthentication no # as above
  • PermitEmptyPasswords no # prevents users from logging into SSH with an empty password, if set as such
  • X11Forwarding no # stops the possiblity of the server sending commands back to the client
  • MaxAuthTries 5 # drops the SSH connection after 5 failed authorization attempts
  • Ciphers aes128-ctr,aes192-ctr,aes256-ctr # disable weak ciphers
  • UsePAM yes # disables password authentication and defers authorization to the key-based PAM
  • ClientAliveInterval 900 # logs out idle users after 15 minutes
  • ClientAliveCountMax 0 # how many times the server checks whether the session is active before dropping

Step 11: Disable telnet

apt-get remove telnet

Step 12: Configure sysctl securely

Your default sysctl settings may leave you open to syn flood attacks and IP spoofing, or may not log suspicious packages.

To harden your sysctl settings, open

nano /etc/sysctl.conf

in a text editor, and do the following:

Disable IP Forwarding by setting the net.ipv4.ip_forward parameter to 0
Disable the Send Packet Redirects by setting the net.ipv4.conf.all.send_redirects and net.ipv4.conf.default.send_redirects parameters to 0
Disable ICMP Redirect Acceptance by setting the net.ipv4.conf.all.accept_redirects and net.ipv4.conf.default.accept_redirects parameters to 0
Enable Bad Error Message Protection by setting the net.ipv4.icmp_ignore_bogus_error_responses parameter to 1

Step 13: Lock user accounts after failed attempts with Fail2Ban

Fail2Ban helps you bypass the PAM configuration file and easily modify global rules for user lockouts. Using Fail2Ban is a simple matter of installing it, enabling it, and then editing the configuration file to specific how many attempts a user can make before being locked out, and how long they should be locked out for. This helps prevent brute-force password cracking attempts.

Install Fail2Ban:

apt-get install fail2ban

Enable it:

systemctl start fail2ban && systemctl enable fail2ban

Edit the config file:

nano /etc/fail2ban/jail.local

Below is an excerpt of the jail.local config file where you can set the maximum number of login attempts before lockout:

# "bantime" is the number of seconds that a host is banned.
bantime  = 600

# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime = 600
maxretry = 3

Step 14: Check for hidden open ports with netstat

Open ports can reveal information about system or network architecture, and increase your attack surface. If you don’t need a port, close it. If you see an open port you don’t recognize, use netstat to investigate.


netstat -antp

to check for hidden open ports.

Step 15: Scan for rootkits

A rootkit lurking on your server could be intercepting login details, concealing malware, providing a backdoor for an attacker, or even remotely controlling your server as part of a botnet. Rootkits are designed to be difficult to find, which is why you need a specialized tool for the job.

To do this, first install chkrootkit:

apt-get install chkrootkit

Next, simply type in chkrootkit as a root user. The tool will scan every part of your server and return details of anything suspicious.

Step 16: Install linux malware detect

Linux Malware Detect (LMD) is a malware scanner for Linux released under the GNU GPLv2 license, that is designed around the threats faced in shared hosted environments. It uses threat data from network edge intrusion detection systems to extract malware that is actively being used in attacks and generates signatures for detection. In addition, threat data is also derived from user submissions with the LMD checkout feature and from malware community resources. The signatures that LMD uses are MD5 file hashes and HEX pattern matches, they are also easily exported to any number of detection tools such as ClamAV.

cd /usr/local/src
tar xzfv maldetect-current.tar.gz