ssh -i ~/.ssh/your-key.pem ec2-user@<ec2-public-ip>Linux Users
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.
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-userUnlike 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-userChanging 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-useruid=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_keysBoth 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%%{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
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:
whoamitest-userThen confirm sudo escalates to root:
sudo whoamiWe 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:
rootCreating 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-trajectoryThis 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.
Recap
wheel(notsudo) is the group that grants admin rights on Amazon Linux 2 (check withidif you’re unsure which convention an AMI uses).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.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 nosudorights unless the app genuinely needs them.