Skip to main content

Linux Users

Published

2026-11-06

This chapter sets up two accounts on the EC2 instance: a sudo enabled personal account for daily work, and a locked down service account that will run the baseball-trajectory app.

Log in

Log back into the EC2 instance as ec2-user (the Linux Commands chapter covers the SSH options and what to expect after connecting). The AWS Server Login and Managing SSH key pairs chapters are there if you need a refresher.

ssh -i ~/.ssh/your-key.pem ec2-user@<ec2-public-ip>

In the last chapter we got our bearings, confirmed ec2-user belongs to wheel, and read the man page for useradd, so now we can put that to work and create a new user.

Create a non-root user

Everything so far has run as ec2-user, which already has sudo. That’s fine for completing the labs in DO4DS, but on a real life shared server we’d want a personal account for daily work, with sudo granted on purpose (instead of handed out by default).

Create the account:

sudo useradd test-user

Unlike Ubuntu’s adduser, Amazon Linux 2’s useradd doesn’t prompt for anything; it just creates the account and home directory silently.

Set a password separately:

sudo passwd test-user
Changing password for user test-user.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.

Add test-user to wheel so it has sudo:

sudo usermod -aG wheel test-user

-aG appends the group without disturbing any groups the user already belongs to; leaving off -a would replace them.

Confirm it took:

id test-user
uid=1001(test-user) gid=1001(test-user) groups=1001(test-user),10(wheel)

Add an SSH key for your new user

test-user has a password now, but password authentication over SSH is disabled on this instance, same as it is for ec2-user. The account needs its own .ssh directory and an authorized_keys file before it can log in at all.

I already have a public key trusted for ec2-user (from Managing SSH key pairs), so the fastest path is reusing it rather than generating a new pair just for this exercise.

First I create a .ssh/ folder in test-user’s home directory and copy over ec2-user’s authorized_keys:

sudo mkdir -p /home/test-user/.ssh
sudo cp ~/.ssh/authorized_keys /home/test-user/.ssh/authorized_keys

Both commands ran via sudo, so the new files belong to root, not test-user. Hand them over and lock them down (SSH silently ignores a .ssh directory or key file that’s group or world readable):

# change ownership
sudo chown -R test-user:test-user /home/test-user/.ssh
# change permissions
sudo chmod 700 /home/test-user/.ssh
sudo chmod 600 /home/test-user/.ssh/authorized_keys

chown and chmod solve two different problems, and both showed up in the commands above.

  • chown user:group file changes who owns a file or directory - the user and the group. sudo chown -R test-user:test-user /home/test-user/.ssh handed the .ssh folder from root (who created it via sudo) to test-user, recursively (-R) so the authorized_keys file inside came along with it.

  • chmod mode file changes what the owner, group, and everyone else are allowed to do with a file - read, write, execute. chmod 700 restricts a directory to the owner only (read/write/execute); chmod 600 restricts a file to owner read/write, no execute. SSH enforces these exact permissions on .ssh and authorized_keys and silently refuses to use anything looser.

In short: chown decides who the file belongs to, chmod decides what they (and everyone else) can do with it.

%%{init: {'theme': 'base', 'look': 'classic', 'themeVariables': {'fontFamily': 'monospace'}}}%%

graph LR
    subgraph Laptop["Your Computer"]
      subgraph priv["Private Key"]
        pkey["<strong>~/.ssh/your-key-name</strong>"]
      end
    end
    subgraph EC2["EC2 Instance"]
        ecuser[("ec2-user<br/>authorized_keys")]
        testuser[("test-user<br/>authorized_keys<br/>(<em>copied</em>)")]
    end
    pkey -->|"<strong>ssh ec2-user@...</strong>"| ecuser
    pkey -->|"<strong>ssh test-user@...</strong>"| testuser

    style pkey font-family:monospace
    style Laptop fill:#E8E8E8,stroke:#333,stroke-width:2px,color:#000
    style EC2 fill:#1B2A41,stroke:#fff,stroke-width:2px,color:#fff
    style priv fill:#D2562B,stroke:#fff,stroke-width:1px,color:#fff
    style ecuser fill:#2A6F77,stroke:#fff,stroke-width:1px,color:#fff,font-family:monospace
    style testuser fill:#2A6F77,stroke:#fff,stroke-width:1px,color:#fff,font-family:monospace

