Securing your Linux server involves skillfully managing users.

It's crucial to ensure that users can only access their own data, know how to protect login credentials, and use secure methods for server access.

Additionally, understanding the risks associated with using the root user and learning how to use sudo to grant either full root privileges or specific privileges is essential.

Familiarity with working with the sudoers file is also important.

This guide will cover these aspects in detail, along with other key aspects related to securely managing users on Linux servers.

Preparation

To make the most of this guide, ensure you have a properly set up Ubuntu server.

👉
Check out my guide on preparing Ubuntu servers to ensure your server is properly set up.

If you don’t have one, consider getting a free VPS server to follow along.

Potential Risks

When accessing your server for the first time, you may use the root user, but this is not recommended due to the significant risks involved.

The root user possesses unrestricted control over the entire server, allowing you to execute any command, even those that could potentially break the system.

Firstly, root is the default all-powerful user on Unix and Linux systems, making it a prime target for hackers attempting to brute force your server. Not disabling root access leaves your server vulnerable to attacks.

Additionally, there's a risk of unintentionally damaging the server.

To enhance security, it's essential to create a new user with root privileges, while completely disabling access for the root user.

This non-root user should prepend each command with the sudo prefix and enter their password, providing a safer approach.

Furthermore, using an unfamiliar username for this user helps mitigate the risk of brute force attacks.

For now, I'm only discussing the scenario where you are the only administrator.

Consider the scenario of multiple admins in a company having access to the server with the root password. This is not ideal as it poses security risks.

If an admin stores the root password in an insecure place or leaves the company, changing the password becomes necessary, requiring distribution of the new password to all admins.

Moreover, in a corporate environment, it's often preferable to assign specific commands to specific users rather than granting full access.

Understanding the proper way to manage users on your Linux server is crucial, especially in scenarios involving multiple administrators.

In this guide, I will teach you the best practices to ensure the secure management of users on your Linux server.

Adding Users

The first step is understanding how to add users on a Linux server, a task that is quite straightforward.

Two commands are commonly used for adding new users.

The adduser Command

The first command is adduser, typically found on Debian-based distributions such as Ubuntu.

It is a user-friendly command that interactively prompts for user information, streamlining the user creation process.

To add a new user, execute the following command:

adduser username
💡
Try to use a randomly generated string for the username instead of well-known usernames like admin, administrator, etc.

Apart from creating a new user, the adduser command performs the following tasks:

  • Creates a new group for the user.
  • Creates the home directory for the new user under the /home directory.
  • Asks for a password.
  • Prompts for additional information such as Full Name, Room number, Work phone, Home phone, and other details. Simply press ENTER to skip through these optional details.

The useradd Command

The second command is useradd, commonly used on Red Hat-based distributions like CentOS but also available on Ubuntu.

On Ubuntu, it requires specifying user details and options directly in the command line, offering a more manual approach to user creation.

While it creates a new user like the adduser command, it has a few differences:

  • It does not create a home directory for the new user by default.
  • It does not create a password for the user.
  • It does not ask for any additional information.

Something important to know is that when using the adduser command, the default shell for the user is the Bash shell. In contrast, when using the useradd command without any options, the default shell is the classic SH shell.

If you want the useradd command to perform the same tasks as adduser, execute the following command:

useradd -m -d /home/<username> -c "Full Name" -s /bin/bash <username>

The -m option is used to create a home directory for the user. The -d /home/username option allows you to specify the path of the home directory.

Using the -c "Full Name" option allows you to set the full name associated with the user account.

Lastly, the -s /bin/bash option specifies the default shell for the new user as Bash.

Combining these options, the command creates a new user with a home directory, assigns a full name, and sets the default shell to Bash.

After creating the user, remember to use the passwd command to set a password for the new user:

passwd username

Enter and confirm the password.

Which One To Use

Now, which command to use depends on your needs and use-case, but in most cases, the adduser command is preferable as it is easy to use and allows you to add users quickly.

However, this doesn't mean the useradd command is useless. If you temporarily want to create a user without providing much detail, you can use it. Generally, it's used in bash scripts for quick user creation without interactive prompts.

Locking Down Home Directories

Each Linux distribution comes with default security settings for users' home directories.

If you're working in a mixed environment with different distros, it's important to be aware of these variations.

