Skip to main content

Create a non-root user with an SSH key and sudo

Using the root user all the time is risky, and most tasks on a server do not require full privileges. In this lesson we'll create an unprivileged user with sudo access.

Why not root directly

You can certainly use root for all the things when managing a WordPress server, however there are quite a few risks associated with doing so, such as typos, audits, blast radius when things go wrong, and even compliance.

Sticking to the least privilege principle is a great security practice, especially if you'll be granting other people access to your server in the future. Using sudo allows you to set up granular control and keep a log of privileged commands and attempts.

Escape hatch

When working on things that could potentially lock you out, it's recommended to keep a separate root shell open while testing SSH settings in a different shell. That way if you make a mistake you still have a chance to correct it instead of being locked out completely.

Creating a new user

In this course we will stick to just one user with full sudo privileges. I'll name mine karl but you're welcome to use a different name. While logged in as root, use useradd to create a new user, and usermod to add them to the sudo group:

useradd -m -s /bin/bash karl
usermod -aG sudo karl

The -m flag automatically creates a home directory for karl at /home/karl. We do need a home directory for karl to store an authorized SSH key, and some other configuration files in the future.

The -s /bin/bash flag sets the user's default shell to /bin/bash.

Authorized keys

Using a password to log in is insecure these days, so we'll be using an SSH key. You can use an existing key or create a new one for this server specifically. The GitHub Docs site has everything you need to know about SSH keys, passphrases and agents.

Let's add a new authorized_keys file to our configuration repository under the misc directory. Place the public portion of your SSH key into this file, commit and push it. You can do this directly on the server as the root user, or push it from a local clone, and pull to the server from GitHub:

cd /config
echo "ssh-ed25519 AAAAC3NzaC..." > misc/authorized_keys
git add misc/authorized_keys
git commit -m "Adding authorized_keys"
git push

Now while still as root, let's create a symbolic link to that file from Karl's authorized_keys:

mkdir -p /home/karl/.ssh
chown karl:karl /home/karl/.ssh
ln -s /config/misc/authorized_keys /home/karl/.ssh/authorized_keys

Here I created a new .ssh directory and made sure karl is the owner and group. Then I created the symbolic link using ln. This allows me to log in as karl using that SSH key. However there are a couple more things we need to configure before using this new user.

Passwordless sudo

By default sudo will ask for the user's password when invoked. Since creating the karl user we haven't assigned them a password, so it will be impossible to invoke sudo. We can solve this by setting a password for Karl:

passwd karl

Or we can configure sudo to not ask for a password at all for this user. While a tiny bit less secure, I prefer this option since I already have a passphrase on my SSH key which is used to access the server.

Let's create a new configuration file in /config/misc/sudoers with the following contents:

karl ALL=(ALL) NOPASSWD: ALL

Don't forget to use a different username if you're not Karl. Commit and push this change, and create a symbolic link for this sudoers file.

ln -s /config/misc/sudoers /etc/sudoers.d/sudoers

Now, let's log out of the server as root and log back in as karl using the SSH key and make sure sudo is working:

ssh [email protected]
sudo id

The sudo id command runs id as the root user, so your output should begin with uid=0(root). You can also use the sudo --list command to check the sudo configuration for the current user. Here's what I see:

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

User karl may run the following commands on origin:
    (ALL : ALL) ALL
    (ALL) NOPASSWD: ALL

Now karl has pretty much the same privileges as root when using sudo, so there is no longer a reason to use root directly. Let's make a few tweaks to make sure the root user stays secure.

Note that going forward, when you are logged in as a non-root user, you should always use sudo when interacting with our configs repository (to retain correct ownership). If you're relying on SSH agent forwarding to pull/push changes, you'll need to pass the current environment with sudo -E:

cd /config
sudo -E git pull

Same goes for creating new files or editing existing files. If you made a mistake, you can change ownership of an existing file using chown.

Disabling root login

Let's create a new /config/misc/sshd.conf file in our configuration repo with the following contents:

PasswordAuthentication no
PermitRootLogin no

This disables password authentication (for SSH) and disallows the root user to log in via SSH. Commit and push this file, and add the following symlink:

ln -s /config/misc/sshd.conf /etc/ssh/sshd_config.d/sshd.conf

Next, restart the SSH service:

sudo systemctl restart ssh

Exit the system and try logging back in as root:

ssh [email protected]

You should see a similar error message:

[email protected]: Permission denied (publickey).

Finally, if your root user had a password set, we can make sure it's locked using the passwd command. Run this as karl (or equivalent) user on your server:

sudo passwd -l root

This locks the password, but doesn't disable the account itself. You're still able to use sudo and even sudo su to get a root shell.

Symlinks

You may have noticed a trend: all our configuration files live in the /config repository and then symlinked to the appropriate place. We've done this with authorized_keys, sshd.conf and sudoers, and we'll do a lot more in the coming lessons.

This technique allows us to keep everything in a single repository, rather than multiple smaller repositories scattered around the system. It does come with a slight drawback though and that's symlink management.

It's very easy to remove a symlink, intentionally or accidentally, and restoring them might be tricky. You can keep a list in the README.md file for reference, though I usually prefer to have a helper script to restore all the symlinks for me.

This is especially useful for disaster recovery and migrations. Here's an example with the three files we covered in this lesson:

#!/bin/bash
mkdir -p /home/karl/.ssh
chown karl:karl /home/karl/.ssh
ln -sfn /config/misc/authorized_keys /home/karl/.ssh/authorized_keys
ln -sfn /config/misc/sshd.conf /etc/ssh/sshd_config.d/sshd.conf
ln -sfn /config/misc/sudoers /etc/sudoers.d/sudoers

Write this to bin/symlinks.sh and make it executable with:

chmod +x bin/symlinks.sh

Don't forget to commit and push the helper script to our configs repository. Now you can easily restore all symlinks using the helper script with:

sudo /config/bin/symlinks.sh

You can get a lot more complex than this, with dynamic file lists, permission checks, file and directory modes and more. Feel free to build that complexity for this and other helper scripts, however, I will try to keep things as simple as possible for learning purposes.

Enroll
Enjoying the course content? Enroll today to keep track of your progress, access premium lessons and more.