Managing Users on Linux Server Securely
Guide to securing your Linux server by managing users, controlling access, and configuring sudo safely.
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.
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.
Want more security tips and server hardening guides? Subscribe to my newsletter for practical advice delivered directly to your inbox!
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.
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)
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.
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.
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.
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 lengthminclass
: 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.
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.
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 groupALL
: 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.
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 remove, purge, 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 nginx
, mysql
, 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
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 less
, more
, 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:
- Let the user create and test the script in their own home directory
- Review the script thoroughly
- Move the script to a trusted server directory, like
/usr/local/sbin
- Change the owner to root and restrict write access
- 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
.
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.
💬 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.
Discussion