One Trusted Key, Two Local Accounts

The same private key on my computer now opens two separate accounts, because SSH only checks whether the key I present matches a line in the authorized_keys of the account I’m connecting to.

From a new terminal (leave the ec2-user session open in case something’s wrong), test the new account:

ssh -i ~/.ssh/your-key-name test-user@<ec2-public-ip>

If successful, you’ll see the following:

   ,     #_
   ~\_  ####_        Amazon Linux 2
  ~~  \_#####\
  ~~     \###|       AL2 End of Life is 2026-06-30.
  ~~       \#/ ___
   ~~       V~' '->
    ~~~         /    A newer version of Amazon Linux is available!
      ~~._.   _/
         _/ _/       Amazon Linux 2023, GA and supported until 2029-06-30.
       _/m/'           https://aws.amazon.com/linux/amazon-linux-2023/
       
[test-user@ip-your-ip-address ~]$

Confirm the account’s identity with whoami:

whoami
test-user

Then confirm sudo escalates to root:

sudo whoami
We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for test-user:
root

Creating a dedicated service account

test-user above is a human account: it has a password, a home directory, and (once granted) the same sudo rights I use interactively. A service account exists to run one thing and nothing else, so it gets a narrower shape: no login shell, no password, no reason for anyone to ever land a session on it.

sudo useradd -r -s /sbin/nologin baseball-trajectory

-r puts it in the system UID range rather than the normal human range, so it never collides with, or gets mistaken for, somebody’s actual login. -s /sbin/nologin means any attempt to log in as baseball-trajectory, over SSH or locally, is rejected outright:

sudo su - baseball-trajectory
This account is currently not available.

That’s the message you want to see. If su - into it ever succeeds, the account is doing more than its job requires.

Give it ownership of the state it writes to, but not the code it runs. baseball-trajectory makes that easy: it keeps nothing on disk, so the account owns nothing at all. /opt/baseball-trajectory stays owned by root and readable by everyone, which means a compromised process can’t rewrite its own source.

It also needs no sudo rights. The app serves requests and calls an external API, and nothing else, so there’s nothing to grant.

ImportantRoot is a starting point, not a destination

Running a service as root is the fastest way to a working deployment, and it’s a reasonable way to confirm the plumbing works end to end. It is not what I’d leave in place.

The tighter option is a dedicated service account with no privilege beyond what the app actually uses.

A few things worth holding to for any service account, not just this one:

  • No login shell and no password. The account exists to run a process, not to be logged into, so /sbin/nologin and a locked password are the default, not an exception you add later.
  • System UID range (-r). Keeps it out of the human login range and out of any audit that lists who can access the box.
  • Own the state, not the code. The account should be able to write to its own data directory and have no write access to the application files it’s running.
  • No sudo unless the app truly needs it. If it does, grant it by allowlist in /etc/sudoers.d/, one line per verb and unit, never a bare ALL grant.

Decide which side of it you want to live on before you hand out logins, not after. Retrofitting a scoped account onto a team that’s already used to root access is a far harder conversation than starting there.

Recap

  1. wheel (not sudo) is the group that grants admin rights on Amazon Linux 2 (check with id if you’re unsure which convention an AMI uses).

  2. SSH doesn’t care which account is logging in, only whether the presented private key matches a line in that account’s authorized_keys. Copying a trusted key to a new account is enough; a new key pair isn’t required.

  3. A service account is the opposite of a human one: -r -s /sbin/nologin, ownership of its own state (if any) but never its code, and no sudo rights unless the app genuinely needs them.