Let's add a user named John using the useradd command on an Ubuntu 20.04 server:

useradd -m -d /home/john -s /bin/bash john

Next, let's list the contents of the /home directory:

ls -l /home

Output:

total 4
drwxr-xr-x 2 john john 4096 Jan 19 08:50 john

Notice that John's home directory has open permissions, allowing everyone to execute and read. This is not ideal, as it exposes John's home directory to all users on the server.

Although users can manually adjust their home directory permissions, it's preferable to set the correct permissions by default when adding any new user.

To achieve this, modify the default permissions for home directories by opening the /etc/login.defs file.

Look for the line starting with UMASK and change the value to 077 like this:

UMASK 077

Now, new users' home directories will have secure default permissions without requiring manual adjustments.

I will add a user named Ivan now for verification:

useradd -m -d /home/ivan -s /bin/bash ivan

Next, let's once again list the contents of the /home directory:

ls -l /home

Output:

total 4
drwx------ 2 ivan ivan 4096 Jan 19 09:02 ivan
drwxr-xr-x 2 john john 4096 Jan 19 08:50 john

Do you notice the change? Access to Ivan's home directory and the viewing of their files are now restricted.

But, this change won't apply to the adduser command, and using it will still utilize the old permission setting.

That's why we need to open the /etc/adduser.conf file and change the value of DIR_MODE to 0700 like this:

DIR_MODE=0700

With this, access to a new user's home directory and the viewing of their files are now restricted.

💡
Red Hat Enterprise Linux and all distributions based on it, such as CentOS, come with secure default permissions for home directories out-of-the-box.

Now, let's talk about Ubuntu 22.04 and 24.04.

The default permissions for both the useradd and adduser commands have been changed to a more secure setting, which is excellent.

If you open the /etc/login.defs file, you will notice a new line under UMASK called HOME_MODE with a value of 0750 by default.

If you open the /etc/adduser.conf file, you will notice that the value of DIR_MODE has been changed to 0750.

These changes mean access permissions for a user's own personal group, which is acceptable.

This effectively ensures that only the respective owners of the various home directories can access them.

Password Management

Now that you've learned how to add users, let's talk about managing their passwords.

It's crucial to keep passwords secure in one central place, rather than each user storing them individually. This is where a password manager comes in.

Nowadays, using a secure password manager is essential for storing and generating strong passwords.

With a tool like Bitwarden (which I personally recommend and use), you can store all user passwords in one accessible place.

Bitwarden offers a feature called Bitwarden Organizations which adds a layer of collaboration, allowing secure sharing of information among users, such as passwords.

Now that you know how to manage passwords, let's delve into enforcing the use of strong, lengthy passwords and setting up periodic password changes.

Strong Password Criteria

We want to enforce users to use strong passwords that meet the criteria we specify.

For that, we will use the pwquality module for the Pluggable Authentication Module (PAM).

Let's begin by installing the module:

apt install libpam-pwquality

Now, to configure the module, there is only one file to edit, which is the /etc/security/pwquality.conf file.

Open the file in your preferred editor and take a look. It is well-documented.

The two important variables to adjust are minlen and minclass.

The minlen variable determines the minimum length of the password, while the minclass variable specifies the minimum number of character classes that the user should use when creating the password.

I always set the minlen variable to 32, and the minclass variable to 4, ensuring users include symbols in their passwords.

These values pose no problem since users will use the password manager both for creating complex passwords and securely storing them.

I'll leave the rest up to you to meet your own criteria, as mentioned before, the file is well-documented. Don't forget to uncomment the variables for them to take effect.

Once you're finished, save the file, and that's it.

To test the new criteria, add a new user and attempt to change their password.

💡
The root user and any user with root privileges can change passwords, even if they don't meet the specified criteria.

Password Expiration

The first time you add a user, you assign their password and store it in the password manager.

Now, you want to enforce a policy where users must change their passwords every month, requiring a change every 30 days from the last password update.

To do this, we change the value of the PASS_MAX_DAYS variable inside the /etc/login.defs file to 30 like this:

PASS_MAX_DAYS 30

Add a new user for verification and use the chage command to check the expiration data of that user:

chage -l <username>

No one needs root privileges to see their own expiration data, but they still need to specify their username. They cannot view any expiration data of other users or make changes to them.

