The canonical guide to setting up a Linux server securely for the first time. Follow this whenever you spin up a new EC2 instance. DigitalOcean →
Nearly every production server on the planet runs Linux. AWS EC2 instances are Linux. Docker containers are Linux. Nginx runs on Linux. You don't need to become a Linux expert — but you need to be comfortable enough to connect to a server, inspect what's running, and not feel lost when things break.
Think of the Linux terminal like Node.js's REPL — a direct interface to the system. Commands are functions. Flags are arguments. Pipes (|) chain them like .then().
SSH (Secure Shell) lets you run commands on a remote machine as if you were sitting at its keyboard. Every AWS EC2 connection is SSH.
# Basic SSH connection
ssh ubuntu@your-server-ip
# With a specific key file (required for EC2)
ssh -i ~/.ssh/my-key.pem ubuntu@your-server-ip
# Copy files TO server
scp -i ~/.ssh/my-key.pem ./myfile.txt ubuntu@your-server-ip:~/
# Copy files FROM server
scp -i ~/.ssh/my-key.pem ubuntu@your-server-ip:~/logfile.txt ./
AWS will refuse to connect if your .pem file is readable by others. Fix it: chmod 400 ~/.ssh/my-key.pem
SSH uses asymmetric cryptography: you have a private key (never share this) and a public key (put this on the server). AWS generates the key pair for you when you create an EC2 instance. Generate your own for other servers:
# Generate an Ed25519 key pair (modern, secure)
ssh-keygen -t ed25519 -C "your@email.com"
# View your public key (put this on servers)
cat ~/.ssh/id_ed25519.pub
You need exactly these. Learn them, forget the rest for now.
pwd # Where am I? (print working directory)
ls -la # List files (long format, show hidden)
cd /var/log # Change directory
cd ~ # Go home
mkdir -p app/logs # Create directory (and parents)
rm -rf ./dist # Delete recursively — careful!
cp file.txt backup.txt
mv old.txt new.txt
cat /etc/nginx/nginx.conf # Print file contents
less /var/log/nginx/error.log # Read large files (q to quit)
tail -f /var/log/app.log # Watch live log output (Ctrl+C to stop)
ps aux # All running processes
ps aux | grep node # Find your Node process
kill -9 12345 # Force kill process by PID
htop # Interactive process viewer (install: apt install htop)
On Ubuntu, apps run as systemd services. This is how Nginx, Docker, and your app start on boot and restart on crash.
sudo systemctl status nginx # Is it running?
sudo systemctl start nginx # Start it
sudo systemctl stop nginx # Stop it
sudo systemctl restart nginx # Restart
sudo systemctl reload nginx # Reload config (no downtime)
sudo systemctl enable nginx # Start on boot
sudo journalctl -u nginx -f # Live logs for a service
curl http://localhost:3000/health # Test if your app responds
curl -I https://your-domain.com # Check response headers
netstat -tlnp # What's listening on which port?
ss -tlnp # Modern alternative to netstat
sudo apt update # Update package index
sudo apt upgrade -y # Upgrade installed packages
sudo apt install -y nginx # Install a package
sudo apt remove nginx # Remove a package
which nginx # Where is this binary?
nginx -v # Check version
chmod 644 file.txt # Owner: read+write. Others: read only
chmod 755 script.sh # Owner: full. Others: read+execute
chmod 400 key.pem # Owner: read only (required for SSH keys)
chown ubuntu:ubuntu file.txt # Change owner
Never hardcode secrets. On Linux, environment variables are set per-session or in system files:
# Temporary (current session only)
export DATABASE_URL="postgres://..."
# Permanent for a user (add to ~/.bashrc or ~/.profile)
echo 'export NODE_ENV=production' >> ~/.bashrc
source ~/.bashrc
# For a systemd service (edit the service file)
sudo systemctl edit myapp
# Add under [Service]:
# Environment="NODE_ENV=production"
# EnvironmentFile=/etc/myapp/env
In production, use AWS Secrets Manager or a .env file that is never committed to git. We cover this in Lesson 16 (RDS & Secrets Manager).
Every new server gets this treatment:
sudo apt update && sudo apt upgrade -yadduser deploy, add to sudo: usermod -aG sudo deployrsync --archive --chown=deploy:deploy ~/.ssh /home/deploy/etc/ssh/sshd_config → PermitRootLogin nosudo ufw allow OpenSSH && sudo ufw allow 'Nginx Full' && sudo ufw enablesudo apt install -y nginx git curl