Managing users properly is one of the most important things you can do to keep your Linux server secure.

After years of running my own servers – sometimes learning the hard way – I’ve come to appreciate how critical good user management really is. It's not just about keeping things organized – it's about reducing risk and locking down potential entry points.

🔒
Think of it like this: every user account is a potential door into your server. Your job is to make sure each door is locked unless absolutely necessary – and only the right people have the keys.

One of the biggest mistakes I made early on (and I've seen others make too) was over-relying on the root account. It’s tempting because it’s convenient, but it opens the door to serious security risks. Learning how to properly use sudo was a game-changer for me – it lets you give just the right amount of access, no more, no less.

I’ll also show you how to safely edit the sudoers file – a crucial but often intimidating step for many.

In this guide, I’ll walk you through everything I’ve learned about managing users securely on a Linux server. These are tips and techniques I now use by default, and I hope they’ll save you from some of the headaches I ran into along the way.

📬 NEWSLETTER

Want more security tips and server hardening guides? Subscribe to my newsletter for practical advice delivered directly to your inbox!

I'm In!

Potential Risks of Using Root

When I first started managing my own servers, I instinctively logged in as the root user. After all, it felt powerful – it had full control over everything. But I quickly learned how dangerous that could be.

👉
Here’s the deal: The root user has unlimited power. It can execute any command – including those capable of breaking your server if you make even a tiny mistake.

But there's an even bigger issue: hackers know about this power too.

The root user is a prime target for brute-force attacks. Leaving root enabled is like leaving your front door unlocked.

What you should do instead:

  • Create a regular user with sudo privileges: This lets you run admin tasks safely, and you’ll be prompted for your password each time – a small but effective security layer.
  • Disable direct root login altogether: If root can’t log in, hackers can’t brute-force it.
  • Use a unique admin username: Something that’s not obvious like "admin" or your name. It’s a small trick that adds another layer of protection.

What If You Have Multiple Admins?

This is where things can really go sideways if you're not careful.

Let’s say a few team members all share the root password. What happens if one person leaves the company? Or stores the password in plain text somewhere insecure? Now you're stuck resetting the root password and sending it around again – not ideal.

Instead, here's what I've learned works best:

  • Give each admin their own user account.
  • Use sudo to assign only the privileges they actually need.
  • Limit access intentionally – don’t hand out root-level permissions unless absolutely necessary.

Over the years, these practices have saved me from some painful mistakes and security scares. In the rest of this guide, I’ll show you how to set all of this up, step by step – so you can manage your users confidently and securely.

Adding Users on Linux (Clearly Explained!)

Before you can manage users, you need to know how to add them – and luckily, Linux makes this pretty straightforward.

Over the years, I’ve worked with both Debian-based and Red Hat-based servers, and while they use slightly different tools, the core idea is the same.

Let's break it down.

Using adduser – the Friendly Way (Ubuntu/Debian)

If you're on Ubuntu or any Debian-based server, you'll probably want to use adduser. It’s a more user-friendly wrapper around the lower-level useradd command and handles a lot of things for you.

To add a new user:

adduser username

This command will:

  • Create a group with the same name as the user
  • Set up a home directory under /home/username
  • Ask for a password
  • Prompt for optional info like full name and phone number (you can just press ENTER to skip those)
💡
Tip: Avoid common usernames like admin or user. I recommend using something unique or even a randomly generated string – it makes brute-force attacks a lot harder.

Using useradd – the Manual Way (CentOS/Red Hat)

The useradd command is more common on Red Hat-based systems like CentOS, but it's also available on Ubuntu. It's more barebones and gives you more control, which makes it great for scripting — but also easier to mess up if you're not careful.

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

  • It does not create a home directory.
  • It does not set a password.
  • It does not ask for any user details.

To make it behave more like adduser, you'll need to provide a few flags:

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

Here’s what each flag does:

  • -m: Creates a home directory
  • -d /home/username: Sets the path of the home directory
  • -c "Full Name": Adds a comment (often used for the full name)
  • -s /bin/bash: Sets the default shell to Bash

Then don’t forget to set the user’s password:

passwd username

You’ll be prompted to enter and confirm the new password.

💡
Shell matters: When you use adduser, the default shell is /bin/bash. But useradd (unless you specify otherwise) sets the shell to /bin/sh, which is more limited. For most users, especially new ones, Bash is the better choice.

So, Which One Should You Use?

If you're doing things manually or managing a small number of users, adduser is almost always the better choice. It’s simple, clean, and gets everything set up with minimal effort.

That said, useradd shines when you're writing scripts or doing automated setups where you don’t want interactive prompts.

Personally, I use adduser for one-off tasks and useradd in automation. Knowing both gives you flexibility depending on the situation.

Locking Down Home Directories

One detail that often slips under the radar – especially when working with different Linux distributions – is how home directories are secured by default.

Each distro handles this a little differently, and if you're not paying attention, you could end up with user directories that are readable by everyone on the server. Not ideal.

Let’s see what happens on Ubuntu 20.04

First, let’s add a user named john:

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

Then, check the permissions on the /home directory:

ls -l /home

Output:

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

See that? John's home directory has r-x (read and execute) permissions for everyone. That means other users on the server can view his files or even browse the folder. Definitely not what you want for sensitive user environments.

Setting Secure Defaults with useradd

Sure, users can change directory permissions manually using chmod, but there’s a better way: set secure defaults.

Open /etc/login.defs, find the UMASK line and change its value to 077 like this:

UMASK 077

This setting ensures new users will have locked-down home directories by default.

Let’s try it again. This time, I’ll create a user named ivan:

useradd -m -d /home/ivan -s /bin/bash ivan
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

Success! Ivan’s directory is now fully private (drwx------), while John's is still open.

But Wait – What About adduser?

If you use adduser instead, it won’t follow the UMASK setting. Instead, it uses a config file of its own: /etc/adduser.conf.

Open that file and change the DIR_MODE value:

DIR_MODE=0700

Now adduser will also create private home directories moving forward.

💡
Heads-up for Red Hat users: Red Hat and its derivatives (like CentOS) already use secure home directory defaults out of the box – so no changes are needed there.

Ubuntu 22.04+ – Good News!

Starting with Ubuntu 22.04 and 24.04, things have improved. Both useradd and adduser now use more secure default settings.

In /etc/login.defs you'll find:

HOME_MODE 0750

In /etc/adduser.conf:

DIR_MODE=0750

This means home directories are still private to the user and their group – a reasonable default for most setups.

Password Management

Now that you've seen how to add users, it’s time to talk about managing their passwords – securely and efficiently.

It’s not enough for each user to just “remember” their password. In any serious setup, you need a centralized, secure way to store and share credentials – and that’s where password managers come in.

These days, using a password manager isn’t optional – it’s essential.

Personally, I use and recommend Proton Pass. It’s fast, secure, and works seamlessly across devices. Their Business plan supports team use, making it easy to securely share passwords and sensitive credentials within your organization – without sacrificing performance or privacy.

👉
Bonus: If you sign up using my link, you’ll get 50% off your first year.

Having one secure vault for storing and generating strong passwords saves you from reusing weak ones – and makes enforcing strict password policies a lot more practical.

Enforcing Strong Passwords

Once you’ve got a manager in place, the next step is making sure users create strong passwords in the first place.

To do this, we’ll use the pwquality module, part of PAM (Pluggable Authentication Modules).

Let's begin by installing the module:

apt install libpam-pwquality

Next, open the config file:

vim /etc/security/pwquality.conf

This file is well-documented, so feel free to explore. But to get started, focus on these two important settings:

minlen = 32
minclass = 4

Here’s what they mean:

  • minlen: sets the minimum password length
  • minclass: sets the minimum number of character types (uppercase, lowercase, digits, symbols)

Why so strict? Because users won’t have to memorize these – they’ll use the password manager to generate and store them securely. So go big on security here.

⚠️
Note: Users with root privileges can bypass these rules when setting passwords.

I’ll leave the rest up to you to meet your own criteria – as mentioned earlier, the file is well-documented. Just make sure to uncomment the variables for your changes to take effect.

After saving the file, test it by creating a new user and trying to set a weak password. You’ll see the policy in action.

Password Expiration Policy

When you add a new user, you typically assign them a password and store it securely in your password manager.

But that’s just the start – you also want to enforce a password rotation policy so users change their password regularly.

In this example, we’ll require users to change their password every 30 days.

To enforce this, open /etc/login.defs and set the PASS_MAX_DAYS variable like this:

PASS_MAX_DAYS 30

This means each user must change their password within 30 days of the last password update.

After adding a new user, you can verify their expiration policy with:

chage -l username

This command shows info like when the password was last changed and when it will expire.

💡
Note: Any user can run this command for their own account – no root privileges needed – but they can’t view or modify anyone else’s data.

Let’s test this by adding a user named elie and checking the output:

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, Elie’s password will expire 30 days after it was last set. He’ll also receive a warning starting 7 days before expiration – in this case, beginning on Feb 17, 2024.

Optional: Control When Passwords Can Be Changed

By default, users can change their password any time before the expiration date – even daily. While that might sound secure, it’s not a policy I recommend.

Personally, I prefer users to only change their password during the 7-day warning window. That way, they can’t rotate passwords unnecessarily – and are gently nudged to update only when needed.

To do this, we set the PASS_MIN_DAYS variable in /etc/login.defs to 23, like so:

PASS_MIN_DAYS 23

This setting ensures users must wait at least 23 days before changing their password again – which lines up perfectly with the 7-day warning period (days 24–30 after their last password change).

Implementing sudo the Right Way

When sudo is set up properly, it significantly boosts the security of your server.

Here’s what I love about sudo, and why I always recommend using it over the root account:

  • Granular control: Give some users full root access, while limiting others to specific commands only.
  • No root password sharing: Users authenticate with their own password – no need to distribute the root password to your whole team.
  • Better security: You can disable the root account, making brute-force attacks far less effective. Attackers also won't know which usernames have elevated privileges.
  • Accountability: Every use of sudo is logged, so you can track exactly who did what and when.

And that’s just scratching the surface – let’s look at how to implement it securely and effectively.

Granting Full Root Privileges to a User

You’ve probably seen this recommended before: create a non-root user, add them to the sudo group, and disable root login.

But have you ever wondered what actually gives that group its power?

The answer lies in the /etc/sudoers file – the heart of sudo configuration.

How sudo Permissions Work

Whenever a user tries to run a command with sudo, the server checks the /etc/sudoers file to see if they’re allowed to do so.

To safely edit that file, use the visudo command. Never edit it directly with a regular text editor, or you risk locking yourself out due to syntax errors.

If visudo opens with an editor you don’t like (e.g., nano), you can change it:

update-alternatives --config editor

Select your preferred editor and then run visudo again.

Inside the file, you’ll see something like this:

# 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 % symbol means it's referencing a group. So when a user is added to the sudo group, they inherit full root-level privileges.

Let’s break down that last line:

  • ALL: Applies on all hosts
  • (ALL:ALL): Run commands as any user and any group
  • ALL: Run any command

That’s full root access – without needing to touch the root password.

Add a User to the sudo Group

Here’s how to add a user named ivan to the sudo group:

usermod -aG sudo ivan

Now, Ivan can run commands like this:

sudo apt update

...and enter his own password – not the root user’s.

💡
Note: On Red Hat-based servers (like CentOS or RHEL), the equivalent group is called wheel instead of sudo.

Manual sudo Privileges in the sudoers File

You don’t have to rely on groups. You can give individual users specific rules.

Let’s say you want to give full privileges to ivan. In the sudoers file, just add this line under the root entry:

ivan    ALL=(ALL:ALL) ALL

This grants Ivan the same full access as root – across all users, groups, hosts, and commands.

Add a User with sudo From the Start

Want to create a new user and give them sudo access immediately? Use this:

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

Let’s break that down:

  • -G sudo: Adds Ivan to the sudo group
  • -m: Creates a home directory
  • -d /home/ivan: Sets the home directory path
  • -s /bin/bash: Sets the default shell to Bash

This saves a step and gets your user set up with privileges right away.

Customized User Privileges

So far, we’ve seen how to give users full root access with sudo. But most of the time, you don’t want to do that.

Instead, it’s best to follow the principle of least privilege: give each user just the access they need to do their job – and nothing more.

Let me show you how to set that up with sudo.

Scenario: Software Team Access Only

Imagine this situation: you have two users on the Software team – ivan and john. You want them to be able to install and manage packages, but not touch anything else on the server.

Sure, you could add separate entries for each of them in the sudoers file, but there’s a better, cleaner way – using aliases.

Open the sudoers file with visudo, and create a user alias for the Software team:

# User alias specification
User_Alias SOFTWAREADMINS = ivan, john

This way, instead of repeating usernames, you can refer to SOFTWAREADMINS as a group.

Now, define a command alias for the allowed software-related commands:

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

You can add more commands here if needed – just separate them with commas.

Finally, give the SOFTWAREADMINS group permission to run those commands using sudo:

SOFTWAREADMINS ALL=(ALL:ALL) SOFTWARECOMMANDS

This line says: All users in SOFTWAREADMINS can run SOFTWARECOMMANDS as any user or group, on any host.

The beauty of this setup is how easy it is to manage:

  • If someone joins the Software team, just add their username to the SOFTWAREADMINS alias.
  • If you need to allow a new command, just add it to the SOFTWARECOMMANDS alias.

This keeps your sudoers file clean, readable, and scalable – especially on servers with multiple roles or teams.

Be Careful: How You Specify Commands in sudo Matters

Here’s a common mistake I’ve seen (and made myself early on): defining a command in a sudo alias without any arguments.

Let’s break down what that means – and why it can be risky.

The Problem With Broad Command Permissions

In our earlier example, we created this command alias:

Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt, /usr/bin/apt

At first glance, it seems fine – we’re just giving access to package management commands, right?

But here’s the issue: when you list a command alone, users can run it with any arguments, subcommands, or options.

This means:

  • They can install software
  • But also removepurge, or even break things unintentionally

That’s probably not what you want.

Limiting What Commands Can Actually Do

Let’s say we only want users to be able to update and install software – not remove or purge it.

You might try this:

Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install

This seems safer – but try running:

sudo apt install postfix

It won’t work.

Why? Because we didn’t specify any argument for install. As far as sudo is concerned, only apt install with no arguments is allowed – and that’s useless.

To allow users to run apt install with actual package names, modify the alias like this:

Cmnd_Alias SOFTWARECOMMANDS = /usr/bin/apt update, /usr/bin/apt upgrade, /usr/bin/apt install *

The * wildcard tells sudo: Allow any arguments after this command.

Now users can do:

sudo apt install nginx
sudo apt update
sudo apt upgrade

But they still can’t run other unrelated or destructive commands.

Same Rule, Different Context: Managing Services

This principle applies beyond just package management.

Let’s say you're managing a services team who needs to check the status of running services. You might be tempted to write:

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

Sounds reasonable, right?

Wrong. This would give them access to commands like:

sudo systemctl reboot
sudo systemctl isolate rescue.target

That's way more access than you intended.

Restricting systemctl to Status Only

To allow only safe, read-only actions, like checking service status, define the alias more precisely:

Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl status *

Now users can run:

sudo systemctl status ssh
sudo systemctl status nginx

But they can’t restart, stop, or disable services – which keeps the server safe.

A More Specific Example

Let’s say you trust a user to manage only the SSH service, but allow full control over it (start, stop, restart, etc.).

Use this:

Cmnd_Alias SERVICECOMMANDS = /usr/bin/systemctl * ssh

This allows things like:

sudo systemctl restart ssh
sudo systemctl status ssh

But they still can’t touch nginxmysql, or anything else.

Best Practices for Command Aliases

  • Never specify a command alone (e.g., just /usr/bin/systemctl)
  • Always include arguments or wildcards to precisely define what's allowed
  • Think through what the user really needs – and limit everything else
  • Test your configuration with sudo -l as the user to confirm permissions
⚠️
My rule of thumb: Never leave a command bare in a sudo alias. Be specific about what comes after – it’s the only way to safely control what users can do.

Mitigating Shell Escapes for Users

Some programs – like text editors and pagers – allow users to escape into a shell and run commands without exiting the program. This feature is called a Shell Escape, and it can be a serious security risk if left unchecked.

Let me show you what that looks like.

Example: Escaping Shell From vim

Let’s say you're working inside vim, and you run this:

:!ls

This will list files in the current directory – like so:

bib.csv  lilly.sh  script.sh

Seems harmless, right? But here’s where it becomes a problem...

When Limited sudo Access Becomes Full Root Access

Let’s say we gave a user – ivan – permission to edit the SSH config file using vim:

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

That should mean Ivan can only edit that one file.

But now watch what happens if Ivan runs this inside vim:

:!apt update

Or worse:

:!bash

Output:

root@testing:/home/ivan#

Just like that, Ivan is in a full root shell – with unrestricted access to the entire server. All from within vim.

Fix 1: Use sudoedit Instead

To avoid shell escapes entirely, use sudoedit instead of directly calling vim:

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

With sudoedit, users still get to edit the file, but without the ability to escape into a root shell.

If Ivan tries to run :!bash, they'll land in their own unprivileged shell – not root.

Fix 2: Use the NOEXEC Flag

Some other programs like lessmore, or emacs also support shell escapes. For these, you can use the NOEXEC option in the sudoers file to block those escape attempts.

For example:

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

Now, Ivan can still view the file using less, but cannot drop into a shell from within the program.

Why Not Just Use NOEXEC With vim?

You can – and it works:

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

But in this guide, I wanted to show both methods – because sometimes using sudoedit is cleaner, especially when the user only needs to edit a file, not run a full program like vim.

Preventing Abuse of Shell Scripts

Let’s say Ivan writes a shell script that requires root privileges to run. He asks you to allow it using sudo.

You might be tempted to add this line to your sudoers file:

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

Now Ivan can run the script as root – but here's the problem: he owns the script.

And since he owns it, he can edit it anytime he wants. That means he could add something like:

sudo -i

...to open a full root shell from inside the script – essentially giving himself unrestricted access. That’s a serious security hole.

The Right Way to Handle User-Created Scripts

Here's the safe process I always follow:

  1. Let the user create and test the script in their own home directory
  2. Review the script thoroughly
  3. Move the script to a trusted server directory, like /usr/local/sbin
  4. Change the owner to root and restrict write access
  5. Then – and only then – grant sudo access to that script

Now, Ivan can run the script with elevated privileges, but he can’t modify it – which prevents abuse.

Viewing Your sudo Privileges

Not sure what you’re allowed to do with sudo? There’s a simple way to check:

sudo -l

This command lists your current sudo privileges, along with any environment-related settings applied through sudo.

💡
Tip: Run sudo -l often, especially when troubleshooting or verifying access. It's a simple way to confirm exactly what commands you’ve been granted – and whether your sudoers entries are working as expected.

Understanding the sudo Timer

Ever notice how, after entering your password once with sudo, you can run more sudo commands for a while without being asked again?

That’s thanks to the sudo timer. By default, sudo remembers your authentication for 5 minutes. During that window, you can run additional sudo commands without re-entering your password.

While convenient, this can be a security risk – especially on shared servers or if a user steps away from their keyboard without locking the terminal.

To avoid this, you might want to disable the sudo timer entirely, requiring the password every time sudo is used.

How to Disable the sudo Timer

To make sudo always prompt for a password, add this line to the Defaults section of the sudoers file (using visudo):

Defaults timestamp_timeout=0

Now, users will be prompted for their password every time they use sudo.

If you're working on a server where the timer is still active (e.g., the default 5-minute window), and you’re about to walk away but don’t want to close the terminal, you can manually expire the session with:

sudo -k

This immediately clears the cached credentials. Any future use of sudo will require re-authentication – even if it’s within the timer window.

Monitoring User Activities with sudo

One of the best things about sudo – beyond security – is transparency.

Every time a user runs a command with sudo, it gets logged. This gives you a clear audit trail of who did what and when.

By default, sudo logs activity in your server’s main authentication log – usually:

/var/log/auth.log

While that works fine, I personally prefer keeping sudo logs separate. It makes things cleaner and easier to investigate when you're troubleshooting or auditing user behavior.

To direct all sudo activity to its own dedicated log file, just add this line to the Defaults section of your sudoers file (use visudo):

Defaults logfile=/var/log/sudo.log

Once set, all sudo commands – both successful and failed – will be logged to:

/var/log/sudo.log

This includes:

  • What command was run
  • When it was run
  • By which user
  • And whether it succeeded or failed

It's a small change that goes a long way in maintaining visibility on your server.

Conclusion and Final Thoughts

Awesome job making it to the end!

I hope this guide has given you a clear, practical understanding of how to securely manage users on a Linux server. If you’ve followed along and implemented these steps, you’ve already taken meaningful action to strengthen your server’s security.

👉
Looking for more? Check out my full collection of in-depth Linux server security guides.

💬 Found this guide helpful?

I'd love to hear about your experiences, questions, or ideas in the discussion section below. Your feedback not only helps improve future guides – it helps fellow admins on their own journey.

Prefer a more direct conversation? Feel free to contact me anytime.