Let me add a user named Elie and check their expiration data after the change:

Last password change			    : Jan 25, 2024
Password expires					: Feb 24, 2024
Password inactive					: never
Account expires						: never
Minimum number of days between password change		: 0
Maximum number of days between password change		: 30
Number of days of warning before password expires	: 7

As you can see, the password of the user Elie will expire on Feb 24, 2024, which is 30 days after its creation on Jan 25, 2024. This implies that Elie needs to change their password every 30 days.

They will receive a warning 7 days before the password expires, indicating that they should change their password.

If we leave the expiration settings as they are, any user could change their password at any time before password expiration, allowing them to do so every day. While changing it every day may be overly secure, it's a practice I don't prefer.

In our scenario, passwords expire every 30 days, and users receive a warning 7 days before expiration, effectively starting on the 23rd day after the password change.

I prefer users to have the ability to change their password only during this 7-day window, spanning from the time they receive the warning until the password reaches its expiration date.

To achieve this, we need to adjust the value of the PASS_MIN_DAYS variable and set it to 23 like this:

PASS_MIN_DAYS 23

Let me add a user named Issa and check their expiration data after the change:

Last password change			    : Jan 25, 2024
Password expires					: Feb 24, 2024
Password inactive					: never
Account expires						: never
Minimum number of days between password change		: 23
Maximum number of days between password change		: 30
Number of days of warning before password expires	: 7

The password for the user Issa now expires after 30 days, and the user can only change their password within the 7 days leading up to password expiration.

With this, we have learned how to specify specific criteria for creating passwords and how to enforce users to change their passwords periodically.

Implementing Sudo

When sudo is set up correctly, it significantly boosts the security of our server.

Here are its key advantages:

  • Assign full root privileges to specific users, while granting others access to execute only designated commands.
  • Users can perform administrative tasks without needing the root password. They can use their own password instead. This avoids the necessity of sharing the root password with everyone.
  • Implementing sudo allows us to disable the root user, preventing brute force attacks targeting the root user. Additionally, attackers remain unaware of usernames with root privileges, enhancing overall server security.
  • It enables us to monitor user activities closely. We can track and review actions performed with sudo capabilities.

These are the points I love about sudo, but it has more features to offer.

In the following, I'm going to teach you the important things about implementing sudo and how you can do it the right way.

Full Root Privileges for Users

When exploring Linux server security guidelines, it's common advice to create a non-root user, add them to the sudo group for full root privileges, and disable root access entirely.

But have you wondered why the sudo group and where its powers come from? This is precisely what we are about to delve into.

The key to these privileges is the /etc/sudoers file.

It is a configuration file that specifies which users or groups are granted permission to run specific commands with elevated privileges using the sudo prefix.

When you attempt to run a command that requires root privileges using the sudo prefix, the server checks your username against the sudoers file.

If you have the privilege to run the command, you are good to go, otherwise, you will receive an error.

To access the sudoers file, we use the visudo command. It's important not to open the file with a regular text editor. This command checks for errors and warns before saving changes.

The command will open the file with the default editor used by the server, which is often nano. If you want to use another editor, you can change the default editor by using the following command:

update-alternatives --config editor

The command will prompt you to choose between editors. Select your preferred one and press the ENTER key.

Now, run the visudo command and take a look at the sudoers file:

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

The percent sign indicates that we’re working with a group.

Look, the sudo group has the same powerful privileges as the root user.

So, when you add a user to this group, they can run any command needing root privileges.

Let's break down those ALLs:

  • The first one means the rule applies to all hosts.
  • The second one says the user can run commands as any user.
  • The third means commands can be run as any group.
  • Lastly, the last ALL means these privileges apply to any command.

Let me add the user Ivan to the sudo group using the following command:

usermod -aG sudo ivan

Now, if I run the apt update command without the sudo prefix, it won't work directly. I need to run the command like this: sudo apt update and then enter my password.

💡
Red Hat Enterprise Linux and all distributions based on it, such as CentOS, use a different group called wheel for full root privileges.

We can also manually add any user to this file with a specific rule and set of privileges.

I will add a rule for the user Ivan under the one for the root user like this:

ivan    ALL=(ALL:ALL) ALL

In simpler terms, the new line now allows the user Ivan to execute any command as any user and any group on any host, just like the root user.

Save the file, and if you don't see any warnings, you're good to go.

If you wish to add a user to the sudo group while creating their user account, use the following command:

useradd -G sudo -m -d /home/john -s /bin/bash ivan

The -G option directly adds the user Ivan to the sudo group.

Customized User Privileges

Now that you've learned how to grant users full root privileges, let's move on to understanding how to assign users specific, limited privileges.

The idea is to minimize the number of people with full root privileges, ensuring that the rest have only the necessary privileges for their job.

Imagine this situation: two folks are on the Software team. We just want to let them run commands related to software. We can either make two separate entries in the sudoers file for each user, or we can make things simpler using aliases.

Now, in the sudoers file, let's make a new user alias called SOFTWAREADMINS that includes the two users we want to let run software-related commands:

# User alias specification
User_Alias SOFTWAREADMINS = ivan, john

Next, create a new command alias called SOFTWARECOMMANDS that includes commands related to software:

# Cmnd alias specification
Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt, /usr/bin/dpkg

Finally, link the SOFTWARECOMMANDS alias to the SOFTWAREADMINS alias like this:

SOFTWAREADMINS ALL=(ALL:ALL) SOFTWARECOMMANDS

Ivan and John, who are part of the SOFTWAREADMINS alias, can now run software-related commands listed in the SOFTWARECOMMANDS alias.

If a new person joins the team, just add them to the SOFTWAREADMINS alias.

Similarly, if you want to let them run a new command, simply add it to the SOFTWARECOMMANDS alias. Easy!

Caution: Command Specification

It's crucial to understand that when we define a command alone, as we did in our SOFTWARECOMMANDS alias, users can use it with any subcommands, options, or arguments.

This includes the ability to delete software, which might not be our intention.

But when a command is listed in the command alias with a subcommand, option, or argument, that’s all a user who's assigned to the command alias can run.

If we want users only to install new software and update existing ones, we need to modify how we specify the command like this:

# Cmnd alias specification
Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install

But will this work when installing new software? No.

If you try running sudo apt install postfix to install Postfix, it won't work.

Users could run sudo apt install without specifying what to install, which is pointless.

That's why we need to adjust our alias once again, like this:

# Cmnd alias specification
Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install *

Using the asterisk (wildcard) means users can specify anything after the command, allowing them to install any software.

Now, users in the SOFTWAREADMINS alias can update existing packages and install new software.

In another scenario, you might encounter a similar issue to what we faced. This situation arises when managing a team focused solely on services, and you need to provide them with the privilege to execute the systemctl or service commands.

Consider this command alias:

Cmnd_Alias SERVICECOMMANDS = /usr/sbin/service, /usr/bin/systemctl

What do you think would happen if we assign users to this command alias? What would they be able to run?

If left as is, users assigned to this command alias would have the capability to shut down or reboot the server, edit services files, or change system targets. This is not the desired outcome.

I want them to only check the status of services, for example, I could modify the command alias to something like this:

Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl status

But will this work when checking the status of the ssh service? No.

Now we encounter the same issue we faced when installing new software. We should modify our command alias to include the asterisk:

Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl status *

Now, users assigned to the SERVICECOMMANDS alias can check the status of any service on the server.

We can limit users' actions in another way.

For example, if I want to grant a user the privileges to run the systemctl command with all its subcommands, options, or arguments but only against the ssh service, I could do it like this:

Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl * ssh

Users can now perform all necessary tasks with the Secure Shell service, but they are restricted from shutting down or rebooting the server, editing services files, or changing system targets.

Be careful when specifying commands. I never recommend specifying a command by itself. Always specify what comes after the command and be specific about what these users could do.

Mitigating Shell Escapes for Users

Some programs, like text editors and pagers, have a feature called Shell Escape that allows users to run commands without exiting the program first.

For example, from the command mode of the vim editor, I can run the :!ls command like this:

#!/bin/bash

echo "This is a bash script"
~
~
:!ls

Output:

bib.csv		lilly.sh	script.sh
Press ENTER or type command to continue

As you can see, the ls command listed all files in my current working directory.

This may not look harmless at first, but what would happen if the user runs a command that requires root privileges?

Consider this entry in the sudoers file:

ivan ALL=(ALL:ALL) /usr/bin/vim /etc/ssh/sshd_config

The user Ivan should only be able to edit the sshd_config file.

Let me open the file and run the :!apt update command like this:

:!apt update

Oops, I'm able to run this command as the root user, even though I don't have the privilege to run it.

Let me try running the :shell command and see what happens:

:!shell

Output:

root@testing:/home/ivan#

Look what happened! Now I've switched to the root user and can do anything I want.

So, by only granting Ivan the privilege to edit the sshd_config file using vim, I inadvertently gave them full root privileges. How can this be resolved?

We can fix this by allowing Ivan to use the sudoedit command instead of the vim editor, like this:

ivan ALL=(ALL:ALL) sudoedit /etc/ssh/sshd_config

The sudoedit command doesn't allow any user to escape to a shell with root privileges. If you still try to use the :shell command, you will escape to your own non-privileged shell.

Some programs also have a shell escape feature, such as emacs, less, and more. To address this issue, you can use the NOEXEC option inside the sudoers file like this:

ivan ALL=(ALL:ALL) NOEXEC: /usr/bin/less /etc/ssh/sshd_config

Now, the user Ivan can use the less command to view the content of the sshd_config file, but they can't use the shell escape feature.

We could have used the NOEXEC option with the vim editor from the beginning, but I wanted to demonstrate two different methods.

Preventing Abuse of Shell Scripts

Let's assume that the user Ivan created a shell script that requires root privileges to run and asks for the privilege to run it.

Consider this entry in the sudoers file:

ivan ALL=(ALL:ALL) /home/ivan/script.sh

Now, the user Ivan is able to run the script as the root user.

Since Ivan is the owner of the script, he can edit it the way he wants.

Therefore, he can essentially add the sudo -i command to the script, which will open a new shell with the root user, granting Ivan full root privileges.

And this is a problem.

That's why I never recommend giving any user the privilege to run any script owned by them as the root user.

To solve this problem, we move their script to the /usr/local/sbin/ directory after they finish editing it and after we finish reviewing it.

Then, we change its ownership to the root user and give them the privilege to run it.

This way, Ivan can run the script, but they can't edit it.

Viewing sudo Privileges

If you are unsure of your privileges, using the sudo -l command will provide you with an overview of the privileges you possess.

When running the command, first, you will see some of the environmental variables for your user account and then your privileges.

Let me run this command as the user Ivan and see the results:

sudo -l

Output:

Matching Defaults entries for ivan on testing:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User ivan may run the following commands on testing:
    (ALL : ALL) /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install *
    (ALL) /usr/bin/systemctl status *

On this testing server, I, the user Ivan, can update existing packages, install software, and check the status of any service.

Great, now I know what I'm capable of!

The sudo Timer

Have you ever wondered why, after running any command using sudo and entering your password, running another command with sudo won't ask you for your password?

This is due to the sudo timer. By default, it is set to five minutes.

This means that once you run a command with sudo and enter your password, your password will be cached for five minutes, allowing you to run subsequent commands without entering your password during that time frame.

However, this could pose a risk when users leave their desks and leave the terminal open. That's why you may want to consider disabling the sudo timer.

To do this, add the following line to the Defaults section of the sudoers file:

Defaults timestamp_timeout = 0

Now, try running two commands in succession and observe that you need to enter your password each time.

If you are on a server where the sudo timer is enabled and you want to leave your desk without closing the terminal, consider resetting the timer using this command:

sudo -k

This will reset the timer, and now if someone tries to run a command using sudo, they will need your password.

Monitoring User Activities

One of the great features of sudo is its ability to monitor how users utilize sudo and track their actions.

By default, sudo logs its activities in the auth.log file. However, I prefer to separate log files for easier investigations.

For this, we need to add one thing to the Defaults section in the sudoers file:

Defaults logfile=/var/log/sudo.log

Now, sudo will log its activities in the sudo.log file, capturing both successful and failed attempts.

Conclusion and Final Thoughts

Awesome job reaching the end!

I hope this guide has been helpful in giving you a solid understanding of how to securely manage users on a Linux server.

By implementing these practices, you’re taking important steps to protect your server and its data.

👉
You can find the full collection of detailed Linux server security guides here.

If you found value in this guide or have any questions or feedback, please don't hesitate to share your thoughts in the discussion section.

Your input is greatly appreciated, and you can also contact me directly if you prefer.